Merge branch 'master' into admin-email-confirmation

This commit is contained in:
Moriz Wahl 2022-02-11 15:03:29 +01:00
commit 14373ff302
15 changed files with 539 additions and 705 deletions

View File

@ -422,7 +422,7 @@ jobs:
report_name: Coverage Admin Interface report_name: Coverage Admin Interface
type: lcov type: lcov
result_path: ./coverage/lcov.info result_path: ./coverage/lcov.info
min_coverage: 93 min_coverage: 94
token: ${{ github.token }} token: ${{ github.token }}
############################################################################## ##############################################################################

View File

@ -0,0 +1,86 @@
<template>
<div class="component-open-creations-table">
<b-table-lite :items="items" :fields="fields" caption-top striped hover stacked="md">
<template #cell(bookmark)="row">
<b-button
variant="danger"
size="md"
@click="$emit('remove-creation', row.item)"
class="mr-2"
>
<b-icon icon="x" variant="light"></b-icon>
</b-button>
</template>
<template #cell(edit_creation)="row">
<b-button variant="info" size="md" @click="rowToogleDetails(row, 0)" class="mr-2">
<b-icon :icon="row.detailsShowing ? 'x' : 'pencil-square'" aria-label="Help"></b-icon>
</b-button>
</template>
<template #cell(confirm)="row">
<b-button variant="success" size="md" @click="$emit('show-overlay', row.item)" class="mr-2">
<b-icon icon="check" scale="2" variant=""></b-icon>
</b-button>
</template>
<template #row-details="row">
<row-details
:row="row"
type="show-creation"
slotName="show-creation"
:index="0"
@row-toogle-details="rowToogleDetails"
>
<template #show-creation>
<div>
<edit-creation-formular
type="singleCreation"
:creation="row.item.creation"
:item="row.item"
:row="row"
:creationUserData="creationUserData"
@update-creation-data="updateCreationData"
@update-user-data="updateUserData"
/>
</div>
</template>
</row-details>
</template>
</b-table-lite>
</div>
</template>
<script>
import { toggleRowDetails } from '../../mixins/toggleRowDetails'
import RowDetails from '../RowDetails.vue'
import EditCreationFormular from '../EditCreationFormular.vue'
export default {
name: 'OpenCreationsTable',
mixins: [toggleRowDetails],
components: {
EditCreationFormular,
RowDetails,
},
props: {
items: {
type: Array,
required: true,
},
fields: {
type: Array,
required: true,
},
},
methods: {
updateCreationData(data) {
this.creationUserData.amount = data.amount
this.creationUserData.date = data.date
this.creationUserData.memo = data.memo
this.creationUserData.moderator = data.moderator
data.row.toggleDetails()
},
updateUserData(rowItem, newCreation) {
rowItem.creation = newCreation
},
},
}
</script>

View File

@ -0,0 +1,125 @@
<template>
<div class="search-user-table">
<b-table-lite :items="items" :fields="fields" caption-top striped hover stacked="md">
<template #cell(creation)="data">
<div v-html="data.value"></div>
</template>
<template #cell(show_details)="row">
<b-button
variant="info"
size="md"
v-if="row.item.emailChecked"
@click="rowToogleDetails(row, 0)"
class="mr-2"
>
<b-icon :icon="row.detailsShowing ? 'eye-slash-fill' : 'eye-fill'"></b-icon>
</b-button>
</template>
<template #cell(confirm_mail)="row">
<b-button
:variant="row.item.emailChecked ? 'success' : 'danger'"
size="md"
@click="rowToogleDetails(row, 1)"
class="mr-2"
>
<b-icon
:icon="row.item.emailChecked ? 'envelope-open' : 'envelope'"
aria-label="Help"
></b-icon>
</b-button>
</template>
<template #cell(has_elopage)="row">
<b-icon
:variant="row.item.hasElopage ? 'success' : 'danger'"
:icon="row.item.hasElopage ? 'check-circle' : 'x-circle'"
></b-icon>
</template>
<template #cell(transactions_list)="row">
<b-button variant="warning" size="md" @click="rowToogleDetails(row, 2)" class="mr-2">
<b-icon icon="list"></b-icon>
</b-button>
</template>
<template #row-details="row">
<row-details
:row="row"
type="singleCreation"
:slotName="slotName"
:index="slotIndex"
@row-toogle-details="rowToogleDetails"
>
<template #show-creation>
<div>
<creation-formular
type="singleCreation"
pagetype="singleCreation"
:creation="row.item.creation"
:item="row.item"
:creationUserData="creationUserData"
@update-user-data="updateUserData"
/>
</div>
</template>
<template #show-register-mail>
<confirm-register-mail-formular
:checked="row.item.emailChecked"
:email="row.item.email"
:dateLastSend="
row.item.emailConfirmationSend
? $d(new Date(row.item.emailConfirmationSend), 'long')
: ''
"
/>
</template>
<template #show-transaction-list>
<creation-transaction-list-formular :userId="row.item.userId" />
</template>
</row-details>
</template>
</b-table-lite>
</div>
</template>
<script>
import CreationFormular from '../CreationFormular.vue'
import ConfirmRegisterMailFormular from '../ConfirmRegisterMailFormular.vue'
import RowDetails from '../RowDetails.vue'
import CreationTransactionListFormular from '../CreationTransactionListFormular.vue'
import { toggleRowDetails } from '../../mixins/toggleRowDetails'
const slotNames = ['show-creation', 'show-register-mail', 'show-transaction-list']
export default {
name: 'SearchUserTable',
mixins: [toggleRowDetails],
components: {
CreationFormular,
ConfirmRegisterMailFormular,
CreationTransactionListFormular,
RowDetails,
},
props: {
items: {
type: Array,
required: true,
},
fields: {
type: Array,
required: true,
},
},
data() {
return {
creationUserData: {},
}
},
methods: {
updateUserData(rowItem, newCreation) {
rowItem.creation = newCreation
},
},
computed: {
slotName() {
return slotNames[this.slotIndex]
},
},
}
</script>

View File

@ -0,0 +1,35 @@
<template>
<div class="component-select-users-table">
<b-table-lite :items="items" :fields="fields" caption-top striped hover stacked="md">
<template #cell(bookmark)="row">
<div>
<b-button
v-if="row.item.emailChecked"
variant="warning"
size="md"
@click="$emit('push-item', row.item)"
class="mr-2"
>
<b-icon icon="plus" variant="success"></b-icon>
</b-button>
<div v-else>{{ $t('e_mail') }}!</div>
</div>
</template>
</b-table-lite>
</div>
</template>
<script>
export default {
name: 'SelectUsersTable',
props: {
items: {
type: Array,
required: true,
},
fields: {
type: Array,
required: true,
},
},
}
</script>

View File

@ -0,0 +1,26 @@
<template>
<div class="component-selected-users-table">
<b-table-lite :items="items" :fields="fields" caption-top striped hover stacked="md">
<template #cell(bookmark)="row">
<b-button variant="danger" size="md" @click="$emit('remove-item', row.item)" class="mr-2">
<b-icon icon="x" variant="light"></b-icon>
</b-button>
</template>
</b-table-lite>
</div>
</template>
<script>
export default {
name: 'SelectedUsersTable',
props: {
items: {
type: Array,
required: true,
},
fields: {
type: Array,
required: true,
},
},
}
</script>

View File

@ -1,343 +0,0 @@
import { mount } from '@vue/test-utils'
import UserTable from './UserTable.vue'
const localVue = global.localVue
const apolloQueryMock = jest.fn()
apolloQueryMock.mockResolvedValue()
describe('UserTable', () => {
let wrapper
const defaultItemsUser = [
{
userId: 1,
firstName: 'Bibi',
lastName: 'Bloxberg',
email: 'bibi@bloxberg.de',
creation: [200, 400, 600],
emailChecked: true,
},
{
userId: 2,
firstName: 'Benjamin',
lastName: 'Blümchen',
email: 'benjamin@bluemchen.de',
creation: [1000, 1000, 1000],
emailChecked: true,
},
{
userId: 3,
firstName: 'Peter',
lastName: 'Lustig',
email: 'peter@lustig.de',
creation: [0, 0, 0],
emailChecked: true,
},
{
userId: 4,
firstName: 'New',
lastName: 'User',
email: 'new@user.ch',
creation: [1000, 1000, 1000],
emailChecked: false,
},
]
const confirmationItemsUser = [
{
email: 'bibi@bloxberg.de',
firstName: 'Bibi',
lastName: 'Bloxberg',
amount: 10,
memo: 'Test 1',
date: '11-09-2001',
moderator: 1,
},
{
email: 'bibi@bloxberg.de',
firstName: 'Bibi',
lastName: 'Bloxberg',
amount: 10,
memo: 'Test 2',
date: '21-09-2001',
moderator: 1,
},
{
email: 'bibi@bloxberg.de',
firstName: 'Bibi',
lastName: 'Bloxberg',
amount: 10,
memo: 'Test 3',
date: '30-09-2001',
moderator: 1,
},
]
const propsDataPageUserSearch = {
type: 'PageUserSearch',
itemsUser: defaultItemsUser,
fieldsTable: [
'email',
'firstName',
'lastName',
'creation',
'show_details',
'confirm_mail',
'transactions_list',
],
}
const propsDataUserListSearch = {
type: 'UserListSearch',
itemsUser: defaultItemsUser,
fieldsTable: ['bookmark', 'email', 'firstName', 'lastName', 'creation'],
creation: [1000, 1000, 1000],
}
const propsDataUserListMassCreation = {
type: 'UserListMassCreation',
itemsUser: defaultItemsUser,
fieldsTable: ['email', 'firstName', 'lastName', 'creation', 'bookmark'],
creation: [1000, 1000, 1000],
}
const propsDataPageCreationConfirm = {
type: 'PageCreationConfirm',
itemsUser: confirmationItemsUser,
fieldsTable: [
'bookmark',
'email',
'firstName',
'lastName',
'amount',
'memo',
'date',
'moderator',
'edit_creation',
'confirm',
],
}
const mocks = {
$t: jest.fn((t) => t),
$d: jest.fn((d) => String(d)),
$apollo: {
query: apolloQueryMock,
},
$store: {
commit: jest.fn(),
},
}
const Wrapper = (propsData) => {
return mount(UserTable, { localVue, propsData, mocks })
}
describe('mount', () => {
describe('type PageUserSearch', () => {
beforeEach(async () => {
wrapper = Wrapper(propsDataPageUserSearch)
})
it('has a DIV element with the class.component-user-table', () => {
expect(wrapper.find('.component-user-table').exists()).toBeTruthy()
})
it('has a DIV element with the id overlay that is not displayed', () => {
expect(wrapper.find('#overlay').exists()).toBeTruthy()
expect(wrapper.find('#overlay').attributes('style')).toBe('display: none;')
})
describe('table', () => {
it('has a table', () => {
expect(wrapper.find('table').exists()).toBeTruthy()
})
describe('header definition', () => {
it('has 4 column', () => {
expect(wrapper.findAll('th').length).toBe(7)
})
it('has Email as first column', () => {
expect(wrapper.find('th[aria-colindex="1"] div').text()).toBe('Email')
})
it('has First Name as second column', () => {
expect(wrapper.find('th[aria-colindex="2"] div').text()).toBe('First Name')
})
it('has Last Name as third column', () => {
expect(wrapper.find('th[aria-colindex="3"] div').text()).toBe('Last Name')
})
it('has Creation as fourth column', () => {
expect(wrapper.find('th[aria-colindex="4"] div').text()).toBe('Creation')
})
it('has Creation as fifth column', () => {
expect(wrapper.find('th[aria-colindex="5"] div').text()).toBe('Show Details')
})
it('has Creation as sixth column', () => {
expect(wrapper.find('th[aria-colindex="6"] div').text()).toBe('Confirm Mail')
})
it('has Creation as seventh column', () => {
expect(wrapper.find('th[aria-colindex="7"] div').text()).toBe('Transactions List')
})
})
describe('content', () => {
it('has 4 rows', () => {
expect(wrapper.findAll('tbody tr')).toHaveLength(4)
})
it('has 7 columns', () => {
expect(wrapper.findAll('tr:nth-child(1) > td')).toHaveLength(7)
})
it('find button on fifth column', () => {
expect(
wrapper.findAll('tr:nth-child(1) > td').at(5).find('button').isVisible(),
).toBeTruthy()
})
})
describe('row toggling', () => {
describe('user with email not activated', () => {
it('has no details button', () => {
expect(
wrapper.findAll('tbody > tr').at(3).findAll('td').at(4).find('button').exists(),
).toBeFalsy()
})
it('has a red confirmed button with envelope item', () => {
const row = wrapper.findAll('tbody > tr').at(3)
expect(row.findAll('td').at(5).find('button').exists()).toBeTruthy()
expect(row.findAll('td').at(5).find('button').classes('btn-danger')).toBeTruthy()
expect(row.findAll('td').at(5).find('svg').classes('bi-envelope')).toBeTruthy()
})
describe('click on envelope', () => {
beforeEach(async () => {
await wrapper
.findAll('tbody > tr')
.at(3)
.findAll('td')
.at(5)
.find('button')
.trigger('click')
})
it('opens the details', async () => {
expect(wrapper.findAll('tbody > tr')).toHaveLength(6)
expect(wrapper.findAll('tbody > tr').at(5).find('input').element.value).toBe(
'new@user.ch',
)
expect(wrapper.findAll('tbody > tr').at(5).text()).toContain(
'unregister_mail.text_false',
)
// HACK: for some reason we need to close the row details after this test
await wrapper
.findAll('tbody > tr')
.at(3)
.findAll('td')
.at(5)
.find('button')
.trigger('click')
})
describe('click on envelope again', () => {
beforeEach(async () => {
await wrapper
.findAll('tbody > tr')
.at(3)
.findAll('td')
.at(5)
.find('button')
.trigger('click')
})
it('closes the details', () => {
expect(wrapper.findAll('tbody > tr')).toHaveLength(4)
})
})
describe('click on close details', () => {
beforeEach(async () => {
await wrapper.findAll('tbody > tr').at(5).findAll('button').at(1).trigger('click')
})
it('closes the details', () => {
expect(wrapper.findAll('tbody > tr')).toHaveLength(4)
})
})
})
})
describe('different details', () => {
it.skip('shows the creation formular for second user', async () => {
await wrapper
.findAll('tbody > tr')
.at(1)
.findAll('td')
.at(4)
.find('button')
.trigger('click')
expect(wrapper.findAll('tbody > tr')).toHaveLength(6)
expect(
wrapper
.findAll('tbody > tr')
.at(3)
.find('div.component-creation-formular')
.exists(),
).toBeTruthy()
})
it.skip('shows the transactions for third user', async () => {
await wrapper
.findAll('tbody > tr')
.at(4)
.findAll('td')
.at(6)
.find('button')
.trigger('click')
expect(wrapper.findAll('tbody > tr')).toHaveLength(6)
})
})
})
})
})
describe('type UserListSearch', () => {
beforeEach(() => {
wrapper = Wrapper(propsDataUserListSearch)
})
it('has a DIV element with the class.component-user-table', () => {
expect(wrapper.find('.component-user-table').exists()).toBeTruthy()
})
})
describe('type UserListMassCreation', () => {
beforeEach(() => {
wrapper = Wrapper(propsDataUserListMassCreation)
})
it('has a DIV element with the class.component-user-table', () => {
expect(wrapper.find('.component-user-table').exists()).toBeTruthy()
})
})
describe('type PageCreationConfirm', () => {
beforeEach(() => {
wrapper = Wrapper(propsDataPageCreationConfirm)
})
it('has a DIV element with the class.component-user-table', () => {
expect(wrapper.find('.component-user-table').exists()).toBeTruthy()
})
})
})
})

View File

@ -1,320 +0,0 @@
<template>
<div class="component-user-table">
<div v-show="overlay" id="overlay" class="">
<b-jumbotron class="bg-light p-4">
<template #header>{{ overlayText.header }}</template>
<template #lead>
{{ overlayText.text1 }}
</template>
<hr class="my-4" />
<p>
{{ overlayText.text2 }}
</p>
<b-button size="md" variant="danger" class="m-3" @click="overlayCancel">
{{ overlayText.button_cancel }}
</b-button>
<b-button
size="md"
variant="success"
class="m-3 text-right"
@click="overlayOK(overlayBookmarkType, overlayItem)"
>
{{ overlayText.button_ok }}
</b-button>
</b-jumbotron>
</div>
<b-table-lite :items="itemsUser" :fields="fieldsTable" caption-top striped hover stacked="md">
<template #cell(creation)="data">
<div v-html="data.value"></div>
</template>
<template #cell(edit_creation)="row">
<b-button variant="info" size="md" @click="rowToogleDetails(row, 0)" class="mr-2">
<b-icon :icon="row.detailsShowing ? 'x' : 'pencil-square'" aria-label="Help"></b-icon>
</b-button>
</template>
<template #cell(show_details)="row">
<b-button
variant="info"
size="md"
v-if="row.item.emailChecked"
@click="rowToogleDetails(row, 0)"
class="mr-2"
>
<b-icon :icon="row.detailsShowing ? 'eye-slash-fill' : 'eye-fill'"></b-icon>
</b-button>
</template>
<template #cell(confirm_mail)="row">
<b-button
:variant="row.item.emailChecked ? 'success' : 'danger'"
size="md"
@click="rowToogleDetails(row, 1)"
class="mr-2"
>
<b-icon
:icon="row.item.emailChecked ? 'envelope-open' : 'envelope'"
aria-label="Help"
></b-icon>
</b-button>
</template>
<template #cell(has_elopage)="row">
<b-icon
:variant="row.item.hasElopage ? 'success' : 'danger'"
:icon="row.item.hasElopage ? 'check-circle' : 'x-circle'"
></b-icon>
</template>
<template #cell(transactions_list)="row">
<b-button variant="warning" size="md" @click="rowToogleDetails(row, 2)" class="mr-2">
<b-icon icon="list"></b-icon>
</b-button>
</template>
<template #row-details="row">
<row-details
v-if="type !== 'UserListSearch' && type !== 'UserListMassCreation'"
:row="row"
:type="type"
:slotName="slotName"
:index="slotIndex"
@row-toogle-details="rowToogleDetails"
>
<template #show-creation>
<div>
<creation-formular
v-if="type === 'PageUserSearch'"
type="singleCreation"
:pagetype="type"
:creation="row.item.creation"
:item="row.item"
:creationUserData="creationUserData"
@update-creation-data="updateCreationData"
@update-user-data="updateUserData"
/>
<edit-creation-formular
v-else
type="singleCreation"
:pagetype="type"
:creation="row.item.creation"
:item="row.item"
:row="row"
:creationUserData="creationUserData"
@update-creation-data="updateCreationData"
@update-user-data="updateUserData"
/>
</div>
</template>
<template #show-register-mail>
<confirm-register-mail-formular
:checked="row.item.emailChecked"
:email="row.item.email"
:dateLastSend="
row.item.emailConfirmationSend
? $d(new Date(row.item.emailConfirmationSend), 'long')
: ''
"
/>
</template>
<template #show-transaction-list>
<creation-transaction-list-formular :userId="row.item.userId" />
</template>
</row-details>
</template>
<template #cell(bookmark)="row">
<div v-if="type === 'UserListSearch'">
<b-button
v-if="row.item.emailChecked"
variant="warning"
size="md"
@click="bookmarkPush(row.item)"
class="mr-2"
>
<b-icon icon="plus" variant="success"></b-icon>
</b-button>
<div v-else>{{ $t('e_mail') }}!</div>
</div>
<b-button
variant="danger"
v-show="type === 'UserListMassCreation' || type === 'PageCreationConfirm'"
size="md"
@click="bookmarkRemove(row.item)"
class="mr-2"
>
<b-icon icon="x" variant="light"></b-icon>
</b-button>
</template>
<template #cell(confirm)="row">
<b-button
variant="success"
v-show="type === 'PageCreationConfirm'"
size="md"
@click="overlayShow('confirm', row.item)"
class="mr-2"
>
<b-icon icon="check" scale="2" variant=""></b-icon>
</b-button>
</template>
</b-table-lite>
</div>
</template>
<script>
import CreationFormular from '../components/CreationFormular.vue'
import EditCreationFormular from '../components/EditCreationFormular.vue'
import ConfirmRegisterMailFormular from '../components/ConfirmRegisterMailFormular.vue'
import CreationTransactionListFormular from '../components/CreationTransactionListFormular.vue'
import RowDetails from '../components/RowDetails.vue'
const slotNames = ['show-creation', 'show-register-mail', 'show-transaction-list']
export default {
name: 'UserTable',
props: {
type: {
type: String,
required: true,
},
itemsUser: {
type: Array,
required: true,
},
fieldsTable: {
type: Array,
required: true,
},
},
components: {
CreationFormular,
EditCreationFormular,
ConfirmRegisterMailFormular,
CreationTransactionListFormular,
RowDetails,
},
data() {
return {
showCreationFormular: null,
showConfirmRegisterMailFormular: null,
showCreationTransactionListFormular: null,
creationUserData: {},
overlay: false,
overlayBookmarkType: '',
overlayItem: [],
overlayText: [
{
header: '-',
text1: '--',
text2: '---',
button_ok: 'OK',
button_cancel: 'Cancel',
},
],
slotIndex: 0,
openRow: null,
}
},
methods: {
rowToogleDetails(row, index) {
if (this.openRow) {
if (this.openRow.index === row.index) {
if (index === this.slotIndex) {
row.toggleDetails()
this.openRow = null
} else {
this.slotIndex = index
}
} else {
this.openRow.toggleDetails()
row.toggleDetails()
this.slotIndex = index
this.openRow = row
if (this.type === 'PageCreationConfirm') {
this.creationUserData = row.item
}
}
} else {
row.toggleDetails()
this.slotIndex = index
this.openRow = row
if (this.type === 'PageCreationConfirm') {
this.creationUserData = row.item
}
}
},
overlayShow(bookmarkType, item) {
this.overlay = true
this.overlayBookmarkType = bookmarkType
this.overlayItem = item
if (bookmarkType === 'confirm') {
this.overlayText.header = this.$t('overlay.confirm.title')
this.overlayText.text1 = this.$t('overlay.confirm.text')
this.overlayText.text2 = this.$t('overlay.confirm.question')
this.overlayText.button_ok = this.$t('overlay.confirm.yes')
this.overlayText.button_cancel = this.$t('overlay.confirm.no')
}
},
overlayOK(bookmarkType, item) {
if (bookmarkType === 'confirm') {
this.$emit('confirm-creation', item)
}
this.overlay = false
},
overlayCancel() {
this.overlay = false
},
bookmarkPush(item) {
this.$emit('push-item', item)
},
bookmarkRemove(item) {
if (this.type === 'UserListMassCreation') {
this.$emit('remove-item', item)
}
if (this.type === 'PageCreationConfirm') {
this.$emit('remove-creation', item)
}
},
updateCreationData(data) {
this.creationUserData.amount = data.amount
this.creationUserData.date = data.date
this.creationUserData.memo = data.memo
this.creationUserData.moderator = data.moderator
data.row.toggleDetails()
},
updateUserData(rowItem, newCreation) {
rowItem.creation = newCreation
},
},
computed: {
slotName() {
return slotNames[this.slotIndex]
},
},
}
</script>
<style>
#overlay {
position: fixed;
display: flex;
align-items: center;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding-left: 5%;
background-color: rgba(12, 11, 11, 0.781);
z-index: 1000000;
cursor: pointer;
}
</style>

View File

@ -2,6 +2,7 @@
"all_emails": "Alle Nutzer", "all_emails": "Alle Nutzer",
"bookmark": "bookmark", "bookmark": "bookmark",
"confirmed": "bestätigt", "confirmed": "bestätigt",
"creation": "Schöpfung",
"creation_form": { "creation_form": {
"creation_for": "Aktives Grundeinkommen für", "creation_for": "Aktives Grundeinkommen für",
"enter_text": "Text eintragen", "enter_text": "Text eintragen",
@ -19,12 +20,15 @@
"update_creation": "Schöpfung aktualisieren" "update_creation": "Schöpfung aktualisieren"
}, },
"date": "Datum", "date": "Datum",
"delete": "Löschen",
"details": "Details", "details": "Details",
"edit": "Bearbeiten",
"e_mail": "E-Mail", "e_mail": "E-Mail",
"firstname": "Vorname", "firstname": "Vorname",
"gradido_admin_footer": "Gradido Akademie Adminkonsole", "gradido_admin_footer": "Gradido Akademie Adminkonsole",
"hide_details": "Details verbergen von", "hide_details": "Details verbergen von",
"lastname": "Nachname", "lastname": "Nachname",
"moderator": "Moderator",
"multiple_creation_text": "Bitte wähle ein oder mehrere Mitglieder aus für die du Schöpfen möchtest.", "multiple_creation_text": "Bitte wähle ein oder mehrere Mitglieder aus für die du Schöpfen möchtest.",
"navbar": { "navbar": {
"logout": "Abmelden", "logout": "Abmelden",
@ -55,6 +59,8 @@
}, },
"remove": "Entfernen", "remove": "Entfernen",
"remove_all": "alle Nutzer entfernen", "remove_all": "alle Nutzer entfernen",
"save": "Speichern",
"text": "Text",
"transaction": "Transaktion", "transaction": "Transaktion",
"transactionlist": { "transactionlist": {
"amount": "Betrag", "amount": "Betrag",

View File

@ -2,6 +2,7 @@
"all_emails": "All users", "all_emails": "All users",
"bookmark": "Remember", "bookmark": "Remember",
"confirmed": "confirmed", "confirmed": "confirmed",
"creation": "Creation",
"creation_form": { "creation_form": {
"creation_for": "Active Basic Income for", "creation_for": "Active Basic Income for",
"enter_text": "Enter text", "enter_text": "Enter text",
@ -19,12 +20,15 @@
"update_creation": "Creation update" "update_creation": "Creation update"
}, },
"date": "Date", "date": "Date",
"delete": "Delete",
"details": "Details", "details": "Details",
"edit": "Edit",
"e_mail": "E-mail", "e_mail": "E-mail",
"firstname": "Firstname", "firstname": "Firstname",
"gradido_admin_footer": "Gradido Academy Admin Console", "gradido_admin_footer": "Gradido Academy Admin Console",
"hide_details": "Hide details from", "hide_details": "Hide details from",
"lastname": "Lastname", "lastname": "Lastname",
"moderator": "Moderator",
"multiple_creation_text": "Please select one or more members for which you would like to perform creations.", "multiple_creation_text": "Please select one or more members for which you would like to perform creations.",
"navbar": { "navbar": {
"logout": "Logout", "logout": "Logout",
@ -55,6 +59,8 @@
}, },
"remove": "Remove", "remove": "Remove",
"remove_all": "Remove all users", "remove_all": "Remove all users",
"save": "Speichern",
"text": "Text",
"transaction": "Transaction", "transaction": "Transaction",
"transactionlist": { "transactionlist": {
"amount": "Amount", "amount": "Amount",

View File

@ -0,0 +1,34 @@
export const toggleRowDetails = {
data() {
return {
slotIndex: 0,
openRow: null,
creationUserData: {},
}
},
methods: {
rowToogleDetails(row, index) {
if (this.openRow) {
if (this.openRow.index === row.index) {
if (index === this.slotIndex) {
row.toggleDetails()
this.openRow = null
} else {
this.slotIndex = index
}
} else {
this.openRow.toggleDetails()
row.toggleDetails()
this.slotIndex = index
this.openRow = row
this.creationUserData = row.item
}
} else {
row.toggleDetails()
this.slotIndex = index
this.openRow = row
this.creationUserData = row.item
}
},
},
}

View File

@ -0,0 +1,141 @@
import { toggleRowDetails } from './toggleRowDetails'
import { mount } from '@vue/test-utils'
const localVue = global.localVue
const Component = {
render() {},
mixins: [toggleRowDetails],
}
const toggleDetailsMock = jest.fn()
const secondToggleDetailsMock = jest.fn()
const row = {
toggleDetails: toggleDetailsMock,
index: 0,
item: {
data: 'item-data',
},
}
let wrapper
describe('toggleRowDetails', () => {
beforeEach(() => {
jest.clearAllMocks()
wrapper = mount(Component, { localVue })
})
it('sets default data', () => {
expect(wrapper.vm.slotIndex).toBe(0)
expect(wrapper.vm.openRow).toBe(null)
expect(wrapper.vm.creationUserData).toEqual({})
})
describe('no open row', () => {
beforeEach(() => {
wrapper.vm.rowToogleDetails(row, 2)
})
it('calls toggleDetails', () => {
expect(toggleDetailsMock).toBeCalled()
})
it('updates slot index', () => {
expect(wrapper.vm.slotIndex).toBe(2)
})
it('updates open row', () => {
expect(wrapper.vm.openRow).toEqual(
expect.objectContaining({
index: 0,
item: {
data: 'item-data',
},
}),
)
})
it('updates creation user data', () => {
expect(wrapper.vm.creationUserData).toEqual({ data: 'item-data' })
})
})
describe('with open row', () => {
beforeEach(() => {
wrapper.setData({ openRow: row })
})
describe('row index is open row index', () => {
describe('index is slot index', () => {
beforeEach(() => {
wrapper.vm.rowToogleDetails(row, 0)
})
it('calls toggleDetails', () => {
expect(toggleDetailsMock).toBeCalled()
})
it('sets open row to null', () => {
expect(wrapper.vm.openRow).toBe(null)
})
})
describe('index is not slot index', () => {
beforeEach(() => {
wrapper.vm.rowToogleDetails(row, 2)
})
it('does not call toggleDetails', () => {
expect(toggleDetailsMock).not.toBeCalled()
})
it('updates slot index', () => {
expect(wrapper.vm.slotIndex).toBe(2)
})
})
})
describe('row index is not open row index', () => {
beforeEach(() => {
wrapper.vm.rowToogleDetails(
{
toggleDetails: secondToggleDetailsMock,
index: 2,
item: {
data: 'new-item-data',
},
},
2,
)
})
it('closes the open row', () => {
expect(toggleDetailsMock).toBeCalled()
})
it('opens the new row', () => {
expect(secondToggleDetailsMock).toBeCalled()
})
it('updates slot index', () => {
expect(wrapper.vm.slotIndex).toBe(2)
})
it('updates open row', () => {
expect(wrapper.vm.openRow).toEqual({
toggleDetails: secondToggleDetailsMock,
index: 2,
item: {
data: 'new-item-data',
},
})
})
it('updates creation user data', () => {
expect(wrapper.vm.creationUserData).toEqual({ data: 'new-item-data' })
})
})
})
})

View File

@ -17,11 +17,10 @@
</b-input-group-text> </b-input-group-text>
</b-input-group-append> </b-input-group-append>
</b-input-group> </b-input-group>
<user-table <select-users-table
v-if="itemsList.length > 0" v-if="itemsList.length > 0"
type="UserListSearch" :items="itemsList"
:itemsUser="itemsList" :fields="Searchfields"
:fieldsTable="Searchfields"
@push-item="pushItem" @push-item="pushItem"
/> />
<b-pagination <b-pagination
@ -41,11 +40,10 @@
{{ $t('remove_all') }} {{ $t('remove_all') }}
</b-button> </b-button>
</div> </div>
<user-table <selected-users-table
class="shadow p-3 mb-5 bg-white rounded" class="shadow p-3 mb-5 bg-white rounded"
type="UserListMassCreation" :items="itemsMassCreation"
:itemsUser="itemsMassCreation" :fields="fields"
:fieldsTable="fields"
@remove-item="removeItem" @remove-item="removeItem"
/> />
</div> </div>
@ -65,7 +63,8 @@
</template> </template>
<script> <script>
import CreationFormular from '../components/CreationFormular.vue' import CreationFormular from '../components/CreationFormular.vue'
import UserTable from '../components/UserTable.vue' import SelectUsersTable from '../components/Tables/SelectUsersTable.vue'
import SelectedUsersTable from '../components/Tables/SelectedUsersTable.vue'
import { searchUsers } from '../graphql/searchUsers' import { searchUsers } from '../graphql/searchUsers'
import { creationMonths } from '../mixins/creationMonths' import { creationMonths } from '../mixins/creationMonths'
@ -74,7 +73,8 @@ export default {
mixins: [creationMonths], mixins: [creationMonths],
components: { components: {
CreationFormular, CreationFormular,
UserTable, SelectUsersTable,
SelectedUsersTable,
}, },
data() { data() {
return { return {

View File

@ -165,17 +165,17 @@ describe('CreationConfirm', () => {
expect(wrapper.findAll('tbody > tr')).toHaveLength(1) expect(wrapper.findAll('tbody > tr')).toHaveLength(1)
}) })
}) })
})
})
describe('confirm creation with error', () => { describe('confirm creation with error', () => {
beforeEach(async () => { beforeEach(async () => {
apolloMutateMock.mockRejectedValue({ message: 'Ouchhh!' }) apolloMutateMock.mockRejectedValue({ message: 'Ouchhh!' })
await wrapper.findComponent({ name: 'UserTable' }).vm.$emit('confirm-creation', { id: 2 }) await wrapper.find('#overlay').findAll('button').at(1).trigger('click')
}) })
it('toasts an error message', () => { it('toasts an error message', () => {
expect(toastedErrorMock).toBeCalledWith('Ouchhh!') expect(toastedErrorMock).toBeCalledWith('Ouchhh!')
})
})
}) })
}) })

View File

@ -1,17 +1,30 @@
<template> <template>
<div class="creation-confirm"> <div class="creation-confirm">
<user-table <div v-show="overlay" id="overlay" class="">
<b-jumbotron class="bg-light p-4">
<template #header>{{ $t('overlay.confirm.title') }}</template>
<template #lead>{{ $t('overlay.confirm.text') }}</template>
<hr class="my-4" />
<p>{{ $t('overlay.confirm.question') }}</p>
<b-button size="md" variant="danger" class="m-3" @click="overlay = false">
{{ $t('overlay.confirm.no') }}
</b-button>
<b-button size="md" variant="success" class="m-3 text-right" @click="confirmCreation">
{{ $t('overlay.confirm.yes') }}
</b-button>
</b-jumbotron>
</div>
<open-creations-table
class="mt-4" class="mt-4"
type="PageCreationConfirm" :items="pendingCreations"
:itemsUser="pendingCreations" :fields="fields"
:fieldsTable="fields"
@remove-creation="removeCreation" @remove-creation="removeCreation"
@confirm-creation="confirmCreation" @show-overlay="showOverlay"
/> />
</div> </div>
</template> </template>
<script> <script>
import UserTable from '../components/UserTable.vue' import OpenCreationsTable from '../components/Tables/OpenCreationsTable.vue'
import { getPendingCreations } from '../graphql/getPendingCreations' import { getPendingCreations } from '../graphql/getPendingCreations'
import { deletePendingCreation } from '../graphql/deletePendingCreation' import { deletePendingCreation } from '../graphql/deletePendingCreation'
import { confirmPendingCreation } from '../graphql/confirmPendingCreation' import { confirmPendingCreation } from '../graphql/confirmPendingCreation'
@ -19,11 +32,13 @@ import { confirmPendingCreation } from '../graphql/confirmPendingCreation'
export default { export default {
name: 'CreationConfirm', name: 'CreationConfirm',
components: { components: {
UserTable, OpenCreationsTable,
}, },
data() { data() {
return { return {
pendingCreations: [], pendingCreations: [],
overlay: false,
item: [],
} }
}, },
methods: { methods: {
@ -43,19 +58,21 @@ export default {
this.$toasted.error(error.message) this.$toasted.error(error.message)
}) })
}, },
confirmCreation(item) { confirmCreation() {
this.$apollo this.$apollo
.mutate({ .mutate({
mutation: confirmPendingCreation, mutation: confirmPendingCreation,
variables: { variables: {
id: item.id, id: this.item.id,
}, },
}) })
.then((result) => { .then((result) => {
this.updatePendingCreations(item.id) this.overlay = false
this.updatePendingCreations(this.item.id)
this.$toasted.success(this.$t('creation_form.toasted_created')) this.$toasted.success(this.$t('creation_form.toasted_created'))
}) })
.catch((error) => { .catch((error) => {
this.overlay = false
this.$toasted.error(error.message) this.$toasted.error(error.message)
}) })
}, },
@ -78,22 +95,26 @@ export default {
this.pendingCreations = this.pendingCreations.filter((obj) => obj.id !== id) this.pendingCreations = this.pendingCreations.filter((obj) => obj.id !== id)
this.$store.commit('openCreationsMinus', 1) this.$store.commit('openCreationsMinus', 1)
}, },
showOverlay(item) {
this.overlay = true
this.item = item
},
}, },
computed: { computed: {
fields() { fields() {
return [ return [
{ key: 'bookmark', label: 'löschen' }, { key: 'bookmark', label: this.$t('delete') },
{ key: 'email', label: 'Email' }, { key: 'email', label: this.$t('e_mail') },
{ key: 'firstName', label: 'Vorname' }, { key: 'firstName', label: this.$t('firstname') },
{ key: 'lastName', label: 'Nachname' }, { key: 'lastName', label: this.$t('lastname') },
{ {
key: 'amount', key: 'amount',
label: 'Schöpfung', label: this.$t('creation'),
formatter: (value) => { formatter: (value) => {
return value + ' GDD' return value + ' GDD'
}, },
}, },
{ key: 'memo', label: 'Text' }, { key: 'memo', label: this.$t('text') },
{ {
key: 'date', key: 'date',
label: this.$t('date'), label: this.$t('date'),
@ -101,9 +122,9 @@ export default {
return this.$d(new Date(value), 'short') return this.$d(new Date(value), 'short')
}, },
}, },
{ key: 'moderator', label: 'Moderator' }, { key: 'moderator', label: this.$t('moderator') },
{ key: 'edit_creation', label: 'ändern' }, { key: 'edit_creation', label: this.$t('edit') },
{ key: 'confirm', label: 'speichern' }, { key: 'confirm', label: this.$t('save') },
] ]
}, },
}, },
@ -112,3 +133,20 @@ export default {
}, },
} }
</script> </script>
<style>
#overlay {
position: fixed;
display: flex;
align-items: center;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
padding-left: 5%;
background-color: rgba(12, 11, 11, 0.781);
z-index: 1000000;
cursor: pointer;
}
</style>

View File

@ -22,7 +22,7 @@
</b-input-group-append> </b-input-group-append>
</b-input-group> </b-input-group>
</div> </div>
<user-table type="PageUserSearch" :itemsUser="searchResult" :fieldsTable="fields" /> <search-user-table type="PageUserSearch" :items="searchResult" :fields="fields" />
<b-pagination <b-pagination
pills pills
size="lg" size="lg"
@ -35,7 +35,7 @@
</div> </div>
</template> </template>
<script> <script>
import UserTable from '../components/UserTable.vue' import SearchUserTable from '../components/Tables/SearchUserTable.vue'
import { searchUsers } from '../graphql/searchUsers' import { searchUsers } from '../graphql/searchUsers'
import { creationMonths } from '../mixins/creationMonths' import { creationMonths } from '../mixins/creationMonths'
@ -43,7 +43,7 @@ export default {
name: 'UserSearch', name: 'UserSearch',
mixins: [creationMonths], mixins: [creationMonths],
components: { components: {
UserTable, SearchUserTable,
}, },
data() { data() {
return { return {