mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge pull request #524 from gradido/remove-base-input-example-login-vue
Remove base input example login vue
This commit is contained in:
commit
bdee28fde6
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -212,7 +212,7 @@ jobs:
|
||||
report_name: Coverage Frontend
|
||||
type: lcov
|
||||
result_path: ./coverage/lcov.info
|
||||
min_coverage: 21
|
||||
min_coverage: 28
|
||||
token: ${{ github.token }}
|
||||
|
||||
##############################################################################
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
//
|
||||
// Circle badge
|
||||
//
|
||||
|
||||
|
||||
// General styles
|
||||
|
||||
.badge-circle {
|
||||
text-align: center;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
font-size: .875rem;
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
//
|
||||
// Dot badge
|
||||
//
|
||||
|
||||
|
||||
// General styles
|
||||
|
||||
.badge-dot {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
background: transparent;
|
||||
font-weight: $font-weight-normal;
|
||||
font-size: $font-size-sm;
|
||||
text-transform: none;
|
||||
|
||||
strong {
|
||||
color: $gray-800;
|
||||
}
|
||||
|
||||
i {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: .375rem;
|
||||
height: .375rem;
|
||||
border-radius: 50%;
|
||||
margin-right: .375rem;
|
||||
}
|
||||
|
||||
&.badge-md {
|
||||
i {
|
||||
width: .5rem;
|
||||
height: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.badge-lg {
|
||||
i {
|
||||
width: .625rem;
|
||||
height: .625rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
//
|
||||
// Badge
|
||||
//
|
||||
|
||||
|
||||
// General styles
|
||||
|
||||
.badge {
|
||||
text-transform: $badge-text-transfom;
|
||||
|
||||
a {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Size variations
|
||||
|
||||
.badge-md {
|
||||
padding: .65em 1em;
|
||||
}
|
||||
|
||||
.badge-lg {
|
||||
padding: .85em 1.375em;
|
||||
}
|
||||
|
||||
|
||||
// Multiple inline badges
|
||||
|
||||
.badge-inline {
|
||||
margin-right: .625rem;
|
||||
|
||||
+ span {
|
||||
top: 2px;
|
||||
position: relative;
|
||||
|
||||
> a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Badge spacing inside a btn with some text
|
||||
|
||||
.btn {
|
||||
.badge {
|
||||
&:not(:first-child) {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
&:not(:last-child) {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,6 @@
|
||||
|
||||
@import "custom/alert";
|
||||
@import "custom/avatar";
|
||||
@import "custom/badge";
|
||||
@import "custom/buttons";
|
||||
@import "custom/card";
|
||||
@import "custom/chart";
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
<template>
|
||||
<b-badge :variant="type" :pill="rounded" :size="size" :class="{ 'badge-circle': circle }">
|
||||
<slot>
|
||||
<i v-if="icon" :class="icon"></i>
|
||||
</slot>
|
||||
</b-badge>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'badge',
|
||||
props: {
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'span',
|
||||
description: 'Html tag to use for the badge.',
|
||||
},
|
||||
rounded: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
description: 'Whether badge is of pill type',
|
||||
},
|
||||
circle: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
description: 'Whether badge is circle',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
description: 'Icon name. Will be overwritten by slot if slot is used',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
description: 'Badge type (primary|info|danger|default|warning|success)',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
description: 'Badge size (md, lg)',
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,75 +0,0 @@
|
||||
<template>
|
||||
<fade-transition>
|
||||
<b-alert
|
||||
v-model="visible"
|
||||
:variant="type"
|
||||
:class="[{ 'alert-dismissible': dismissible }]"
|
||||
role="alert"
|
||||
>
|
||||
<slot v-if="!dismissible"></slot>
|
||||
<template v-else>
|
||||
<template v-if="icon || $slots.icon">
|
||||
<slot name="icon">
|
||||
<span class="alert-icon" data-notify="icon">
|
||||
<i :class="icon"></i>
|
||||
</span>
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
<span class="alert-text"><slot></slot></span>
|
||||
|
||||
<slot name="dismiss-icon">
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="alert"
|
||||
aria-label="Close"
|
||||
@click="dismissAlert"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</slot>
|
||||
</template>
|
||||
</b-alert>
|
||||
</fade-transition>
|
||||
</template>
|
||||
<script>
|
||||
import { FadeTransition } from 'vue2-transitions'
|
||||
|
||||
export default {
|
||||
name: 'base-alert',
|
||||
components: {
|
||||
FadeTransition,
|
||||
},
|
||||
created() {
|
||||
// console.log('base-alert gesetzt in =>', this.$route.path)
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
description: 'Alert type',
|
||||
},
|
||||
dismissible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
description: 'Whether alert is dismissible (closeable)',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
description: 'Alert icon to display',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: true,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dismissAlert() {
|
||||
this.visible = false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,75 +0,0 @@
|
||||
<template>
|
||||
<b-button
|
||||
:type="nativeType"
|
||||
:disabled="disabled || loading"
|
||||
@click="handleClick"
|
||||
class="base-button"
|
||||
:variant="!outline ? type : `outline-${type}`"
|
||||
:size="size"
|
||||
:block="block"
|
||||
:class="[
|
||||
{ 'rounded-circle': round },
|
||||
{ 'btn-wd': wide },
|
||||
{ 'btn-icon btn-fab': icon },
|
||||
{ 'btn-link': link },
|
||||
{ disabled: disabled },
|
||||
]"
|
||||
>
|
||||
<slot name="loading">
|
||||
<i v-if="loading" class="fas fa-spinner fa-spin"></i>
|
||||
</slot>
|
||||
<slot></slot>
|
||||
</b-button>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'base-button',
|
||||
props: {
|
||||
round: Boolean,
|
||||
icon: Boolean,
|
||||
block: Boolean,
|
||||
loading: Boolean,
|
||||
wide: Boolean,
|
||||
disabled: Boolean,
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
description: 'Button type (primary|secondary|danger etc)',
|
||||
},
|
||||
nativeType: {
|
||||
type: String,
|
||||
default: 'button',
|
||||
description: 'Button native type (e.g button, input etc)',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: '',
|
||||
description: 'Button size (sm|lg)',
|
||||
},
|
||||
outline: {
|
||||
type: Boolean,
|
||||
description: 'Whether button is outlined (only border has color)',
|
||||
},
|
||||
link: {
|
||||
type: Boolean,
|
||||
description: 'Whether button is a link (no borders or background)',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClick(evt) {
|
||||
this.$emit('click', evt)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.base-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
i {
|
||||
padding: 0 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,97 +0,0 @@
|
||||
<template>
|
||||
<component
|
||||
:is="tag"
|
||||
:class="[{ show: isOpen }, `drop${direction}`]"
|
||||
@click="toggleDropDown"
|
||||
v-click-outside="closeDropDown"
|
||||
>
|
||||
<slot name="title-container" :is-open="isOpen">
|
||||
<component
|
||||
:is="titleTag"
|
||||
class="btn-rotate"
|
||||
:class="[{ 'dropdown-toggle': hasToggle }, titleClasses]"
|
||||
:aria-expanded="isOpen"
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<slot name="title" :is-open="isOpen">
|
||||
<i :class="icon"></i>
|
||||
{{ title }}
|
||||
</slot>
|
||||
</component>
|
||||
</slot>
|
||||
<ul
|
||||
class="dropdown-menu"
|
||||
:class="[{ show: isOpen }, { 'dropdown-menu-right': menuOnRight }, menuClasses]"
|
||||
>
|
||||
<slot></slot>
|
||||
</ul>
|
||||
</component>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'base-dropdown',
|
||||
props: {
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
description: 'Dropdown html tag (e.g div, ul etc)',
|
||||
},
|
||||
titleTag: {
|
||||
type: String,
|
||||
default: 'button',
|
||||
description: 'Dropdown title (toggle) html tag',
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
description: 'Dropdown title',
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'down', // up | down
|
||||
description: 'Dropdown menu direction (up|down)',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
description: 'Dropdown icon',
|
||||
},
|
||||
titleClasses: {
|
||||
type: [String, Object, Array],
|
||||
description: 'Title css classes',
|
||||
},
|
||||
menuClasses: {
|
||||
type: [String, Object],
|
||||
description: 'Menu css classes',
|
||||
},
|
||||
menuOnRight: {
|
||||
type: Boolean,
|
||||
description: 'Whether menu should appear on the right',
|
||||
},
|
||||
hasToggle: {
|
||||
type: Boolean,
|
||||
description: 'Whether dropdown has arrow icon shown',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isOpen: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleDropDown() {
|
||||
this.isOpen = !this.isOpen
|
||||
this.$emit('change', this.isOpen)
|
||||
},
|
||||
closeDropDown() {
|
||||
this.isOpen = false
|
||||
this.$emit('change', false)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.dropdown {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
@ -1,22 +0,0 @@
|
||||
<template>
|
||||
<div class="header" :class="{ [`bg-${type}`]: type }">
|
||||
<b-container fluid>
|
||||
<div class="header-body">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</b-container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'base-header',
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'success',
|
||||
description: 'Header background type',
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,67 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-pagination
|
||||
first-number
|
||||
last-number
|
||||
:per-page="perPage"
|
||||
:size="size"
|
||||
:value="value"
|
||||
@change="(val) => $emit('change', val)"
|
||||
:align="align"
|
||||
:total-rows="total"
|
||||
>
|
||||
<template v-slot:prev-text>
|
||||
<a class="page-link" aria-label="Previous">
|
||||
<span aria-hidden="true">
|
||||
<i class="fa fa-angle-left" aria-hidden="true"></i>
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
<template v-slot:next-text>
|
||||
<a class="page-link" aria-label="Next">
|
||||
<span aria-hidden="true">
|
||||
<i class="fa fa-angle-right" aria-hidden="true"></i>
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
</b-pagination>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'base-pagination',
|
||||
props: {
|
||||
pageCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
description: 'Pagination page count. This should be specified in combination with perPage',
|
||||
},
|
||||
perPage: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
description: 'Pagination per page. Should be specified with total or pageCount',
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
description:
|
||||
'Can be specified instead of pageCount. The page count in this case will be total/perPage',
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
description: 'Pagination value',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: '',
|
||||
description: 'Pagination size',
|
||||
},
|
||||
align: {
|
||||
type: String,
|
||||
default: '',
|
||||
description: 'Pagination alignment (e.g center|start|end)',
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,79 +0,0 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div :class="`progress-${type}`" v-if="showLabel">
|
||||
<div class="progress-label">
|
||||
<slot name="label">
|
||||
<span>{{ label }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="progress-percentage">
|
||||
<slot>
|
||||
<span>{{ value }}%</span>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
<b-progress :size="size" :class="[progressClasses]" :style="`height: ${height}px`">
|
||||
<b-progress-bar :class="computedClasses" :value="value"></b-progress-bar>
|
||||
</b-progress>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'base-progress',
|
||||
props: {
|
||||
striped: {
|
||||
type: Boolean,
|
||||
description: 'Whether progress is striped',
|
||||
},
|
||||
animated: {
|
||||
type: Boolean,
|
||||
description: 'Whether progress is animated (works only with `striped` prop together)',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
description: 'Progress label (shown on the left above progress)',
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 3,
|
||||
description: 'Progress line height',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
description: 'Progress type (e.g danger, primary etc)',
|
||||
},
|
||||
showLabel: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
progressClasses: {
|
||||
type: [Array, String],
|
||||
default: '',
|
||||
description: 'Progress css classes',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
validator: (value) => {
|
||||
return value >= 0 && value <= 100
|
||||
},
|
||||
description: 'Progress value',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
computedClasses() {
|
||||
return [
|
||||
{ 'progress-bar-striped': this.striped },
|
||||
{ 'progress-bar-animated': this.animated },
|
||||
{ [`bg-${this.type}`]: this.type },
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,92 +0,0 @@
|
||||
<template>
|
||||
<div class="slider" :disabled="disabled"></div>
|
||||
</template>
|
||||
<script>
|
||||
import noUiSlider from 'nouislider'
|
||||
|
||||
export default {
|
||||
name: 'base-slider',
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Array, Number],
|
||||
description: 'slider value',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
description: 'whether the slider is disabled',
|
||||
},
|
||||
start: {
|
||||
type: [Number, Array],
|
||||
default: 0,
|
||||
description:
|
||||
'[noUi Slider start](https://refreshless.com/nouislider/slider-options/#section-start)',
|
||||
},
|
||||
connect: {
|
||||
type: [Boolean, Array],
|
||||
default: () => [true, false],
|
||||
description:
|
||||
'[noUi Slider connect](https://refreshless.com/nouislider/slider-options/#section-connect)',
|
||||
},
|
||||
range: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
min: 0,
|
||||
max: 100,
|
||||
}
|
||||
},
|
||||
description:
|
||||
'[noUi Slider range](https://refreshless.com/nouislider/slider-values/#section-range)',
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
},
|
||||
description: '[noUi Slider options](https://refreshless.com/nouislider/slider-options/)',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
slider: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
createSlider() {
|
||||
noUiSlider.create(this.$el, {
|
||||
start: this.value || this.start,
|
||||
connect: Array.isArray(this.value) ? true : this.connect,
|
||||
range: this.range,
|
||||
...this.options,
|
||||
})
|
||||
const slider = this.$el.noUiSlider
|
||||
slider.on('slide', () => {
|
||||
const value = slider.get()
|
||||
if (value !== this.value) {
|
||||
this.$emit('input', value)
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.createSlider()
|
||||
},
|
||||
watch: {
|
||||
value(newValue, oldValue) {
|
||||
const slider = this.$el.noUiSlider
|
||||
const sliderValue = slider.get()
|
||||
if (newValue !== oldValue && sliderValue !== newValue) {
|
||||
if (Array.isArray(sliderValue) && Array.isArray(newValue)) {
|
||||
if (oldValue.length === newValue.length && oldValue.every((v, i) => v === newValue[i])) {
|
||||
slider.set(newValue)
|
||||
}
|
||||
} else {
|
||||
slider.set(newValue)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,66 +0,0 @@
|
||||
<template>
|
||||
<table class="table tablesorter" :class="tableClass">
|
||||
<thead :class="theadClasses">
|
||||
<tr>
|
||||
<slot name="columns" :columns="columns">
|
||||
<th v-for="column in columns" :key="column">{{ column }}</th>
|
||||
</slot>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody :class="tbodyClasses">
|
||||
<tr v-for="(item, index) in data" :key="index">
|
||||
<slot :row="item" :index="index">
|
||||
<td v-for="(column, index) in columns" :key="index">
|
||||
{{ itemValue(item, column) }}
|
||||
</td>
|
||||
</slot>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'base-table',
|
||||
props: {
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
description: 'Table columns',
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
description: 'Table data',
|
||||
},
|
||||
type: {
|
||||
type: String, // striped | hover
|
||||
default: '',
|
||||
description: 'Whether table is striped or hover type',
|
||||
},
|
||||
theadClasses: {
|
||||
type: String,
|
||||
default: '',
|
||||
description: '<thead> css classes',
|
||||
},
|
||||
tbodyClasses: {
|
||||
type: String,
|
||||
default: '',
|
||||
description: '<tbody> css classes',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
tableClass() {
|
||||
return this.type && `table-${this.type}`
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasValue(item, column) {
|
||||
return item[column.toLowerCase()] !== 'undefined'
|
||||
},
|
||||
itemValue(item, column) {
|
||||
return item[column.toLowerCase()]
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,25 +0,0 @@
|
||||
<template>
|
||||
<nav aria-label="breadcrumb">
|
||||
<b-breadcrumb :class="[{ [`bg-${type}`]: type }, listClasses]">
|
||||
<slot></slot>
|
||||
</b-breadcrumb>
|
||||
</nav>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'breadcrumb',
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
description: 'Breadcrumb background type',
|
||||
},
|
||||
listClasses: {
|
||||
type: [String, Object],
|
||||
default: '',
|
||||
description: 'Breadcrumb list classes',
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,18 +0,0 @@
|
||||
<template>
|
||||
<b-breadcrumb-item :active="active">
|
||||
<slot></slot>
|
||||
</b-breadcrumb-item>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'breadcrumb-item',
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
description: 'Whether breadcrumb item is active',
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,40 +0,0 @@
|
||||
<template>
|
||||
<bread-crumb list-classes="breadcrumb-links breadcrumb-dark">
|
||||
<bread-crumb-item>
|
||||
<router-link to="/overview">
|
||||
<i class="fas fa-home"></i>
|
||||
</router-link>
|
||||
</bread-crumb-item>
|
||||
<bread-crumb-item
|
||||
v-for="(route, index) in $route.matched.slice()"
|
||||
:key="route.name"
|
||||
:active="index === $route.matched.length - 1"
|
||||
style="display: inline-block"
|
||||
>
|
||||
<router-link :to="{ name: route.name }" v-if="index < $route.matched.length - 1">
|
||||
{{ route.name }}
|
||||
</router-link>
|
||||
<span v-else>{{ route.name }}</span>
|
||||
</bread-crumb-item>
|
||||
</bread-crumb>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BreadCrumb from './Breadcrumb'
|
||||
import BreadCrumbItem from './BreadcrumbItem'
|
||||
|
||||
export default {
|
||||
name: 'route-breadcrumb',
|
||||
components: {
|
||||
BreadCrumb,
|
||||
BreadCrumbItem,
|
||||
},
|
||||
methods: {
|
||||
getBreadName(route) {
|
||||
return route.name
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@ -1,38 +0,0 @@
|
||||
<template>
|
||||
<div class="btn-group-toggle" data-toggle="buttons">
|
||||
<label class="btn" :class="[{ active: value }, buttonClasses]">
|
||||
<input v-model="model" type="checkbox" checked="" autocomplete="off" />
|
||||
<slot></slot>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'button-checkbox',
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
description: 'Checked value',
|
||||
},
|
||||
buttonClasses: {
|
||||
type: [String, Object],
|
||||
description: 'Inner button css classes',
|
||||
},
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
computed: {
|
||||
model: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,55 +0,0 @@
|
||||
<template>
|
||||
<div class="btn-group-toggle" data-toggle="buttons">
|
||||
<label
|
||||
v-for="(option, index) in options"
|
||||
:key="index"
|
||||
class="btn"
|
||||
:class="[{ active: value === option.value }, buttonClasses]"
|
||||
>
|
||||
<input
|
||||
:value="option.value"
|
||||
v-model="model"
|
||||
type="radio"
|
||||
id="option1"
|
||||
autocomplete="off"
|
||||
checked=""
|
||||
/>
|
||||
{{ option.label }}
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'button-radio-group',
|
||||
props: {
|
||||
options: {
|
||||
type: Array,
|
||||
description: 'Radio options. Should be an array of objects {value: "", label: ""}',
|
||||
default: () => [],
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
description: 'Radio value',
|
||||
},
|
||||
buttonClasses: {
|
||||
type: [String, Object],
|
||||
description: 'Inner button css classes',
|
||||
},
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
computed: {
|
||||
model: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('change', val)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,71 +0,0 @@
|
||||
<template>
|
||||
<b-card
|
||||
no-body
|
||||
:class="[
|
||||
{ 'card-lift--hover': hover },
|
||||
{ shadow: shadow },
|
||||
{ [`shadow-${shadowSize}`]: shadowSize },
|
||||
{ [`bg-gradient-${gradient}`]: gradient },
|
||||
{ [`bg-${type}`]: type },
|
||||
]"
|
||||
>
|
||||
<slot name="image"></slot>
|
||||
<b-card-header :class="headerClasses" v-if="$slots.header">
|
||||
<slot name="header"></slot>
|
||||
</b-card-header>
|
||||
<b-card-body :class="bodyClasses" v-if="!noBody">
|
||||
<slot></slot>
|
||||
</b-card-body>
|
||||
|
||||
<slot v-if="noBody"></slot>
|
||||
|
||||
<b-card-footer :class="footerClasses" v-if="$slots.footer">
|
||||
<slot name="footer"></slot>
|
||||
</b-card-footer>
|
||||
</b-card>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'card',
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
description: 'Card type',
|
||||
},
|
||||
gradient: {
|
||||
type: String,
|
||||
description: 'Card background gradient type (warning,danger etc)',
|
||||
},
|
||||
hover: {
|
||||
type: Boolean,
|
||||
description: 'Whether card should move on hover',
|
||||
},
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
description: 'Whether card has shadow',
|
||||
},
|
||||
shadowSize: {
|
||||
type: String,
|
||||
description: 'Card shadow size',
|
||||
},
|
||||
noBody: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
description: 'Whether card should have wrapper body class',
|
||||
},
|
||||
bodyClasses: {
|
||||
type: [String, Object, Array],
|
||||
description: 'Card body css classes',
|
||||
},
|
||||
headerClasses: {
|
||||
type: [String, Object, Array],
|
||||
description: 'Card header css classes',
|
||||
},
|
||||
footerClasses: {
|
||||
type: [String, Object, Array],
|
||||
description: 'Card footer css classes',
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<card class="card-stats" :show-footer-line="true">
|
||||
<b-row>
|
||||
<b-col>
|
||||
<slot>
|
||||
<h5 class="card-title text-uppercase text-muted mb-0" v-if="title">
|
||||
{{ title }}
|
||||
</h5>
|
||||
<span class="h2 font-weight-bold mb-0" v-if="subTitle">
|
||||
{{ subTitle }}
|
||||
</span>
|
||||
</slot>
|
||||
</b-col>
|
||||
|
||||
<b-col cols="auto" v-if="$slots.icon || icon">
|
||||
<slot name="icon">
|
||||
<div
|
||||
class="icon icon-shape text-white rounded-circle shadow"
|
||||
:class="[`bg-${type}`, iconClasses]"
|
||||
>
|
||||
<i :class="icon"></i>
|
||||
</div>
|
||||
</slot>
|
||||
</b-col>
|
||||
<b-col cols="auto" v-if="$slots.img || img">
|
||||
<slot name="img">
|
||||
<img :src="img" width="80" />
|
||||
</slot>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<p class="mt-3 mb-0 text-sm">
|
||||
<slot name="footer"></slot>
|
||||
</p>
|
||||
</card>
|
||||
</template>
|
||||
<script>
|
||||
import Card from './Card.vue'
|
||||
|
||||
export default {
|
||||
name: 'stats-card',
|
||||
components: {
|
||||
Card,
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'primary',
|
||||
},
|
||||
icon: String,
|
||||
img: String,
|
||||
title: String,
|
||||
subTitle: String,
|
||||
iconClasses: [String, Array],
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,30 +0,0 @@
|
||||
import { Bar, mixins } from 'vue-chartjs'
|
||||
import globalOptionsMixin from '@/components/Charts/globalOptionsMixin'
|
||||
|
||||
export default {
|
||||
name: 'bar-chart',
|
||||
extends: Bar,
|
||||
mixins: [mixins.reactiveProp, globalOptionsMixin],
|
||||
props: {
|
||||
extraOptions: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ctx: null,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$watch(
|
||||
'chartData',
|
||||
(newVal, oldVal) => {
|
||||
if (!oldVal) {
|
||||
this.renderChart(this.chartData, this.extraOptions)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
},
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
import { Line, mixins } from 'vue-chartjs'
|
||||
import globalOptionsMixin from '@/components/Charts/globalOptionsMixin'
|
||||
export default {
|
||||
name: 'line-chart',
|
||||
extends: Line,
|
||||
mixins: [mixins.reactiveProp, globalOptionsMixin],
|
||||
props: {
|
||||
extraOptions: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ctx: null,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$watch(
|
||||
'chartData',
|
||||
(newVal, oldVal) => {
|
||||
if (!oldVal) {
|
||||
this.renderChart(this.chartData, this.extraOptions)
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
},
|
||||
}
|
||||
@ -1,234 +0,0 @@
|
||||
import { parseOptions } from '@/components/Charts/optionHelpers'
|
||||
import Chart from 'chart.js'
|
||||
|
||||
export const Charts = {
|
||||
mode: 'light', // (themeMode) ? themeMode : 'light';
|
||||
fonts: {
|
||||
base: 'Open Sans',
|
||||
},
|
||||
colors: {
|
||||
gray: {
|
||||
100: '#f6f9fc',
|
||||
200: '#e9ecef',
|
||||
300: '#dee2e6',
|
||||
400: '#ced4da',
|
||||
500: '#adb5bd',
|
||||
600: '#8898aa',
|
||||
700: '#525f7f',
|
||||
800: '#32325d',
|
||||
900: '#212529',
|
||||
},
|
||||
theme: {
|
||||
default: '#172b4d',
|
||||
primary: '#5e72e4',
|
||||
secondary: '#f4f5f7',
|
||||
info: '#11cdef',
|
||||
success: '#2dce89',
|
||||
danger: '#f5365c',
|
||||
warning: '#fb6340',
|
||||
},
|
||||
black: '#12263F',
|
||||
white: '#FFFFFF',
|
||||
transparent: 'transparent',
|
||||
},
|
||||
}
|
||||
|
||||
function chartOptions() {
|
||||
const { colors, mode, fonts } = Charts
|
||||
// Options
|
||||
const options = {
|
||||
defaults: {
|
||||
global: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
defaultColor: mode === 'dark' ? colors.gray[700] : colors.gray[600],
|
||||
defaultFontColor: mode === 'dark' ? colors.gray[700] : colors.gray[600],
|
||||
defaultFontFamily: fonts.base,
|
||||
defaultFontSize: 13,
|
||||
layout: {
|
||||
padding: 0,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
usePointStyle: true,
|
||||
padding: 16,
|
||||
},
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
radius: 0,
|
||||
backgroundColor: colors.theme.primary,
|
||||
},
|
||||
line: {
|
||||
tension: 0.4,
|
||||
borderWidth: 4,
|
||||
borderColor: colors.theme.primary,
|
||||
backgroundColor: colors.transparent,
|
||||
borderCapStyle: 'rounded',
|
||||
},
|
||||
rectangle: {
|
||||
backgroundColor: colors.theme.warning,
|
||||
},
|
||||
arc: {
|
||||
backgroundColor: colors.theme.primary,
|
||||
borderColor: mode === 'dark' ? colors.gray[800] : colors.white,
|
||||
borderWidth: 4,
|
||||
},
|
||||
},
|
||||
tooltips: {
|
||||
enabled: true,
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
},
|
||||
pie: {
|
||||
tooltips: {
|
||||
mode: 'point',
|
||||
},
|
||||
},
|
||||
doughnut: {
|
||||
tooltips: {
|
||||
mode: 'point',
|
||||
},
|
||||
cutoutPercentage: 83,
|
||||
legendCallback: function (chart) {
|
||||
const data = chart.data
|
||||
let content = ''
|
||||
|
||||
data.labels.forEach(function (label, index) {
|
||||
const bgColor = data.datasets[0].backgroundColor[index]
|
||||
|
||||
content += '<span class="chart-legend-item">'
|
||||
content +=
|
||||
'<i class="chart-legend-indicator" style="background-color: ' + bgColor + '"></i>'
|
||||
content += label
|
||||
content += '</span>'
|
||||
})
|
||||
|
||||
return content
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// yAxes
|
||||
Chart.scaleService.updateScaleDefaults('linear', {
|
||||
gridLines: {
|
||||
borderDash: [2],
|
||||
borderDashOffset: [2],
|
||||
color: mode === 'dark' ? colors.gray[900] : colors.gray[200],
|
||||
drawBorder: false,
|
||||
drawTicks: true,
|
||||
zeroLineWidth: 1,
|
||||
zeroLineColor: mode === 'dark' ? colors.gray[900] : colors.gray[200],
|
||||
zeroLineBorderDash: [2],
|
||||
zeroLineBorderDashOffset: [2],
|
||||
},
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
padding: 10,
|
||||
callback: function (value) {
|
||||
if (!(value % 10)) {
|
||||
return value
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// xAxes
|
||||
Chart.scaleService.updateScaleDefaults('category', {
|
||||
gridLines: {
|
||||
drawBorder: false,
|
||||
drawOnChartArea: false,
|
||||
drawTicks: false,
|
||||
lineWidth: 1,
|
||||
zeroLineWidth: 1,
|
||||
},
|
||||
ticks: {
|
||||
padding: 20,
|
||||
},
|
||||
maxBarThickness: 10,
|
||||
})
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
let initialized = false
|
||||
|
||||
export function initGlobalOptions() {
|
||||
if (initialized) {
|
||||
return
|
||||
}
|
||||
parseOptions(Chart, chartOptions())
|
||||
initialized = true
|
||||
}
|
||||
|
||||
export const basicOptions = {
|
||||
maintainAspectRatio: false,
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
responsive: true,
|
||||
}
|
||||
export const blueChartOptions = {
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
gridLines: {
|
||||
color: Charts.colors.gray[700],
|
||||
zeroLineColor: Charts.colors.gray[700],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const lineChartOptionsBlue = {
|
||||
...basicOptions,
|
||||
tooltips: {
|
||||
backgroundColor: '#f5f5f5',
|
||||
titleFontColor: '#333',
|
||||
bodyFontColor: '#666',
|
||||
bodySpacing: 4,
|
||||
xPadding: 12,
|
||||
mode: 'nearest',
|
||||
intersect: 0,
|
||||
position: 'nearest',
|
||||
},
|
||||
responsive: true,
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
barPercentage: 1.6,
|
||||
gridLines: {
|
||||
drawBorder: false,
|
||||
color: 'rgba(29,140,248,0.0)',
|
||||
zeroLineColor: 'transparent',
|
||||
},
|
||||
ticks: {
|
||||
suggestedMin: 60,
|
||||
suggestedMax: 125,
|
||||
padding: 20,
|
||||
fontColor: '#9e9e9e',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
xAxes: [
|
||||
{
|
||||
barPercentage: 1.6,
|
||||
gridLines: {
|
||||
drawBorder: false,
|
||||
color: 'rgba(29,140,248,0.1)',
|
||||
zeroLineColor: 'transparent',
|
||||
},
|
||||
ticks: {
|
||||
padding: 20,
|
||||
fontColor: '#9e9e9e',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
import { initGlobalOptions } from '@/components/Charts/config'
|
||||
import './roundedCornersExtension'
|
||||
export default {
|
||||
mounted() {
|
||||
initGlobalOptions()
|
||||
},
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
// Parse global options
|
||||
export function parseOptions(parent, options) {
|
||||
for (const item in options) {
|
||||
if (typeof options[item] !== 'object') {
|
||||
parent[item] = options[item]
|
||||
} else {
|
||||
parseOptions(parent[item], options[item])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,126 +0,0 @@
|
||||
//
|
||||
// Chart extension for making the bars rounded
|
||||
// Code from: https://codepen.io/jedtrow/full/ygRYgo
|
||||
//
|
||||
import Chart from 'chart.js'
|
||||
Chart.elements.Rectangle.prototype.draw = function () {
|
||||
const ctx = this._chart.ctx
|
||||
const vm = this._view
|
||||
let left, right, top, bottom, signX, signY, borderSkipped
|
||||
let borderWidth = vm.borderWidth
|
||||
// Set Radius Here
|
||||
// If radius is large enough to cause drawing errors a max radius is imposed
|
||||
const cornerRadius = 6
|
||||
|
||||
if (!vm.horizontal) {
|
||||
// bar
|
||||
left = vm.x - vm.width / 2
|
||||
right = vm.x + vm.width / 2
|
||||
top = vm.y
|
||||
bottom = vm.base
|
||||
signX = 1
|
||||
signY = bottom > top ? 1 : -1
|
||||
borderSkipped = vm.borderSkipped || 'bottom'
|
||||
} else {
|
||||
// horizontal bar
|
||||
left = vm.base
|
||||
right = vm.x
|
||||
top = vm.y - vm.height / 2
|
||||
bottom = vm.y + vm.height / 2
|
||||
signX = right > left ? 1 : -1
|
||||
signY = 1
|
||||
borderSkipped = vm.borderSkipped || 'left'
|
||||
}
|
||||
|
||||
// Canvas doesn't allow us to stroke inside the width so we can
|
||||
// adjust the sizes to fit if we're setting a stroke on the line
|
||||
if (borderWidth) {
|
||||
// borderWidth shold be less than bar width and bar height.
|
||||
const barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom))
|
||||
borderWidth = borderWidth > barSize ? barSize : borderWidth
|
||||
const halfStroke = borderWidth / 2
|
||||
// Adjust borderWidth when bar top position is near vm.base(zero).
|
||||
const borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0)
|
||||
const borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0)
|
||||
const borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0)
|
||||
const borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0)
|
||||
// not become a vertical line?
|
||||
if (borderLeft !== borderRight) {
|
||||
top = borderTop
|
||||
bottom = borderBottom
|
||||
}
|
||||
// not become a horizontal line?
|
||||
if (borderTop !== borderBottom) {
|
||||
left = borderLeft
|
||||
right = borderRight
|
||||
}
|
||||
}
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.fillStyle = vm.backgroundColor
|
||||
ctx.strokeStyle = vm.borderColor
|
||||
ctx.lineWidth = borderWidth
|
||||
|
||||
// Corner points, from bottom-left to bottom-right clockwise
|
||||
// | 1 2 |
|
||||
// | 0 3 |
|
||||
const corners = [
|
||||
[left, bottom],
|
||||
[left, top],
|
||||
[right, top],
|
||||
[right, bottom],
|
||||
]
|
||||
|
||||
// Find first (starting) corner with fallback to 'bottom'
|
||||
const borders = ['bottom', 'left', 'top', 'right']
|
||||
let startCorner = borders.indexOf(borderSkipped, 0)
|
||||
if (startCorner === -1) {
|
||||
startCorner = 0
|
||||
}
|
||||
|
||||
function cornerAt(index) {
|
||||
return corners[(startCorner + index) % 4]
|
||||
}
|
||||
|
||||
// Draw rectangle from 'startCorner'
|
||||
let corner = cornerAt(0)
|
||||
ctx.moveTo(corner[0], corner[1])
|
||||
|
||||
for (let i = 1; i < 4; i++) {
|
||||
corner = cornerAt(i)
|
||||
let nextCornerId = i + 1
|
||||
if (nextCornerId === 4) {
|
||||
nextCornerId = 0
|
||||
}
|
||||
|
||||
const width = corners[2][0] - corners[1][0]
|
||||
const height = corners[0][1] - corners[1][1]
|
||||
const x = corners[1][0]
|
||||
const y = corners[1][1]
|
||||
|
||||
let radius = cornerRadius
|
||||
|
||||
// Fix radius being too large
|
||||
if (radius > height / 2) {
|
||||
radius = height / 2
|
||||
}
|
||||
if (radius > width / 2) {
|
||||
radius = width / 2
|
||||
}
|
||||
|
||||
ctx.moveTo(x + radius, y)
|
||||
ctx.lineTo(x + width - radius, y)
|
||||
ctx.quadraticCurveTo(x + width, y, x + width, y + radius)
|
||||
ctx.lineTo(x + width, y + height - radius)
|
||||
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height)
|
||||
ctx.lineTo(x + radius, y + height)
|
||||
ctx.quadraticCurveTo(x, y + height, x, y + height - radius)
|
||||
ctx.lineTo(x, y + radius)
|
||||
ctx.quadraticCurveTo(x, y, x + radius, y)
|
||||
}
|
||||
|
||||
ctx.fill()
|
||||
if (borderWidth) {
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
|
||||
import CloseButton from './CloseButton'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
describe('CloseButton', () => {
|
||||
let wrapper
|
||||
const propsData = {
|
||||
target: 'Target',
|
||||
expanded: false,
|
||||
}
|
||||
|
||||
const Wrapper = () => {
|
||||
return mount(CloseButton, { localVue, propsData })
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = Wrapper()
|
||||
})
|
||||
|
||||
it('emmits click event', () => {
|
||||
wrapper.find('.navbar-toggler').trigger('click')
|
||||
expect(wrapper.emitted('click')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1,36 +0,0 @@
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="navbar-toggler"
|
||||
data-toggle="collapse"
|
||||
@click="handleClick"
|
||||
:data-target="`#${target}`"
|
||||
:aria-controls="target"
|
||||
:aria-expanded="expanded"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'close-button',
|
||||
props: {
|
||||
target: {
|
||||
type: [String, Number],
|
||||
description: 'Close button target element',
|
||||
},
|
||||
expanded: {
|
||||
type: Boolean,
|
||||
description: 'Whether button is expanded (aria-expanded attribute)',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClick(evt) {
|
||||
this.$emit('click', evt)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="custom-control custom-checkbox"
|
||||
:class="[{ disabled: disabled }, { [`custom-checkbox-${type}`]: type }, inlineClass]"
|
||||
>
|
||||
<input
|
||||
:id="cbId"
|
||||
class="custom-control-input"
|
||||
:class="inputClasses"
|
||||
type="checkbox"
|
||||
:disabled="disabled"
|
||||
v-model="model"
|
||||
/>
|
||||
<label :for="cbId" class="custom-control-label">
|
||||
<slot>
|
||||
<span v-if="inline"> </span>
|
||||
</slot>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'base-checkbox',
|
||||
model: {
|
||||
prop: 'checked',
|
||||
},
|
||||
props: {
|
||||
checked: {
|
||||
type: [Array, Boolean],
|
||||
description: 'Whether checkbox is checked',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
description: 'Whether checkbox is disabled',
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
description: 'Whether checkbox is inline',
|
||||
},
|
||||
inputClasses: {
|
||||
type: [String, Object, Array],
|
||||
description: 'Checkbox input classes',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
description: 'Checkbox type (e.g info, danger etc)',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cbId: '',
|
||||
touched: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
model: {
|
||||
get() {
|
||||
return this.checked
|
||||
},
|
||||
set(check) {
|
||||
if (!this.touched) {
|
||||
this.touched = true
|
||||
}
|
||||
this.$emit('input', check)
|
||||
},
|
||||
},
|
||||
inlineClass() {
|
||||
if (this.inline) {
|
||||
return `form-check-inline`
|
||||
}
|
||||
return ''
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.cbId = Math.random().toString(16).slice(2)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,187 +0,0 @@
|
||||
<template>
|
||||
<validation-provider
|
||||
:rules="rules"
|
||||
:name="name"
|
||||
v-bind="$attrs"
|
||||
v-slot="{ errors, valid, invalid, validated }"
|
||||
>
|
||||
<b-form-group>
|
||||
<slot name="label">
|
||||
<label v-if="label" :class="labelClasses">
|
||||
{{ label }}
|
||||
</label>
|
||||
</slot>
|
||||
<div
|
||||
:class="[
|
||||
{ 'input-group': hasIcon },
|
||||
{ focused: focused },
|
||||
{ 'input-group-alternative': alternative },
|
||||
{ 'has-label': label || $slots.label },
|
||||
inputGroupClasses,
|
||||
]"
|
||||
>
|
||||
<div v-if="prependIcon || $slots.prepend" class="input-group-prepend">
|
||||
<span class="input-group-text">
|
||||
<slot name="prepend">
|
||||
<i :class="prependIcon"></i>
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
<slot v-bind="slotData">
|
||||
<input
|
||||
:value="value"
|
||||
:type="type"
|
||||
v-on="listeners"
|
||||
v-bind="$attrs"
|
||||
:valid="valid"
|
||||
:required="required"
|
||||
class="form-control"
|
||||
:class="[
|
||||
{ 'is-valid': valid && validated && successMessage },
|
||||
{ 'is-invalid': invalid && validated },
|
||||
inputClasses,
|
||||
]"
|
||||
/>
|
||||
</slot>
|
||||
<div v-if="appendIcon || $slots.append" class="input-group-append">
|
||||
<span class="input-group-text">
|
||||
<slot name="append">
|
||||
<i :class="appendIcon"></i>
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
<slot name="infoBlock"></slot>
|
||||
</div>
|
||||
<slot name="success">
|
||||
<div class="valid-feedback" v-if="valid && validated && successMessage">
|
||||
{{ successMessage }}
|
||||
</div>
|
||||
</slot>
|
||||
<slot name="error">
|
||||
<div v-if="errors[0]" class="invalid-feedback" style="display: block">
|
||||
{{ errors[0] }}
|
||||
</div>
|
||||
</slot>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
inheritAttrs: false,
|
||||
name: 'base-input',
|
||||
props: {
|
||||
required: {
|
||||
type: Boolean,
|
||||
description: 'Whether input is required (adds an asterix *)',
|
||||
},
|
||||
group: {
|
||||
type: Boolean,
|
||||
description: 'Whether input is an input group (manual override in special cases)',
|
||||
},
|
||||
alternative: {
|
||||
type: Boolean,
|
||||
description: 'Whether input is of alternative layout',
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
description: 'Input label (text before input)',
|
||||
},
|
||||
error: {
|
||||
type: String,
|
||||
description: 'Input error (below input)',
|
||||
},
|
||||
successMessage: {
|
||||
type: String,
|
||||
description: 'Input success message',
|
||||
default: '',
|
||||
},
|
||||
labelClasses: {
|
||||
type: String,
|
||||
description: 'Input label css classes',
|
||||
default: 'form-control-label',
|
||||
},
|
||||
inputClasses: {
|
||||
type: String,
|
||||
description: 'Input css classes',
|
||||
},
|
||||
inputGroupClasses: {
|
||||
type: String,
|
||||
description: 'Input group css classes',
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
description: 'Input value',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
description: 'Input type',
|
||||
default: 'text',
|
||||
},
|
||||
appendIcon: {
|
||||
type: String,
|
||||
description: 'Append icon (right)',
|
||||
},
|
||||
prependIcon: {
|
||||
type: String,
|
||||
description: 'Prepend icon (left)',
|
||||
},
|
||||
rules: {
|
||||
type: [String, Array, Object],
|
||||
description: 'Vee validate validation rules',
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
description: 'Input name (used for validation)',
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
focused: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
listeners() {
|
||||
return {
|
||||
...this.$listeners,
|
||||
input: this.updateValue,
|
||||
focus: this.onFocus,
|
||||
blur: this.onBlur,
|
||||
}
|
||||
},
|
||||
slotData() {
|
||||
return {
|
||||
focused: this.focused,
|
||||
error: this.error,
|
||||
...this.listeners,
|
||||
}
|
||||
},
|
||||
hasIcon() {
|
||||
const { append, prepend } = this.$slots
|
||||
return (
|
||||
append !== undefined ||
|
||||
prepend !== undefined ||
|
||||
this.appendIcon !== undefined ||
|
||||
this.prependIcon !== undefined ||
|
||||
this.group
|
||||
)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateValue(evt) {
|
||||
const value = evt.target.value
|
||||
this.$emit('input', value)
|
||||
},
|
||||
onFocus(evt) {
|
||||
this.focused = true
|
||||
this.$emit('focus', evt)
|
||||
},
|
||||
onBlur(evt) {
|
||||
this.focused = false
|
||||
this.$emit('blur', evt)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<div class="custom-control custom-radio" :class="[inlineClass, { disabled: disabled }]">
|
||||
<input
|
||||
:id="cbId"
|
||||
class="custom-control-input"
|
||||
type="radio"
|
||||
:disabled="disabled"
|
||||
:value="name"
|
||||
v-model="model"
|
||||
/>
|
||||
<label :for="cbId" class="custom-control-label">
|
||||
<slot>
|
||||
<span v-if="inline"> </span>
|
||||
</slot>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'base-radio',
|
||||
props: {
|
||||
name: {
|
||||
type: [String, Number],
|
||||
description: 'Radio label',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
description: 'Whether radio is disabled',
|
||||
},
|
||||
value: {
|
||||
type: [String, Boolean],
|
||||
description: 'Radio value',
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
description: 'Whether radio is inline',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cbId: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
model: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(value) {
|
||||
this.$emit('input', value)
|
||||
},
|
||||
},
|
||||
inlineClass() {
|
||||
if (this.inline) {
|
||||
return `form-check-inline`
|
||||
}
|
||||
return ''
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.cbId = Math.random().toString(16).slice(2)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,127 +0,0 @@
|
||||
<template>
|
||||
<slide-y-up-transition :duration="animationDuration">
|
||||
<b-modal
|
||||
class="modal fade"
|
||||
ref="app-modal"
|
||||
:size="size"
|
||||
:hide-header="!$slots.header"
|
||||
:modal-class="[{ 'modal-mini': type === 'mini' }, ...modalClasses]"
|
||||
@mousedown.self="closeModal"
|
||||
tabindex="-1"
|
||||
role="dialog"
|
||||
centered
|
||||
@close="closeModal"
|
||||
@hide="closeModal"
|
||||
:header-class="headerClasses"
|
||||
:footer-class="footerClasses"
|
||||
:content-class="[gradient ? `bg-gradient-${gradient}` : '', ...modalContentClasses]"
|
||||
:body-class="bodyClasses"
|
||||
:aria-hidden="!show"
|
||||
>
|
||||
<template v-slot:modal-header>
|
||||
<slot name="header"></slot>
|
||||
<slot name="close-button">
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
v-if="showClose"
|
||||
@click="closeModal"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
>
|
||||
<span :aria-hidden="!show">×</span>
|
||||
</button>
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
<slot />
|
||||
|
||||
<template v-slot:modal-footer>
|
||||
<slot name="footer"></slot>
|
||||
</template>
|
||||
</b-modal>
|
||||
</slide-y-up-transition>
|
||||
</template>
|
||||
<script>
|
||||
import { SlideYUpTransition } from 'vue2-transitions'
|
||||
|
||||
export default {
|
||||
name: 'modal',
|
||||
components: {
|
||||
SlideYUpTransition,
|
||||
},
|
||||
props: {
|
||||
show: Boolean,
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
validator(value) {
|
||||
const acceptedValues = ['', 'notice', 'mini']
|
||||
return acceptedValues.indexOf(value) !== -1
|
||||
},
|
||||
description: 'Modal type (notice|mini|"") ',
|
||||
},
|
||||
modalClasses: {
|
||||
type: [Object, String],
|
||||
description: 'Modal dialog css classes',
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
description: 'Modal size',
|
||||
validator(value) {
|
||||
const acceptedValues = ['', 'sm', 'lg']
|
||||
return acceptedValues.indexOf(value) !== -1
|
||||
},
|
||||
},
|
||||
modalContentClasses: {
|
||||
type: [Object, String],
|
||||
description: 'Modal dialog content css classes',
|
||||
},
|
||||
gradient: {
|
||||
type: String,
|
||||
description: 'Modal gradient type (danger, primary etc)',
|
||||
},
|
||||
headerClasses: {
|
||||
type: [Object, String],
|
||||
description: 'Modal Header css classes',
|
||||
},
|
||||
bodyClasses: {
|
||||
type: [Object, String],
|
||||
description: 'Modal Body css classes',
|
||||
},
|
||||
footerClasses: {
|
||||
type: [Object, String],
|
||||
description: 'Modal Footer css classes',
|
||||
},
|
||||
animationDuration: {
|
||||
type: Number,
|
||||
default: 500,
|
||||
description: 'Modal transition duration',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
closeModal() {
|
||||
this.$emit('update:show', false)
|
||||
this.$emit('close')
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
show(val) {
|
||||
if (val) {
|
||||
this.$refs['app-modal'].show()
|
||||
} else {
|
||||
this.$refs['app-modal'].hide()
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.modal-backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.6) !important;
|
||||
}
|
||||
</style>
|
||||
@ -1,80 +0,0 @@
|
||||
<template>
|
||||
<!--Notice modal-->
|
||||
<modal
|
||||
:show.sync="$store.state.modals"
|
||||
modal-classes="modal-danger"
|
||||
modal-content-classes="bg-gradient-danger"
|
||||
>
|
||||
<h6 slot="header" class="modal-title">Your attention is required</h6>
|
||||
|
||||
<div class="py-3 text-center">
|
||||
<form ref="form" @submit.stop.prevent="handleSubmit">
|
||||
<b-form-group
|
||||
label="Name"
|
||||
label-for="name-input"
|
||||
invalid-feedback="Name is required"
|
||||
:state="nameState"
|
||||
>
|
||||
<b-form-input id="name-input" v-model="name" :state="nameState" required></b-form-input>
|
||||
</b-form-group>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<template slot="footer">
|
||||
<base-button type="white">Ok</base-button>
|
||||
<base-button type="link" class="ml-auto" @click="$store.state.modals = false">
|
||||
abbrechen
|
||||
</base-button>
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'modal',
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
nameState: null,
|
||||
submittedNames: [],
|
||||
}
|
||||
},
|
||||
/* Modal */
|
||||
checkFormValidity() {
|
||||
const valid = this.$refs.form.checkValidity()
|
||||
this.nameState = valid
|
||||
return valid
|
||||
},
|
||||
resetModal() {
|
||||
this.name = ''
|
||||
this.nameState = null
|
||||
},
|
||||
handleOk(bvModalEvt) {
|
||||
// Prevent modal from closing
|
||||
bvModalEvt.preventDefault()
|
||||
// Trigger submit handler
|
||||
this.handleSubmit()
|
||||
},
|
||||
handleSubmit() {
|
||||
// Exit when the form isn't valid
|
||||
if (!this.checkFormValidity()) {
|
||||
return
|
||||
}
|
||||
// Push the name to submitted names
|
||||
this.submittedNames.push(this.name)
|
||||
this.$store.state.modals = false
|
||||
this.$store.commit('loginAsAdmin')
|
||||
this.$router.push('/AdminOverview')
|
||||
|
||||
// Hide the modal manually
|
||||
this.$nextTick(() => {
|
||||
this.$bvModal.hide('modal-prevent-closing')
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.modal-backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.6) !important;
|
||||
}
|
||||
</style>
|
||||
@ -1,120 +0,0 @@
|
||||
<template>
|
||||
<b-navbar toggleable :class="classes">
|
||||
<div :class="containerClasses">
|
||||
<slot name="brand"></slot>
|
||||
|
||||
<slot name="toggle-button">
|
||||
<button
|
||||
class="navbar-toggler collapsed"
|
||||
v-if="hasMenu"
|
||||
type="button"
|
||||
@click="toggleMenu"
|
||||
aria-expanded="false"
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<span class="navbar-toggler-bar navbar-kebab"></span>
|
||||
<span class="navbar-toggler-bar navbar-kebab"></span>
|
||||
<span class="navbar-toggler-bar navbar-kebab"></span>
|
||||
</button>
|
||||
</slot>
|
||||
|
||||
<b-navbar-toggle target="nav-text-collapse" @click.stop="toggleMenu"></b-navbar-toggle>
|
||||
|
||||
<b-collapse
|
||||
is-nav
|
||||
id="nav-text-collapse"
|
||||
class="navbar-custom-collapse collapse"
|
||||
:class="menuClasses"
|
||||
:visible="show"
|
||||
v-click-outside="closeMenu"
|
||||
>
|
||||
<slot :close-menu="closeMenu"></slot>
|
||||
</b-collapse>
|
||||
</div>
|
||||
</b-navbar>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'base-nav',
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
description:
|
||||
'Whether navbar menu is shown (valid for viewports < specified by `expand` prop)',
|
||||
},
|
||||
transparent: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
description: 'Whether navbar is transparent',
|
||||
},
|
||||
expand: {
|
||||
type: String,
|
||||
default: 'lg',
|
||||
description: 'Breakpoint where nav should expand',
|
||||
},
|
||||
menuClasses: {
|
||||
type: [String, Object, Array],
|
||||
default: '',
|
||||
description: 'Navbar menu (items) classes. Can be used to align menu items to the right/left',
|
||||
},
|
||||
containerClasses: {
|
||||
type: [String, Object, Array],
|
||||
default: 'container',
|
||||
description:
|
||||
'Container classes. Can be used to control container classes (contains both navbar brand and menu items)',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
validator(value) {
|
||||
return [
|
||||
'',
|
||||
'dark',
|
||||
'success',
|
||||
'danger',
|
||||
'warning',
|
||||
'white',
|
||||
'primary',
|
||||
'light',
|
||||
'info',
|
||||
'vue',
|
||||
].includes(value)
|
||||
},
|
||||
description: 'Navbar color type',
|
||||
},
|
||||
},
|
||||
model: {
|
||||
prop: 'show',
|
||||
event: 'change',
|
||||
},
|
||||
computed: {
|
||||
classes() {
|
||||
const color = `bg-${this.type}`
|
||||
const classes = [
|
||||
{ 'navbar-transparent': this.transparent },
|
||||
{ [`navbar-expand-${this.expand}`]: this.expand },
|
||||
]
|
||||
if (this.position) {
|
||||
classes.push(`navbar-${this.position}`)
|
||||
}
|
||||
if (!this.transparent) {
|
||||
classes.push(color)
|
||||
}
|
||||
return classes
|
||||
},
|
||||
hasMenu() {
|
||||
return this.$slots.default
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleMenu() {
|
||||
this.$emit('change', !this.show)
|
||||
},
|
||||
closeMenu() {
|
||||
this.$emit('change', false)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,194 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
@click="tryClose"
|
||||
data-notify="container"
|
||||
class="alert alert-notify alert-dismissible"
|
||||
:class="[{ 'alert-with-icon': icon }, verticalAlign, horizontalAlign, alertType]"
|
||||
role="alert"
|
||||
:style="customPosition"
|
||||
data-notify-position="top-center"
|
||||
>
|
||||
<template v-if="icon || $slots.icon">
|
||||
<slot name="icon">
|
||||
<span class="alert-icon" data-notify="icon">
|
||||
<i :class="icon"></i>
|
||||
</span>
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
<span class="alert-text">
|
||||
<span v-if="title" class="title">
|
||||
<b>
|
||||
{{ title }}
|
||||
<br />
|
||||
</b>
|
||||
</span>
|
||||
<span v-if="message" v-html="message"></span>
|
||||
<content-render v-if="!message && component" :component="component"></content-render>
|
||||
</span>
|
||||
|
||||
<slot name="dismiss-icon">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close" @click="close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'notification',
|
||||
components: {
|
||||
contentRender: {
|
||||
props: ['component'],
|
||||
render: function (createElement) {
|
||||
return createElement(this.component)
|
||||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
message: String,
|
||||
title: {
|
||||
type: String,
|
||||
description: 'Notification title',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
description: 'Notification icon',
|
||||
},
|
||||
verticalAlign: {
|
||||
type: String,
|
||||
default: 'top',
|
||||
validator: (value) => {
|
||||
const acceptedValues = ['top', 'bottom']
|
||||
return acceptedValues.indexOf(value) !== -1
|
||||
},
|
||||
description: 'Vertical alignment of notification (top|bottom)',
|
||||
},
|
||||
horizontalAlign: {
|
||||
type: String,
|
||||
default: 'right',
|
||||
validator: (value) => {
|
||||
const acceptedValues = ['left', 'center', 'right']
|
||||
return acceptedValues.indexOf(value) !== -1
|
||||
},
|
||||
description: 'Horizontal alignment of notification (left|center|right)',
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'info',
|
||||
validator: (value) => {
|
||||
const acceptedValues = ['default', 'info', 'primary', 'danger', 'warning', 'success']
|
||||
return acceptedValues.indexOf(value) !== -1
|
||||
},
|
||||
description:
|
||||
'Notification type of notification (default|info|primary|danger|warning|success)',
|
||||
},
|
||||
timeout: {
|
||||
type: Number,
|
||||
default: 5000,
|
||||
validator: (value) => {
|
||||
return value >= 0
|
||||
},
|
||||
description: 'Notification timeout (closes after X milliseconds). Default is 5000 (5s)',
|
||||
},
|
||||
timestamp: {
|
||||
type: Date,
|
||||
default: () => new Date(),
|
||||
description:
|
||||
'Notification timestamp (used internally to handle notification removal correctly)',
|
||||
},
|
||||
component: {
|
||||
type: [Object, Function],
|
||||
description: 'Custom content component. Cane be a `.vue` component or render function',
|
||||
},
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
description: 'Whether to show close button',
|
||||
},
|
||||
closeOnClick: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
description: "Whether to close notification when clicking it' body",
|
||||
},
|
||||
clickHandler: {
|
||||
type: Function,
|
||||
description: 'Custom notification click handler',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
elmHeight: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasIcon() {
|
||||
return this.icon && this.icon.length > 0
|
||||
},
|
||||
alertType() {
|
||||
return `alert-${this.type}`
|
||||
},
|
||||
customPosition() {
|
||||
const initialMargin = 20
|
||||
const alertHeight = this.elmHeight + 10
|
||||
let sameAlertsCount = this.$notifications.state.filter((alert) => {
|
||||
return (
|
||||
alert.horizontalAlign === this.horizontalAlign &&
|
||||
alert.verticalAlign === this.verticalAlign &&
|
||||
alert.timestamp <= this.timestamp
|
||||
)
|
||||
}).length
|
||||
if (this.$notifications.settings.overlap) {
|
||||
sameAlertsCount = 1
|
||||
}
|
||||
const pixels = (sameAlertsCount - 1) * alertHeight + initialMargin
|
||||
const styles = {}
|
||||
if (this.verticalAlign === 'top') {
|
||||
styles.top = `${pixels}px`
|
||||
} else {
|
||||
styles.bottom = `${pixels}px`
|
||||
}
|
||||
return styles
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit('close', this.timestamp)
|
||||
},
|
||||
tryClose(evt) {
|
||||
if (this.clickHandler) {
|
||||
this.clickHandler(evt, this)
|
||||
}
|
||||
if (this.closeOnClick) {
|
||||
this.close()
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.elmHeight = this.$el.clientHeight
|
||||
if (this.timeout) {
|
||||
setTimeout(this.close, this.timeout)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.notifications .alert {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
|
||||
&[data-notify='container'] {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
&.center {
|
||||
margin: 0 auto;
|
||||
}
|
||||
&.left {
|
||||
left: 20px;
|
||||
}
|
||||
&.right {
|
||||
right: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,52 +0,0 @@
|
||||
<template>
|
||||
<div class="notifications">
|
||||
<slide-y-up-transition :duration="transitionDuration" group mode="out-in">
|
||||
<notification
|
||||
v-for="notification in notifications"
|
||||
v-bind="notification"
|
||||
:clickHandler="notification.onClick"
|
||||
:key="notification.timestamp.getTime()"
|
||||
@close="removeNotification"
|
||||
></notification>
|
||||
</slide-y-up-transition>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Notification from './Notification.vue'
|
||||
import { SlideYUpTransition } from 'vue2-transitions'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SlideYUpTransition,
|
||||
Notification,
|
||||
},
|
||||
props: {
|
||||
transitionDuration: {
|
||||
type: Number,
|
||||
default: 200,
|
||||
},
|
||||
overlap: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
notifications: this.$notifications.state,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
removeNotification(timestamp) {
|
||||
this.$notifications.removeNotification(timestamp)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$notifications.settings.overlap = this.overlap
|
||||
},
|
||||
watch: {
|
||||
overlap: function (newVal) {
|
||||
this.$notifications.settings.overlap = newVal
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -1,66 +0,0 @@
|
||||
import Notifications from './Notifications.vue'
|
||||
|
||||
const NotificationStore = {
|
||||
state: [], // here the notifications will be added
|
||||
settings: {
|
||||
overlap: false,
|
||||
verticalAlign: 'top',
|
||||
horizontalAlign: 'right',
|
||||
type: 'info',
|
||||
timeout: 5000,
|
||||
closeOnClick: true,
|
||||
showClose: true,
|
||||
},
|
||||
setOptions(options) {
|
||||
this.settings = Object.assign(this.settings, options)
|
||||
},
|
||||
removeNotification(timestamp) {
|
||||
const indexToDelete = this.state.findIndex((n) => n.timestamp === timestamp)
|
||||
if (indexToDelete !== -1) {
|
||||
this.state.splice(indexToDelete, 1)
|
||||
}
|
||||
},
|
||||
addNotification(notification) {
|
||||
if (typeof notification === 'string' || notification instanceof String) {
|
||||
notification = { message: notification }
|
||||
}
|
||||
notification.timestamp = new Date()
|
||||
notification.timestamp.setMilliseconds(
|
||||
notification.timestamp.getMilliseconds() + this.state.length,
|
||||
)
|
||||
notification = Object.assign({}, this.settings, notification)
|
||||
this.state.push(notification)
|
||||
},
|
||||
notify(notification) {
|
||||
if (Array.isArray(notification)) {
|
||||
notification.forEach((notificationInstance) => {
|
||||
this.addNotification(notificationInstance)
|
||||
})
|
||||
} else {
|
||||
this.addNotification(notification)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const NotificationsPlugin = {
|
||||
install(Vue, options) {
|
||||
const app = new Vue({
|
||||
data: {
|
||||
notificationStore: NotificationStore,
|
||||
},
|
||||
methods: {
|
||||
notify(notification) {
|
||||
this.notificationStore.notify(notification)
|
||||
},
|
||||
},
|
||||
})
|
||||
Vue.prototype.$notify = app.notify
|
||||
Vue.prototype.$notifications = app.notificationStore
|
||||
Vue.component('Notifications', Notifications)
|
||||
if (options) {
|
||||
NotificationStore.setOptions(options)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default NotificationsPlugin
|
||||
@ -1,81 +0,0 @@
|
||||
<template>
|
||||
<vue-bootstrap-typeahead v-model="query" :data="users" @change="getUser" />
|
||||
</template>
|
||||
<script>
|
||||
import VueBootstrapTypeahead from 'vue-bootstrap-typeahead'
|
||||
|
||||
// Global registration
|
||||
// Vue.component('vue-bootstrap-typeahead', VueBootstrapTypeahead)
|
||||
|
||||
// OR
|
||||
|
||||
// Local registration
|
||||
export default {
|
||||
name: 'SearchUser',
|
||||
components: {
|
||||
VueBootstrapTypeahead,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
user: '',
|
||||
users: [
|
||||
'Bob',
|
||||
'Alice',
|
||||
'Bernd',
|
||||
'Dario',
|
||||
'Alex',
|
||||
'Pauls',
|
||||
'Ulf',
|
||||
'Delaware',
|
||||
'Florida',
|
||||
'Georgia',
|
||||
'Hawaii',
|
||||
'Idaho',
|
||||
'Illnois',
|
||||
'Indiana',
|
||||
'Iowa',
|
||||
'Kansas',
|
||||
'Kentucky',
|
||||
'Louisiana',
|
||||
'Maine',
|
||||
'Maryland',
|
||||
'Massachusetts',
|
||||
'Michigan',
|
||||
'Minnesota',
|
||||
'Mississippi',
|
||||
'Missouri',
|
||||
'Montana',
|
||||
'Nebraska',
|
||||
'Nevada',
|
||||
'New Hampshire',
|
||||
'New Jersey',
|
||||
'New Mexico',
|
||||
'New York',
|
||||
'North Carolina',
|
||||
'North Dakota',
|
||||
'Ohio',
|
||||
'Oklahoma',
|
||||
'Oregon',
|
||||
'Pennsylvania',
|
||||
'Rhode Island',
|
||||
'South Carolina',
|
||||
'South Dakota',
|
||||
'Tennessee',
|
||||
'Texas',
|
||||
'Utah',
|
||||
'Vermont',
|
||||
'Virginia',
|
||||
'Washington',
|
||||
'West Virginia',
|
||||
'Wisconsin',
|
||||
'Wyoming',
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getUser() {
|
||||
alert(this.data.user)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@ -41,6 +41,19 @@ describe('SideBar', () => {
|
||||
expect(wrapper.find('#sidenav-main').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('navbar button', () => {
|
||||
it('has a navbar button', () => {
|
||||
expect(wrapper.find('button.navbar-toggler').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('calls showSidebar when clicked', async () => {
|
||||
const spy = jest.spyOn(wrapper.vm.$sidebar, 'displaySidebar')
|
||||
wrapper.find('button.navbar-toggler').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(spy).toHaveBeenCalledWith(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('balance', () => {
|
||||
it('shows em-dash as balance while loading', () => {
|
||||
expect(wrapper.find('div.row.text-center').text()).toBe('— GDD')
|
||||
@ -55,19 +68,6 @@ describe('SideBar', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('navbar button', () => {
|
||||
it('has a navbar button', () => {
|
||||
expect(wrapper.find('button.navbar-toggler').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('calls showSidebar when clicked', async () => {
|
||||
const spy = jest.spyOn(wrapper.vm.$sidebar, 'displaySidebar')
|
||||
wrapper.find('button.navbar-toggler').trigger('click')
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(spy).toHaveBeenCalledWith(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('close siedbar', () => {
|
||||
it('calls closeSidebar when clicked', async () => {
|
||||
const spy = jest.spyOn(wrapper.vm.$sidebar, 'displaySidebar')
|
||||
|
||||
@ -14,13 +14,11 @@
|
||||
</b-row>
|
||||
<slot name="mobile-right">
|
||||
<ul class="nav align-items-center d-md-none">
|
||||
<a slot="title-container" class="nav-link" role="button">
|
||||
<div class="media align-items-center">
|
||||
<span class="avatar avatar-sm">
|
||||
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="media align-items-center">
|
||||
<span class="avatar avatar-sm">
|
||||
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
|
||||
</span>
|
||||
</div>
|
||||
</ul>
|
||||
</slot>
|
||||
<slot></slot>
|
||||
@ -71,15 +69,15 @@
|
||||
</template>
|
||||
<script>
|
||||
import NavbarToggleButton from '@/components/NavbarToggleButton'
|
||||
import VueQrcode from 'vue-qrcode'
|
||||
import LanguageSwitch from '@/components/LanguageSwitch.vue'
|
||||
import VueQrcode from 'vue-qrcode'
|
||||
|
||||
export default {
|
||||
name: 'sidebar',
|
||||
components: {
|
||||
NavbarToggleButton,
|
||||
VueQrcode,
|
||||
LanguageSwitch,
|
||||
VueQrcode,
|
||||
},
|
||||
props: {
|
||||
logo: {
|
||||
|
||||
@ -10,12 +10,10 @@ const SidebarStore = {
|
||||
},
|
||||
toggleMinimize() {
|
||||
document.body.classList.toggle('sidebar-mini')
|
||||
// we simulate the window Resize so the charts will get updated in realtime.
|
||||
const simulateWindowResize = setInterval(() => {
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
}, 180)
|
||||
|
||||
// we stop the simulation of Window Resize after the animations are completed
|
||||
setTimeout(() => {
|
||||
clearInterval(simulateWindowResize)
|
||||
}, 1000)
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
class="tab-pane"
|
||||
v-show="active"
|
||||
:id="id || title"
|
||||
:class="{ active: active }"
|
||||
:aria-expanded="active"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'tab-pane',
|
||||
props: ['title', 'id'],
|
||||
inject: ['addTab', 'removeTab'],
|
||||
data() {
|
||||
return {
|
||||
active: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.addTab(this)
|
||||
},
|
||||
destroyed() {
|
||||
if (this.$el && this.$el.parentNode) {
|
||||
this.$el.parentNode.removeChild(this.$el)
|
||||
}
|
||||
this.removeTab(this)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style></style>
|
||||
@ -1,155 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
:class="[
|
||||
{ 'col-md-4': vertical && !tabNavWrapperClasses },
|
||||
{ 'col-12': centered && !tabNavWrapperClasses },
|
||||
tabNavWrapperClasses,
|
||||
]"
|
||||
>
|
||||
<b-nav
|
||||
class="nav-pills"
|
||||
role="tablist"
|
||||
:class="[
|
||||
`nav-pills-${type}`,
|
||||
{ 'flex-column': vertical },
|
||||
{ 'justify-content-center': centered },
|
||||
tabNavClasses,
|
||||
]"
|
||||
>
|
||||
<b-nav-item
|
||||
v-for="tab in tabs"
|
||||
class="active"
|
||||
data-toggle="tab"
|
||||
role="tablist"
|
||||
:active="tab.active"
|
||||
:key="tab.id"
|
||||
:href="`#${tab.id}`"
|
||||
@click.prevent="activateTab(tab)"
|
||||
:aria-expanded="tab.active"
|
||||
>
|
||||
<tab-item-content :tab="tab"></tab-item-content>
|
||||
</b-nav-item>
|
||||
</b-nav>
|
||||
</div>
|
||||
<div
|
||||
class="tab-content"
|
||||
:class="[
|
||||
{ 'tab-space': !vertical },
|
||||
{ 'col-md-8': vertical && !tabContentClasses },
|
||||
tabContentClasses,
|
||||
]"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tabs',
|
||||
components: {
|
||||
TabItemContent: {
|
||||
props: ['tab'],
|
||||
render(h) {
|
||||
return h('div', [this.tab.$slots.title || this.tab.title])
|
||||
},
|
||||
},
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
addTab: this.addTab,
|
||||
removeTab: this.removeTab,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'primary',
|
||||
validator: (value) => {
|
||||
const acceptedValues = ['primary', 'info', 'success', 'warning', 'danger']
|
||||
return acceptedValues.indexOf(value) !== -1
|
||||
},
|
||||
},
|
||||
activeTab: {
|
||||
type: String,
|
||||
default: '',
|
||||
description: 'Active tab name',
|
||||
},
|
||||
tabNavWrapperClasses: {
|
||||
type: [String, Object],
|
||||
default: '',
|
||||
description: 'ul wrapper css classes',
|
||||
},
|
||||
tabNavClasses: {
|
||||
type: [String, Object],
|
||||
default: '',
|
||||
description: 'ul css classes',
|
||||
},
|
||||
tabContentClasses: {
|
||||
type: [String, Object],
|
||||
default: '',
|
||||
description: 'tab content css classes',
|
||||
},
|
||||
vertical: Boolean,
|
||||
centered: Boolean,
|
||||
value: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabs: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
findAndActivateTab(title) {
|
||||
const tabToActivate = this.tabs.find((t) => t.title === title)
|
||||
if (tabToActivate) {
|
||||
this.activateTab(tabToActivate)
|
||||
}
|
||||
},
|
||||
activateTab(tab) {
|
||||
if (this.handleClick) {
|
||||
this.handleClick(tab)
|
||||
}
|
||||
this.deactivateTabs()
|
||||
tab.active = true
|
||||
},
|
||||
deactivateTabs() {
|
||||
this.tabs.forEach((tab) => {
|
||||
tab.active = false
|
||||
})
|
||||
},
|
||||
addTab(tab) {
|
||||
const index = this.$slots.default.indexOf(tab.$vnode)
|
||||
if (!this.activeTab && index === 0) {
|
||||
tab.active = true
|
||||
}
|
||||
if (this.activeTab === tab.name) {
|
||||
tab.active = true
|
||||
}
|
||||
this.tabs.splice(index, 0, tab)
|
||||
},
|
||||
removeTab(tab) {
|
||||
const tabs = this.tabs
|
||||
const index = tabs.indexOf(tab)
|
||||
if (index > -1) {
|
||||
tabs.splice(index, 1)
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (this.value) {
|
||||
this.findAndActivateTab(this.value)
|
||||
}
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.findAndActivateTab(newVal)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@ -1,50 +1,8 @@
|
||||
import BaseCheckbox from './Inputs/BaseCheckbox.vue'
|
||||
import BaseAlert from './BaseAlert.vue'
|
||||
import BaseRadio from './Inputs/BaseRadio.vue'
|
||||
import BaseInput from './Inputs/BaseInput.vue'
|
||||
import Badge from './Badge'
|
||||
import BaseProgress from './BaseProgress.vue'
|
||||
import BaseButton from './BaseButton.vue'
|
||||
|
||||
import BaseDropdown from './BaseDropdown.vue'
|
||||
import BaseTable from './BaseTable.vue'
|
||||
|
||||
import Card from './Cards/Card.vue'
|
||||
import StatsCard from './Cards/StatsCard.vue'
|
||||
import BaseNav from './Navbar/BaseNav'
|
||||
import NavbarToggleButton from './Navbar/NavbarToggleButton'
|
||||
|
||||
import TabPane from './Tabs/Tab.vue'
|
||||
import Tabs from './Tabs/Tabs.vue'
|
||||
import Collapse from './Collapse/Collapse.vue'
|
||||
import CollapseItem from './Collapse/CollapseItem.vue'
|
||||
import Modal from './Modal.vue'
|
||||
import BaseSlider from './BaseSlider.vue'
|
||||
|
||||
import BasePagination from './BasePagination.vue'
|
||||
|
||||
import SidebarPlugin from './SidebarPlugin'
|
||||
|
||||
export {
|
||||
BaseCheckbox,
|
||||
Badge,
|
||||
BaseAlert,
|
||||
BaseProgress,
|
||||
BasePagination,
|
||||
BaseRadio,
|
||||
BaseInput,
|
||||
Card,
|
||||
StatsCard,
|
||||
BaseTable,
|
||||
BaseDropdown,
|
||||
SidebarPlugin,
|
||||
BaseNav,
|
||||
NavbarToggleButton,
|
||||
TabPane,
|
||||
Tabs,
|
||||
Modal,
|
||||
BaseSlider,
|
||||
BaseButton,
|
||||
Collapse,
|
||||
CollapseItem,
|
||||
}
|
||||
export { SidebarPlugin, NavbarToggleButton, Collapse, CollapseItem }
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
"email":"E-Mail",
|
||||
"email_repeat":"eMail wiederholen",
|
||||
"password":"Passwort",
|
||||
"password_repeat":"Passwort wiederholen",
|
||||
"passwordRepeat":"Passwort wiederholen",
|
||||
"password_old":"altes Passwort",
|
||||
"password_new":"neues Passwort",
|
||||
"password_new_repeat":"neues Passwort wiederholen",
|
||||
@ -106,7 +106,6 @@
|
||||
},
|
||||
"profil": {
|
||||
"activity": {
|
||||
"chart":"Gemeinschaftsstunden Chart",
|
||||
"new":"Neue Gemeinschaftsstunden eintragen",
|
||||
"list":"Meine Gemeinschaftsstunden Liste"
|
||||
},
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
"email":"Email",
|
||||
"email_repeat":"Repeat Email",
|
||||
"password":"Password",
|
||||
"password_repeat":"Repeat password",
|
||||
"passwordRepeat":"Repeat password",
|
||||
"password_old":"Old password",
|
||||
"password_new":"New password",
|
||||
"password_new_repeat":"Repeat new password",
|
||||
@ -107,7 +107,6 @@
|
||||
"profil": {
|
||||
"transactions":"transactions",
|
||||
"activity": {
|
||||
"chart":"Community Hours Chart",
|
||||
"new":"Register new community hours",
|
||||
"list":"My Community Hours List"
|
||||
},
|
||||
|
||||
@ -1,14 +1,7 @@
|
||||
// Polyfills for js features used in the Dashboard but not supported in some browsers (mainly IE)
|
||||
import '@/polyfills'
|
||||
// Notifications plugin. Used on Notifications page
|
||||
import Notifications from '@/components/NotificationPlugin'
|
||||
// Validation plugin used to validate forms
|
||||
import { configure, extend } from 'vee-validate'
|
||||
// A plugin file where you could register global components used across the app
|
||||
import GlobalComponents from './globalComponents'
|
||||
// A plugin file where you could register global directives
|
||||
import GlobalDirectives from './globalDirectives'
|
||||
// Sidebar on the right. Used as a local plugin in DashboardLayout.vue
|
||||
import SideBar from '@/components/SidebarPlugin'
|
||||
|
||||
import PortalVue from 'portal-vue'
|
||||
@ -27,16 +20,12 @@ import { messages } from 'vee-validate/dist/locale/en.json'
|
||||
import VueQrcodeReader from 'vue-qrcode-reader'
|
||||
import VueQrcode from 'vue-qrcode'
|
||||
|
||||
import VueFlatPickr from 'vue-flatpickr-component'
|
||||
|
||||
import VueGoodTablePlugin from 'vue-good-table'
|
||||
// import the styles
|
||||
import 'vue-good-table/dist/vue-good-table.css'
|
||||
import FlatPickr from 'vue-flatpickr-component'
|
||||
import 'flatpickr/dist/flatpickr.css'
|
||||
|
||||
import VueMoment from 'vue-moment'
|
||||
|
||||
import Loading from 'vue-loading-overlay'
|
||||
// import the styles
|
||||
import 'vue-loading-overlay/dist/vue-loading.css'
|
||||
|
||||
Object.keys(rules).forEach((rule) => {
|
||||
@ -45,21 +34,20 @@ Object.keys(rules).forEach((rule) => {
|
||||
message: messages[rule], // assign message
|
||||
})
|
||||
})
|
||||
|
||||
export default {
|
||||
install(Vue) {
|
||||
Vue.use(GlobalComponents)
|
||||
Vue.use(GlobalDirectives)
|
||||
Vue.use(SideBar)
|
||||
Vue.use(Notifications)
|
||||
Vue.use(PortalVue)
|
||||
Vue.use(BootstrapVue)
|
||||
Vue.use(IconsPlugin)
|
||||
Vue.use(VueBootstrapToasts)
|
||||
Vue.use(VueGoodTablePlugin)
|
||||
Vue.use(VueMoment)
|
||||
Vue.use(VueQrcodeReader)
|
||||
Vue.use(VueQrcode)
|
||||
Vue.use(VueFlatPickr)
|
||||
Vue.use(FlatPickr)
|
||||
Vue.use(Loading)
|
||||
configure({
|
||||
classes: {
|
||||
|
||||
@ -1,38 +1,7 @@
|
||||
import BaseInput from '@/components/Inputs/BaseInput.vue'
|
||||
import BaseDropdown from '@/components/BaseDropdown.vue'
|
||||
import Card from '@/components/Cards/Card.vue'
|
||||
import Modal from '@/components/Modal.vue'
|
||||
import StatsCard from '@/components/Cards/StatsCard.vue'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import Badge from '@/components/Badge.vue'
|
||||
import BaseCheckbox from '@/components/Inputs/BaseCheckbox.vue'
|
||||
import BaseRadio from '@/components/Inputs/BaseRadio'
|
||||
import BaseProgress from '@/components/BaseProgress'
|
||||
import BasePagination from '@/components/BasePagination'
|
||||
import BaseAlert from '@/components/BaseAlert'
|
||||
import BaseNav from '@/components/Navbar/BaseNav'
|
||||
import BaseHeader from '@/components/BaseHeader'
|
||||
import { ValidationProvider, ValidationObserver } from 'vee-validate'
|
||||
/**
|
||||
* You can register global components here and use them as a plugin in your main Vue instance
|
||||
*/
|
||||
|
||||
const GlobalComponents = {
|
||||
install(Vue) {
|
||||
Vue.component(Badge.name, Badge)
|
||||
Vue.component(BaseAlert.name, BaseAlert)
|
||||
Vue.component(BaseButton.name, BaseButton)
|
||||
Vue.component(BaseCheckbox.name, BaseCheckbox)
|
||||
Vue.component(BaseHeader.name, BaseHeader)
|
||||
Vue.component(BaseInput.name, BaseInput)
|
||||
Vue.component(BaseDropdown.name, BaseDropdown)
|
||||
Vue.component(BaseNav.name, BaseNav)
|
||||
Vue.component(BasePagination.name, BasePagination)
|
||||
Vue.component(BaseProgress.name, BaseProgress)
|
||||
Vue.component(BaseRadio.name, BaseRadio)
|
||||
Vue.component(Card.name, Card)
|
||||
Vue.component(Modal.name, Modal)
|
||||
Vue.component(StatsCard.name, StatsCard)
|
||||
Vue.component('validation-provider', ValidationProvider)
|
||||
Vue.component('validation-observer', ValidationObserver)
|
||||
},
|
||||
|
||||
@ -58,7 +58,6 @@ export const store = new Vuex.Store({
|
||||
sessionId: null,
|
||||
email: '',
|
||||
language: null,
|
||||
modals: false,
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
username: '',
|
||||
|
||||
@ -24,7 +24,25 @@
|
||||
</template>
|
||||
</side-bar>
|
||||
<div class="main-content">
|
||||
<dashboard-navbar :type="$route.meta.navbarType"></dashboard-navbar>
|
||||
<div class="d-none d-md-block">
|
||||
<b-navbar>
|
||||
<b-navbar-nav class="ml-auto">
|
||||
<b-nav-item>
|
||||
<b-media no-body class="align-items-center">
|
||||
<span class="pb-2 text-lg font-weight-bold">
|
||||
{{ $store.state.email }}
|
||||
</span>
|
||||
<b-media-body class="ml-2">
|
||||
<span class="avatar">
|
||||
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
|
||||
</span>
|
||||
</b-media-body>
|
||||
</b-media>
|
||||
</b-nav-item>
|
||||
</b-navbar-nav>
|
||||
</b-navbar>
|
||||
</div>
|
||||
|
||||
<div @click="$sidebar.displaySidebar(false)">
|
||||
<fade-transition :duration="200" origin="center top" mode="out-in">
|
||||
<!-- your content here -->
|
||||
@ -48,11 +66,11 @@ import PerfectScrollbar from 'perfect-scrollbar'
|
||||
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
||||
import loginAPI from '../../apis/loginAPI'
|
||||
|
||||
import DashboardNavbar from './DashboardNavbar.vue'
|
||||
import ContentFooter from './ContentFooter.vue'
|
||||
// import DashboardContent from './Content.vue';
|
||||
import { FadeTransition } from 'vue2-transitions'
|
||||
import communityAPI from '../../apis/communityAPI'
|
||||
import VueQrcode from 'vue-qrcode'
|
||||
|
||||
function hasElement(className) {
|
||||
return document.getElementsByClassName(className).length > 0
|
||||
@ -72,9 +90,8 @@ function initScrollbar(className) {
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DashboardNavbar,
|
||||
ContentFooter,
|
||||
// DashboardContent,
|
||||
VueQrcode,
|
||||
FadeTransition,
|
||||
},
|
||||
data() {
|
||||
@ -132,4 +149,9 @@ export default {
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
<style lang="scss">
|
||||
.xxx {
|
||||
position: relative;
|
||||
right: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
<template>
|
||||
<base-nav
|
||||
container-classes="container-fluid"
|
||||
class="navbar-expand"
|
||||
:class="{ 'navbar-dark': type === 'default' }"
|
||||
>
|
||||
<!-- Navbar links -->
|
||||
<b-navbar-nav class="align-items-center ml-md-auto">
|
||||
<!-- This item dont have <b-nav-item> because item have data-action/data-target on tag <a>, wich we cant add -->
|
||||
<li class="nav-item d-sm-none"></li>
|
||||
</b-navbar-nav>
|
||||
<b-navbar-nav class="align-items-center ml-auto ml-md-0">
|
||||
<div class="pr-1" slot="title-container ">
|
||||
<b-media no-body class="align-items-center">
|
||||
<span class="pb-2 text-lg font-weight-bold">
|
||||
{{ $store.state.email }}
|
||||
</span>
|
||||
<b-media-body class="ml-2">
|
||||
<span class="avatar">
|
||||
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
|
||||
</span>
|
||||
</b-media-body>
|
||||
</b-media>
|
||||
</div>
|
||||
</b-navbar-nav>
|
||||
</base-nav>
|
||||
</template>
|
||||
<script>
|
||||
import { BaseNav } from '@/components'
|
||||
import VueQrcode from 'vue-qrcode'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BaseNav,
|
||||
VueQrcode,
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@ -4,32 +4,32 @@
|
||||
<b-tab :title="names.thisMonth" active>
|
||||
<b-row>
|
||||
<b-col lg="3">
|
||||
<base-input :label="$t('communitys.form.hours')">
|
||||
<b-input :label="$t('communitys.form.hours')">
|
||||
<b-form-input
|
||||
type="number"
|
||||
size="lg"
|
||||
placeholder="23"
|
||||
style="font-size: xx-large; padding-left: 5px"
|
||||
/>
|
||||
</base-input>
|
||||
<base-input :label="$t('communitys.form.date_period')">
|
||||
</b-input>
|
||||
<b-input :label="$t('communitys.form.date_period')">
|
||||
<flat-pickr
|
||||
class="form-control"
|
||||
v-model="date"
|
||||
:config="config"
|
||||
style="font-size: 0.5em; padding-left: 5px"
|
||||
></flat-pickr>
|
||||
</base-input>
|
||||
</b-input>
|
||||
</b-col>
|
||||
<b-col lg="9">
|
||||
<base-input :label="$t('communitys.form.hours_report')">
|
||||
<b-input :label="$t('communitys.form.hours_report')">
|
||||
<textarea
|
||||
class="form-control"
|
||||
rows="5"
|
||||
@focus="textFocus"
|
||||
style="font-size: x-large; padding-left: 20px"
|
||||
></textarea>
|
||||
</base-input>
|
||||
</b-input>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-row>
|
||||
@ -57,12 +57,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
import 'flatpickr/dist/flatpickr.css'
|
||||
|
||||
export default {
|
||||
name: 'GDDAddWork2',
|
||||
components: { flatPickr },
|
||||
data() {
|
||||
return {
|
||||
date: null,
|
||||
@ -147,32 +143,32 @@ export default {
|
||||
newWorkForm() {
|
||||
this.formular = `
|
||||
<b-col lg="3">
|
||||
<base-input label="Stunden">
|
||||
<b-input label="Stunden">
|
||||
<b-form-input
|
||||
type="number"
|
||||
size="lg"
|
||||
placeholder="0"
|
||||
style="font-size: xx-large; padding-left: 20px"
|
||||
/>
|
||||
</base-input>
|
||||
<base-input label="Datum / Zeitraum">
|
||||
</b-input>
|
||||
<b-input label="Datum / Zeitraum">
|
||||
<flat-pickr
|
||||
class="form-control"
|
||||
v-model="date"
|
||||
:config="config"
|
||||
style="font-size: xx-large; padding-left: 20px"
|
||||
></flat-pickr>
|
||||
</base-input>
|
||||
</b-input>
|
||||
</b-col>
|
||||
<b-col lg="9">
|
||||
<base-input label="Arbeitsreport">
|
||||
<b-input label="Arbeitsreport">
|
||||
<textarea
|
||||
class="form-control"
|
||||
rows="5"
|
||||
@focus="textFocus"
|
||||
style="font-size: x-large; padding-left: 20px"
|
||||
></textarea>
|
||||
</base-input>
|
||||
</b-input>
|
||||
</b-col>
|
||||
`
|
||||
|
||||
|
||||
@ -17,19 +17,31 @@
|
||||
<b-col lg="6" md="8">
|
||||
<b-card no-body class="border-0" style="background-color: #ebebeba3 !important">
|
||||
<b-card-body class="p-4">
|
||||
<validation-observer v-slot="{ handleSubmit }" ref="formValidator">
|
||||
<validation-observer ref="observer" v-slot="{ handleSubmit }">
|
||||
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)">
|
||||
<base-input
|
||||
alternative
|
||||
class="mb-3"
|
||||
prepend-icon="ni ni-email-83"
|
||||
:placeholder="$t('form.email')"
|
||||
<validation-provider
|
||||
name="Email"
|
||||
:rules="{ required: true, email: true }"
|
||||
v-model="form.email"
|
||||
></base-input>
|
||||
v-slot="validationContext"
|
||||
>
|
||||
<b-form-group class="mb-3" label="Email" label-for="input-reset-pwd">
|
||||
<b-form-input
|
||||
id="input-reset-pwd"
|
||||
name="input-reset-pwd"
|
||||
v-model="form.email"
|
||||
placeholder="Email"
|
||||
:state="getValidationState(validationContext)"
|
||||
aria-describedby="reset-pwd--live-feedback"
|
||||
></b-form-input>
|
||||
|
||||
<b-form-invalid-feedback id="reset-pwd--live-feedback">
|
||||
{{ validationContext.errors[0] }}
|
||||
</b-form-invalid-feedback>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
|
||||
<div class="text-center">
|
||||
<b-button type="submit" outline variant="secondary" class="mt-4">
|
||||
<b-button type="submit" variant="primary">
|
||||
{{ $t('site.password.reset_now') }}
|
||||
</b-button>
|
||||
</div>
|
||||
@ -60,6 +72,9 @@ export default {
|
||||
},
|
||||
created() {},
|
||||
methods: {
|
||||
getValidationState({ dirty, validated, valid = null }) {
|
||||
return dirty || validated ? valid : null
|
||||
},
|
||||
async onSubmit() {
|
||||
await loginAPI.sendEmail(this.form.email)
|
||||
// always give success to avoid email spying
|
||||
|
||||
@ -22,34 +22,63 @@
|
||||
<div class="text-center text-muted mb-4">
|
||||
<small>{{ $t('login') }}</small>
|
||||
</div>
|
||||
<validation-observer v-slot="{ handleSubmit }" ref="formValidator">
|
||||
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)">
|
||||
<base-input
|
||||
alternative
|
||||
class="mb-3"
|
||||
|
||||
<validation-observer ref="observer" v-slot="{ handleSubmit }">
|
||||
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
|
||||
<validation-provider
|
||||
name="Email"
|
||||
:rules="{ required: true, email: true }"
|
||||
prepend-icon="ni ni-email-83"
|
||||
placeholder="Email"
|
||||
v-model="model.email"
|
||||
></base-input>
|
||||
v-slot="validationContext"
|
||||
>
|
||||
<b-form-group class="mb-3" label="Email" label-for="login-email">
|
||||
<b-form-input
|
||||
id="login-email"
|
||||
name="example-input-1"
|
||||
v-model="form.email"
|
||||
placeholder="Email"
|
||||
:state="getValidationState(validationContext)"
|
||||
aria-describedby="login-email-live-feedback"
|
||||
></b-form-input>
|
||||
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
class="mb-0"
|
||||
v-model="model.password"
|
||||
name="Password"
|
||||
:type="passwordVisible ? 'text' : 'password'"
|
||||
prepend-icon="ni ni-lock-circle-open"
|
||||
:placeholder="$t('form.password')"
|
||||
></b-form-input>
|
||||
<b-form-invalid-feedback id="login-email-live-feedback">
|
||||
{{ validationContext.errors[0] }}
|
||||
</b-form-invalid-feedback>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="togglePasswordVisibility">
|
||||
<b-icon :icon="passwordVisible ? 'eye' : 'eye-slash'" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
<validation-provider
|
||||
:name="$t('form.password')"
|
||||
:rules="{ required: true }"
|
||||
v-slot="validationContext"
|
||||
>
|
||||
<b-form-group
|
||||
class="mb-5"
|
||||
id="example-input-group-1"
|
||||
:label="$t('form.password')"
|
||||
label-for="example-input-1"
|
||||
>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
id="input-pwd"
|
||||
name="input-pwd"
|
||||
v-model="form.password"
|
||||
:placeholder="$t('form.password')"
|
||||
:type="passwordVisible ? 'text' : 'password'"
|
||||
:state="getValidationState(validationContext)"
|
||||
aria-describedby="input-2-live-feedback"
|
||||
></b-form-input>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="togglePasswordVisibility">
|
||||
<b-icon :icon="passwordVisible ? 'eye' : 'eye-slash'" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
<b-form-invalid-feedback id="input-2-live-feedback">
|
||||
{{ validationContext.errors[0] }}
|
||||
</b-form-invalid-feedback>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
|
||||
<b-alert v-show="loginfail" show dismissible variant="warning">
|
||||
<span class="alert-text bv-example-row">
|
||||
@ -62,12 +91,8 @@
|
||||
</b-row>
|
||||
</span>
|
||||
</b-alert>
|
||||
|
||||
<!-- <b-form-checkbox v-model="model.rememberMe">{{ $t('site.login.remember')}}</b-form-checkbox> -->
|
||||
<div class="text-center" ref="submitButton">
|
||||
<base-button type="secondary" native-type="submit" class="my-4">
|
||||
{{ $t('site.login.signin') }}
|
||||
</base-button>
|
||||
<div class="text-center">
|
||||
<b-button type="submit" variant="primary">{{ $t('login') }}</b-button>
|
||||
</div>
|
||||
</b-form>
|
||||
</validation-observer>
|
||||
@ -98,7 +123,7 @@ export default {
|
||||
name: 'login',
|
||||
data() {
|
||||
return {
|
||||
model: {
|
||||
form: {
|
||||
email: '',
|
||||
password: '',
|
||||
// rememberMe: false
|
||||
@ -109,6 +134,10 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getValidationState({ dirty, validated, valid = null }) {
|
||||
return dirty || validated ? valid : null
|
||||
},
|
||||
|
||||
togglePasswordVisibility() {
|
||||
this.passwordVisible = !this.passwordVisible
|
||||
},
|
||||
@ -118,7 +147,7 @@ export default {
|
||||
const loader = this.$loading.show({
|
||||
container: this.$refs.submitButton,
|
||||
})
|
||||
const result = await loginAPI.login(this.model.email, this.model.password)
|
||||
const result = await loginAPI.login(this.form.email, this.form.password)
|
||||
if (result.success) {
|
||||
this.$store.dispatch('login', {
|
||||
sessionId: result.result.data.session_id,
|
||||
|
||||
@ -53,16 +53,27 @@ describe('Register', () => {
|
||||
expect(wrapper.find('form').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has 3 text input fields', () => {
|
||||
expect(wrapper.findAll('input[type="text"]').length).toBe(3)
|
||||
it('has firstname input fields', () => {
|
||||
expect(wrapper.find('#registerFirstname').exists()).toBeTruthy()
|
||||
})
|
||||
it('has lastname input fields', () => {
|
||||
expect(wrapper.find('#registerLastname').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has 2 password input fields', () => {
|
||||
expect(wrapper.findAll('input[type="password"]').length).toBe(2)
|
||||
it('has email input fields', () => {
|
||||
expect(wrapper.find('#registerEmail').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has password input fields', () => {
|
||||
expect(wrapper.find('#registerPassword').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has password repeat input fields', () => {
|
||||
expect(wrapper.find('#registerPasswordRepeat').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has 1 checkbox input fields', () => {
|
||||
expect(wrapper.findAll('input[type="checkbox"]').length).toBe(1)
|
||||
expect(wrapper.find('#registerCheckbox').exists()).toBeTruthy()
|
||||
})
|
||||
|
||||
it('has no submit button when not completely filled', () => {
|
||||
@ -70,9 +81,9 @@ describe('Register', () => {
|
||||
})
|
||||
|
||||
it('shows a warning when no valid Email is entered', async () => {
|
||||
wrapper.findAll('input[type="text"]').at(2).setValue('no_valid@Email')
|
||||
wrapper.find('#registerEmail').setValue('no_valid@Email')
|
||||
await flushPromises()
|
||||
await expect(wrapper.find('.invalid-feedback').text()).toEqual(
|
||||
await expect(wrapper.find('#registerEmailLiveFeedback').text()).toEqual(
|
||||
'The Email field must be a valid email',
|
||||
)
|
||||
})
|
||||
|
||||
@ -23,65 +23,137 @@
|
||||
<div class="text-center text-muted mb-4">
|
||||
<small>{{ $t('signup') }}</small>
|
||||
</div>
|
||||
<validation-observer v-slot="{ handleSubmit }" ref="formValidator">
|
||||
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)">
|
||||
<base-input
|
||||
:label="$t('form.firstname')"
|
||||
alternative
|
||||
class="mb-3"
|
||||
name="firstname"
|
||||
:rules="{ required: true, min: 3 }"
|
||||
v-model="model.firstname"
|
||||
></base-input>
|
||||
<base-input
|
||||
:label="$t('form.lastname')"
|
||||
alternative
|
||||
class="mb-3"
|
||||
name="lastname"
|
||||
:rules="{ required: true, min: 2 }"
|
||||
v-model="model.lastname"
|
||||
></base-input>
|
||||
|
||||
<base-input
|
||||
:label="$t('form.email')"
|
||||
alternative
|
||||
class="mb-3"
|
||||
<validation-observer ref="observer" v-slot="{ handleSubmit }">
|
||||
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)">
|
||||
<validation-provider
|
||||
:name="$t('form.firstname')"
|
||||
:rules="{ required: true, min: 3 }"
|
||||
v-slot="validationContext"
|
||||
>
|
||||
<b-form-group
|
||||
class="mb-3"
|
||||
:label="$t('form.firstname')"
|
||||
label-for="registerFirstname"
|
||||
>
|
||||
<b-form-input
|
||||
id="registerFirstname"
|
||||
:name="$t('form.firstname')"
|
||||
v-model="form.firstname"
|
||||
:placeholder="$t('form.firstname')"
|
||||
:state="getValidationState(validationContext)"
|
||||
aria-describedby="registerFirstnameLiveFeedback"
|
||||
></b-form-input>
|
||||
|
||||
<b-form-invalid-feedback id="registerFirstnameLiveFeedback">
|
||||
{{ validationContext.errors[0] }}
|
||||
</b-form-invalid-feedback>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
|
||||
<validation-provider
|
||||
:name="$t('form.lastname')"
|
||||
:rules="{ required: true, min: 2 }"
|
||||
v-slot="validationContext"
|
||||
>
|
||||
<b-form-group
|
||||
class="mb-3"
|
||||
:label="$t('form.lastname')"
|
||||
label-for="registerLastname"
|
||||
>
|
||||
<b-form-input
|
||||
id="registerLastname"
|
||||
:name="$t('form.lastname')"
|
||||
v-model="form.lastname"
|
||||
:placeholder="$t('form.lastname')"
|
||||
:state="getValidationState(validationContext)"
|
||||
aria-describedby="registerLastnameLiveFeedback"
|
||||
></b-form-input>
|
||||
|
||||
<b-form-invalid-feedback id="registerLastnameLiveFeedback">
|
||||
{{ validationContext.errors[0] }}
|
||||
</b-form-invalid-feedback>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
|
||||
<validation-provider
|
||||
name="Email"
|
||||
:rules="{ required: true, email: true }"
|
||||
v-model="model.email"
|
||||
></base-input>
|
||||
v-slot="validationContext"
|
||||
>
|
||||
<b-form-group class="mb-3" label="Email" label-for="registerEmail">
|
||||
<b-form-input
|
||||
id="registerEmail"
|
||||
name="Email"
|
||||
v-model="form.email"
|
||||
placeholder="Email"
|
||||
:state="getValidationState(validationContext)"
|
||||
aria-describedby="registerEmailLiveFeedback"
|
||||
></b-form-input>
|
||||
|
||||
<b-form-invalid-feedback id="registerEmailLiveFeedback">
|
||||
{{ validationContext.errors[0] }}
|
||||
</b-form-invalid-feedback>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
|
||||
<hr />
|
||||
<b-form-group :label="$t('form.password')">
|
||||
|
||||
<validation-provider
|
||||
:name="$t('form.password')"
|
||||
:rules="{ required: true }"
|
||||
v-slot="validationContext"
|
||||
>
|
||||
<b-form-group
|
||||
class="mb-5"
|
||||
:label="$t('form.password')"
|
||||
label-for="registerPassword"
|
||||
>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
id="registerPassword"
|
||||
:name="$t('form.password')"
|
||||
v-model="form.password"
|
||||
:placeholder="$t('form.password')"
|
||||
:type="passwordVisible ? 'text' : 'password'"
|
||||
:state="getValidationState(validationContext)"
|
||||
aria-describedby="registerPasswordLiveFeedback"
|
||||
></b-form-input>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="togglePasswordVisibility">
|
||||
<b-icon :icon="passwordVisible ? 'eye' : 'eye-slash'" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
<b-form-invalid-feedback id="registerPasswordLiveFeedback">
|
||||
{{ validationContext.errors[0] }}
|
||||
</b-form-invalid-feedback>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
|
||||
<b-form-group
|
||||
class="mb-5"
|
||||
:label="$t('form.passwordRepeat')"
|
||||
label-for="registerPasswordRepeat"
|
||||
>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
class="mb-0"
|
||||
v-model="password"
|
||||
name="password"
|
||||
:class="{ valid: passwordValidation.valid }"
|
||||
:type="passwordVisible ? 'text' : 'password'"
|
||||
prepend-icon="ni ni-lock-circle-open"
|
||||
:placeholder="$t('form.password')"
|
||||
id="registerPasswordRepeat"
|
||||
:name="$t('form.passwordRepeat')"
|
||||
v-model.lazy="form.passwordRepeat"
|
||||
:placeholder="$t('form.passwordRepeat')"
|
||||
:type="passwordVisibleRepeat ? 'text' : 'password'"
|
||||
></b-form-input>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="togglePasswordVisibility">
|
||||
<b-icon :icon="passwordVisible ? 'eye' : 'eye-slash'" />
|
||||
<b-button variant="outline-primary" @click="togglePasswordRepeatVisibility">
|
||||
<b-icon :icon="passwordVisibleRepeat ? 'eye' : 'eye-slash'" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
|
||||
<base-input
|
||||
:label="$t('form.password_repeat')"
|
||||
type="password"
|
||||
name="password-repeat"
|
||||
:placeholder="$t('form.password_repeat')"
|
||||
prepend-icon="ni ni-lock-circle-open"
|
||||
v-model.lazy="checkPassword"
|
||||
:class="{ valid: passwordValidation.valid }"
|
||||
/>
|
||||
|
||||
<transition name="hint" appear>
|
||||
<div v-if="passwordValidation.errors.length > 0 && !submitted" class="hints">
|
||||
<ul>
|
||||
@ -99,14 +171,13 @@
|
||||
</transition>
|
||||
<b-row class="my-4">
|
||||
<b-col cols="12">
|
||||
<base-input
|
||||
:rules="{ required: { allowFalse: false } }"
|
||||
name="Privacy Policy"
|
||||
<b-form-checkbox
|
||||
id="registerCheckbox"
|
||||
v-model="form.agree"
|
||||
:name="$t('site.signup.agree')"
|
||||
>
|
||||
<b-form-checkbox v-model="model.agree">
|
||||
<span class="text-muted" v-html="$t('site.signup.agree')"></span>
|
||||
</b-form-checkbox>
|
||||
</base-input>
|
||||
<span class="text-muted" v-html="$t('site.signup.agree')"></span>
|
||||
</b-form-checkbox>
|
||||
</b-col>
|
||||
</b-row>
|
||||
<b-alert
|
||||
@ -131,12 +202,13 @@
|
||||
passwordValidation.valid &&
|
||||
namesFilled &&
|
||||
emailFilled &&
|
||||
model.agree
|
||||
form.agree
|
||||
"
|
||||
>
|
||||
<b-button type="submit" variant="secondary" class="mt-4">
|
||||
{{ $t('signup') }}
|
||||
</b-button>
|
||||
<div class="text-center">
|
||||
<b-button class="ml-2" @click="resetForm()">{{ $t('form.reset') }}</b-button>
|
||||
<b-button type="submit" variant="primary">{{ $t('signup') }}</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</b-form>
|
||||
</validation-observer>
|
||||
@ -157,41 +229,61 @@ export default {
|
||||
name: 'register',
|
||||
data() {
|
||||
return {
|
||||
model: {
|
||||
form: {
|
||||
firstname: '',
|
||||
lastname: '',
|
||||
email: '',
|
||||
agree: false,
|
||||
password: '',
|
||||
passwordRepeat: '',
|
||||
},
|
||||
|
||||
password: '',
|
||||
checkPassword: '',
|
||||
passwordVisible: false,
|
||||
passwordVisibleRepeat: false,
|
||||
submitted: false,
|
||||
showError: false,
|
||||
messageError: '',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getValidationState({ dirty, validated, valid = null }) {
|
||||
return dirty || validated ? valid : null
|
||||
},
|
||||
resetForm() {
|
||||
this.form = {
|
||||
firstname: '',
|
||||
lastname: '',
|
||||
email: '',
|
||||
password: '',
|
||||
passwordRepeat: '',
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.$refs.observer.reset()
|
||||
})
|
||||
},
|
||||
togglePasswordVisibility() {
|
||||
this.passwordVisible = !this.passwordVisible
|
||||
},
|
||||
togglePasswordRepeatVisibility() {
|
||||
this.passwordVisibleRepeat = !this.passwordVisibleRepeat
|
||||
},
|
||||
async onSubmit() {
|
||||
const result = await loginAPI.create(
|
||||
this.model.email,
|
||||
this.model.firstname,
|
||||
this.model.lastname,
|
||||
this.password,
|
||||
this.form.email,
|
||||
this.form.firstname,
|
||||
this.form.lastname,
|
||||
this.form.password,
|
||||
)
|
||||
if (result.success) {
|
||||
this.$store.dispatch('login', {
|
||||
sessionId: result.result.data.session_id,
|
||||
email: this.model.email,
|
||||
email: this.form.email,
|
||||
})
|
||||
this.model.email = ''
|
||||
this.model.firstname = ''
|
||||
this.model.lastname = ''
|
||||
this.form.email = ''
|
||||
this.form.firstname = ''
|
||||
this.form.lastname = ''
|
||||
this.password = ''
|
||||
this.passwordVisibleRepeat = ''
|
||||
this.$router.push('/thx/register')
|
||||
} else {
|
||||
this.showError = true
|
||||
@ -201,29 +293,29 @@ export default {
|
||||
closeAlert() {
|
||||
this.showError = false
|
||||
this.messageError = ''
|
||||
this.model.email = ''
|
||||
this.model.firstname = ''
|
||||
this.model.lastname = ''
|
||||
this.password = ''
|
||||
this.form.email = ''
|
||||
this.form.firstname = ''
|
||||
this.form.lastname = ''
|
||||
this.form.password = ''
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
samePasswords() {
|
||||
return this.password === this.checkPassword
|
||||
return this.form.password === this.form.passwordRepeat
|
||||
},
|
||||
passwordsFilled() {
|
||||
return this.password !== '' && this.checkPassword !== ''
|
||||
return this.form.password !== '' && this.form.passwordRepeat !== ''
|
||||
},
|
||||
namesFilled() {
|
||||
return (
|
||||
this.model.firstname !== '' &&
|
||||
this.model.firstname.length > 2 &&
|
||||
this.model.lastname !== '' &&
|
||||
this.model.lastname.length > 1
|
||||
this.form.firstname !== '' &&
|
||||
this.form.firstname.length > 2 &&
|
||||
this.form.lastname !== '' &&
|
||||
this.form.lastname.length > 1
|
||||
)
|
||||
},
|
||||
emailFilled() {
|
||||
return this.model.email !== ''
|
||||
return this.form.email !== ''
|
||||
},
|
||||
rules() {
|
||||
return [
|
||||
@ -236,7 +328,7 @@ export default {
|
||||
passwordValidation() {
|
||||
const errors = []
|
||||
for (const condition of this.rules) {
|
||||
if (!condition.regex.test(this.password)) {
|
||||
if (!condition.regex.test(this.form.password)) {
|
||||
errors.push(condition.message)
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,38 +20,63 @@
|
||||
<b-col lg="6" md="8">
|
||||
<b-card no-body class="border-0" style="background-color: #ebebeba3 !important">
|
||||
<b-card-body class="p-4">
|
||||
<validation-observer v-slot="{ handleSubmit }" ref="formValidator">
|
||||
<validation-observer ref="observer" v-slot="{ handleSubmit }">
|
||||
<b-form role="form" @submit.prevent="handleSubmit(onSubmit)">
|
||||
<b-form-group :label="$t('form.password')">
|
||||
<validation-provider
|
||||
:name="$t('form.password')"
|
||||
:rules="{ required: true }"
|
||||
v-slot="validationContext"
|
||||
>
|
||||
<b-form-group
|
||||
class="mb-5"
|
||||
:label="$t('form.password')"
|
||||
label-for="resetPassword"
|
||||
>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
id="resetPassword"
|
||||
:name="$t('form.password')"
|
||||
v-model="form.password"
|
||||
:placeholder="$t('form.password')"
|
||||
:type="passwordVisible ? 'text' : 'password'"
|
||||
:state="getValidationState(validationContext)"
|
||||
aria-describedby="resetPasswordLiveFeedback"
|
||||
></b-form-input>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="togglePasswordVisibility">
|
||||
<b-icon :icon="passwordVisible ? 'eye' : 'eye-slash'" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
<b-form-invalid-feedback id="resetPasswordLiveFeedback">
|
||||
{{ validationContext.errors[0] }}
|
||||
</b-form-invalid-feedback>
|
||||
</b-form-group>
|
||||
</validation-provider>
|
||||
|
||||
<b-form-group
|
||||
class="mb-5"
|
||||
:label="$t('form.passwordRepeat')"
|
||||
label-for="resetPasswordRepeat"
|
||||
>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
class="mb-0"
|
||||
v-model="password"
|
||||
name="password"
|
||||
:class="{ valid: passwordValidation.valid }"
|
||||
:type="passwordVisible ? 'text' : 'password'"
|
||||
prepend-icon="ni ni-lock-circle-open"
|
||||
:placeholder="$t('form.password')"
|
||||
id="resetPasswordRepeat"
|
||||
:name="$t('form.passwordRepeat')"
|
||||
v-model.lazy="form.passwordRepeat"
|
||||
:placeholder="$t('form.passwordRepeat')"
|
||||
:type="passwordVisibleRepeat ? 'text' : 'password'"
|
||||
></b-form-input>
|
||||
|
||||
<b-input-group-append>
|
||||
<b-button variant="outline-primary" @click="togglePasswordVisibility">
|
||||
<b-icon :icon="passwordVisible ? 'eye' : 'eye-slash'" />
|
||||
<b-button variant="outline-primary" @click="togglePasswordRepeatVisibility">
|
||||
<b-icon :icon="passwordVisibleRepeat ? 'eye' : 'eye-slash'" />
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
|
||||
<base-input
|
||||
:label="$t('form.password_repeat')"
|
||||
type="password"
|
||||
name="password-repeat"
|
||||
:placeholder="$t('form.password_repeat')"
|
||||
prepend-icon="ni ni-lock-circle-open"
|
||||
v-model.lazy="checkPassword"
|
||||
:class="{ valid: passwordValidation.valid }"
|
||||
/>
|
||||
|
||||
<transition name="hint" appear>
|
||||
<div v-if="passwordValidation.errors.length > 0 && !submitted" class="hints">
|
||||
<ul>
|
||||
@ -90,8 +115,11 @@ export default {
|
||||
name: 'reset',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
password: '',
|
||||
passwordRepeat: '',
|
||||
},
|
||||
password: '',
|
||||
checkPassword: '',
|
||||
passwordVisible: false,
|
||||
submitted: false,
|
||||
authenticated: false,
|
||||
@ -100,13 +128,16 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getValidationState({ dirty, validated, valid = null }) {
|
||||
return dirty || validated ? valid : null
|
||||
},
|
||||
togglePasswordVisibility() {
|
||||
this.passwordVisible = !this.passwordVisible
|
||||
},
|
||||
async onSubmit() {
|
||||
const result = await loginAPI.changePassword(this.sessionId, this.email, this.password)
|
||||
const result = await loginAPI.changePassword(this.sessionId, this.email, this.form.password)
|
||||
if (result.success) {
|
||||
this.password = ''
|
||||
this.form.password = ''
|
||||
/*
|
||||
this.$store.dispatch('login', {
|
||||
sessionId: result.result.data.session_id,
|
||||
@ -132,10 +163,10 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
samePasswords() {
|
||||
return this.password === this.checkPassword
|
||||
return this.form.password === this.form.passwordRepeat
|
||||
},
|
||||
passwordsFilled() {
|
||||
return this.password !== '' && this.checkPassword !== ''
|
||||
return this.form.password !== '' && this.form.passwordRepeat !== ''
|
||||
},
|
||||
rules() {
|
||||
return [
|
||||
@ -148,7 +179,7 @@ export default {
|
||||
passwordValidation() {
|
||||
const errors = []
|
||||
for (const condition of this.rules) {
|
||||
if (!condition.regex.test(this.password)) {
|
||||
if (!condition.regex.test(this.form.password)) {
|
||||
errors.push(condition.message)
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,16 +5,10 @@ import { ValidationProvider, ValidationObserver, extend } from 'vee-validate'
|
||||
import * as rules from 'vee-validate/dist/rules'
|
||||
|
||||
import { messages } from 'vee-validate/dist/locale/en.json'
|
||||
import BaseInput from '@/components/Inputs/BaseInput.vue'
|
||||
import BaseButton from '@/components/BaseButton.vue'
|
||||
import RegeneratorRuntime from 'regenerator-runtime'
|
||||
import Notifications from '@/components/NotificationPlugin'
|
||||
import SideBar from '@/components/SidebarPlugin'
|
||||
import VueRouter from 'vue-router'
|
||||
import BaseDropdown from '@/components/BaseDropdown.vue'
|
||||
import VueQrcode from 'vue-qrcode'
|
||||
import BaseHeader from '@/components/BaseHeader'
|
||||
import StatsCard from '@/components/Cards/StatsCard.vue'
|
||||
|
||||
import VueMoment from 'vue-moment'
|
||||
|
||||
@ -34,18 +28,11 @@ global.localVue.use(BootstrapVue)
|
||||
global.localVue.use(Vuex)
|
||||
global.localVue.use(IconsPlugin)
|
||||
global.localVue.use(RegeneratorRuntime)
|
||||
global.localVue.use(Notifications)
|
||||
global.localVue.use(SideBar)
|
||||
global.localVue.use(VueRouter)
|
||||
global.localVue.use(VueQrcode)
|
||||
global.localVue.use(VueMoment)
|
||||
global.localVue.component(BaseInput.name, BaseInput)
|
||||
global.localVue.component('validation-provider', ValidationProvider)
|
||||
global.localVue.component('validation-observer', ValidationObserver)
|
||||
global.localVue.component(BaseButton.name, BaseButton)
|
||||
global.localVue.component(BaseDropdown.name, BaseDropdown)
|
||||
global.localVue.component(BaseHeader.name, BaseHeader)
|
||||
global.localVue.component(StatsCard.name, StatsCard)
|
||||
|
||||
global.localVue.directive('click-outside', clickOutside)
|
||||
global.localVue.directive('focus', focus)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user