parent
a1bcb12f20
commit
053bcad80a
@ -0,0 +1,78 @@ |
|||||||
|
<template> |
||||||
|
<v-app id="inspire"> |
||||||
|
<snackbar></snackbar> |
||||||
|
<v-navigation-drawer v-model="drawer" app> |
||||||
|
<v-list dense> |
||||||
|
<v-list-item> |
||||||
|
<v-list-item-action> |
||||||
|
<v-icon>mdi-home</v-icon> |
||||||
|
</v-list-item-action> |
||||||
|
<v-list-item-content> |
||||||
|
<v-list-item-title>Home</v-list-item-title> |
||||||
|
</v-list-item-content> |
||||||
|
</v-list-item> |
||||||
|
<v-list-item> |
||||||
|
<v-list-item-action> |
||||||
|
<v-icon>mdi-book</v-icon> |
||||||
|
</v-list-item-action> |
||||||
|
<v-list-item-content> |
||||||
|
<v-list-item-title> |
||||||
|
<router-link :to="{ name: 'CourseList' }">Courses</router-link> |
||||||
|
</v-list-item-title> |
||||||
|
</v-list-item-content> |
||||||
|
</v-list-item> |
||||||
|
<v-list-item> |
||||||
|
<v-list-item-action> |
||||||
|
<v-icon>mdi-book</v-icon> |
||||||
|
</v-list-item-action> |
||||||
|
<v-list-item-content> |
||||||
|
<v-list-item-title> |
||||||
|
<router-link :to="{ name: 'CourseCategoryList' }">Courses category</router-link> |
||||||
|
</v-list-item-title> |
||||||
|
</v-list-item-content> |
||||||
|
</v-list-item> |
||||||
|
|
||||||
|
<v-list-item> |
||||||
|
<v-list-item-action> |
||||||
|
<v-icon>mdi-comment-quote</v-icon> |
||||||
|
</v-list-item-action> |
||||||
|
<v-list-item-content> |
||||||
|
<v-list-item-title> |
||||||
|
<router-link :to="{ name: 'ReviewList' }">Reviews</router-link> |
||||||
|
</v-list-item-title> |
||||||
|
</v-list-item-content> |
||||||
|
</v-list-item> |
||||||
|
</v-list> |
||||||
|
</v-navigation-drawer> |
||||||
|
<v-app-bar app color="indigo" dark> |
||||||
|
<v-app-bar-nav-icon @click.stop="drawer = !drawer"></v-app-bar-nav-icon> |
||||||
|
<v-toolbar-title>Application</v-toolbar-title> |
||||||
|
</v-app-bar> |
||||||
|
|
||||||
|
<v-content> |
||||||
|
<Breadcrumb layout-class="pl-3 py-3" /> |
||||||
|
<router-view></router-view> |
||||||
|
</v-content> |
||||||
|
<v-footer color="indigo" app> |
||||||
|
<span class="white--text">© 2019</span> |
||||||
|
</v-footer> |
||||||
|
</v-app> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import Breadcrumb from './components/Breadcrumb'; |
||||||
|
import Snackbar from './components/Snackbar'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: "App", |
||||||
|
components: { |
||||||
|
Breadcrumb, |
||||||
|
Snackbar |
||||||
|
}, |
||||||
|
data: () => ({ |
||||||
|
drawer: null |
||||||
|
}), |
||||||
|
beforeMount() { |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
export const VueConfig = { |
||||||
|
delimiters: ['[[', ']]'] |
||||||
|
}; |
||||||
@ -0,0 +1,45 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<v-row justify="space-around"> |
||||||
|
<v-icon v-if="handleShow" small class="mr-2" @click="handleShow">mdi-eye</v-icon> |
||||||
|
<v-icon v-if="handleEdit" small class="mr-2" @click="handleEdit">mdi-pencil</v-icon> |
||||||
|
<v-icon v-if="handleDelete" small @click="confirmDelete = true">mdi-delete</v-icon> |
||||||
|
</v-row> |
||||||
|
<ConfirmDelete |
||||||
|
v-if="handleDelete" |
||||||
|
:visible="confirmDelete" |
||||||
|
:handle-delete="handleDelete" |
||||||
|
@close="confirmDelete = false" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import ConfirmDelete from './ConfirmDelete'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'ActionCell', |
||||||
|
components: { |
||||||
|
ConfirmDelete |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
confirmDelete: false |
||||||
|
}; |
||||||
|
}, |
||||||
|
props: { |
||||||
|
handleShow: { |
||||||
|
type: Function, |
||||||
|
required: false |
||||||
|
}, |
||||||
|
handleEdit: { |
||||||
|
type: Function, |
||||||
|
required: false |
||||||
|
}, |
||||||
|
handleDelete: { |
||||||
|
type: Function, |
||||||
|
required: false |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<v-breadcrumbs :items="items" divider="/" :class="layoutClass" /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
name: 'Breadcrumb', |
||||||
|
props: ['layoutClass'], |
||||||
|
data() { |
||||||
|
return {}; |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
items() { |
||||||
|
const { path, matched } = this.$route; |
||||||
|
const items = [ |
||||||
|
{ |
||||||
|
text: 'Home', |
||||||
|
href: '/' |
||||||
|
} |
||||||
|
]; |
||||||
|
const lastItem = matched[matched.length - 1]; |
||||||
|
for (let i = 0, len = matched.length; i < len; i += 1) { |
||||||
|
const route = matched[i]; |
||||||
|
|
||||||
|
if (route.path) { |
||||||
|
items.push({ |
||||||
|
text: route.name, |
||||||
|
disabled: route.path === path || lastItem.path === route.path, |
||||||
|
href: route.path |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return items; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,45 @@ |
|||||||
|
<template> |
||||||
|
<v-dialog v-model="show" persistent width="300"> |
||||||
|
<v-card> |
||||||
|
<v-card-text>{{ $t('Are you sure you want to delete this item?') }}</v-card-text> |
||||||
|
<v-card-actions> |
||||||
|
<v-spacer></v-spacer> |
||||||
|
<v-btn color="error darken-1" @click="handleDelete"> |
||||||
|
{{ $t('Delete') }} |
||||||
|
</v-btn> |
||||||
|
<v-btn color="secondary darken-1" text @click.stop="show = false"> |
||||||
|
{{ $t('Cancel') }} |
||||||
|
</v-btn> |
||||||
|
</v-card-actions> |
||||||
|
</v-card> |
||||||
|
</v-dialog> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
name: 'ConfirmDelete', |
||||||
|
props: { |
||||||
|
visible: { |
||||||
|
type: Boolean, |
||||||
|
required: true, |
||||||
|
default: () => false |
||||||
|
}, |
||||||
|
handleDelete: { |
||||||
|
type: Function, |
||||||
|
required: true |
||||||
|
} |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
show: { |
||||||
|
get() { |
||||||
|
return this.visible; |
||||||
|
}, |
||||||
|
set(value) { |
||||||
|
if (!value) { |
||||||
|
this.$emit('close'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
<template> |
||||||
|
<v-expansion-panels v-model="filtersExpanded"> |
||||||
|
<v-expansion-panel> |
||||||
|
<v-expansion-panel-header> |
||||||
|
{{ $t('Filters') }} |
||||||
|
|
||||||
|
<template slot="actions"> |
||||||
|
<v-icon large>mdi-filter-variant</v-icon> |
||||||
|
</template> |
||||||
|
</v-expansion-panel-header> |
||||||
|
<v-expansion-panel-content> |
||||||
|
<slot name="filter"></slot> |
||||||
|
|
||||||
|
<v-btn color="primary" @click="handleFilter">{{ $t('Filter')}}</v-btn> |
||||||
|
<v-btn color="primary" class="ml-2" text @click="handleReset">{{ $t('Reset') }}</v-btn> |
||||||
|
</v-expansion-panel-content> |
||||||
|
</v-expansion-panel> |
||||||
|
</v-expansion-panels> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
name: 'DataFilter', |
||||||
|
props: { |
||||||
|
handleReset: { |
||||||
|
type: Function, |
||||||
|
required: true |
||||||
|
}, |
||||||
|
handleFilter: { |
||||||
|
type: Function, |
||||||
|
required: true |
||||||
|
} |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
filtersExpanded: false |
||||||
|
}; |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,52 @@ |
|||||||
|
<template> |
||||||
|
<v-menu |
||||||
|
v-model="showMenu" |
||||||
|
:close-on-content-click="false" |
||||||
|
:nudge-right="40" |
||||||
|
transition="scale-transition" |
||||||
|
offset-y |
||||||
|
min-width="290px" |
||||||
|
> |
||||||
|
<template v-slot:activator="{ on }"> |
||||||
|
<v-text-field |
||||||
|
v-model="date" |
||||||
|
:label="label" |
||||||
|
prepend-icon="mdi-calendar" |
||||||
|
readonly |
||||||
|
v-on="on" |
||||||
|
></v-text-field> |
||||||
|
</template> |
||||||
|
<v-date-picker v-model="date" @input="handleInput"></v-date-picker> |
||||||
|
</v-menu> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { formatDateTime } from '../utils/dates'; |
||||||
|
|
||||||
|
export default { |
||||||
|
props: { |
||||||
|
label: { |
||||||
|
type: String, |
||||||
|
required: false, |
||||||
|
default: () => '' |
||||||
|
}, |
||||||
|
value: String |
||||||
|
}, |
||||||
|
created() { |
||||||
|
this.date = this.value ? this.value : this.date; |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
date: this.value ? this.value : new Date().toISOString().substr(0, 10), |
||||||
|
showMenu: false |
||||||
|
}; |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
formatDateTime, |
||||||
|
handleInput() { |
||||||
|
this.showMenu = false; |
||||||
|
this.$emit('input', this.date); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
<template> |
||||||
|
<div class="text-center"> |
||||||
|
<v-overlay :value="visible"> |
||||||
|
<v-progress-circular indeterminate size="64"></v-progress-circular> |
||||||
|
</v-overlay> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
props: { |
||||||
|
visible: { |
||||||
|
type: Boolean, |
||||||
|
required: true |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
<template> |
||||||
|
<v-snackbar |
||||||
|
v-model="show" |
||||||
|
:color="color" |
||||||
|
:multi-line="true" |
||||||
|
:timeout="timeout" |
||||||
|
right |
||||||
|
top |
||||||
|
> |
||||||
|
{{ text }} |
||||||
|
<template v-if="subText"> |
||||||
|
<p>{{ subText }}</p> |
||||||
|
</template> |
||||||
|
<v-btn dark text @click.native="close">{{ $t('Close') }}</v-btn> |
||||||
|
</v-snackbar> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { mapFields } from 'vuex-map-fields'; |
||||||
|
|
||||||
|
export default { |
||||||
|
computed: { |
||||||
|
...mapFields('notifications', ['color', 'show', 'subText', 'text', 'timeout']) |
||||||
|
}, |
||||||
|
|
||||||
|
methods: { |
||||||
|
close() { |
||||||
|
this.show = false; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,137 @@ |
|||||||
|
<template> |
||||||
|
<v-toolbar class="my-md-auto" elevation="0"> |
||||||
|
<slot name="left"></slot> |
||||||
|
<v-spacer /> |
||||||
|
<div> |
||||||
|
<v-btn |
||||||
|
v-if="handleList" |
||||||
|
:loading="isLoading" |
||||||
|
color="primary" |
||||||
|
@click="listItem" |
||||||
|
> |
||||||
|
{{ $t('List') }} |
||||||
|
</v-btn> |
||||||
|
<v-btn |
||||||
|
v-if="handleEdit" |
||||||
|
:loading="isLoading" |
||||||
|
color="primary" |
||||||
|
@click="editItem" |
||||||
|
> |
||||||
|
{{ $t('Edit') }} |
||||||
|
</v-btn> |
||||||
|
<v-btn |
||||||
|
v-if="handleSubmit" |
||||||
|
:loading="isLoading" |
||||||
|
color="primary" |
||||||
|
@click="submitItem" |
||||||
|
> |
||||||
|
<v-icon left>mdi-content-save</v-icon> |
||||||
|
{{ $t('Submit') }} |
||||||
|
</v-btn> |
||||||
|
<v-btn |
||||||
|
v-if="handleReset" |
||||||
|
color="primary" |
||||||
|
class="ml-sm-2" |
||||||
|
@click="resetItem" |
||||||
|
> |
||||||
|
{{ $t('Reset') }} |
||||||
|
</v-btn> |
||||||
|
<v-btn |
||||||
|
v-if="handleDelete" |
||||||
|
color="error" |
||||||
|
class="ml-sm-2" |
||||||
|
@click="confirmDelete = true" |
||||||
|
> |
||||||
|
{{ $t('Delete') }} |
||||||
|
</v-btn> |
||||||
|
|
||||||
|
<v-btn v-if="handleAdd" color="primary" rounded @click="addItem"> |
||||||
|
<v-icon>mdi-plus-circle</v-icon> |
||||||
|
</v-btn> |
||||||
|
</div> |
||||||
|
<ConfirmDelete |
||||||
|
v-if="handleDelete" |
||||||
|
:visible="confirmDelete" |
||||||
|
:handle-delete="handleDelete" |
||||||
|
@close="confirmDelete = false" |
||||||
|
/> |
||||||
|
</v-toolbar> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import ConfirmDelete from './ConfirmDelete'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'Toolbar', |
||||||
|
components: { |
||||||
|
ConfirmDelete |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
confirmDelete: false |
||||||
|
}; |
||||||
|
}, |
||||||
|
props: { |
||||||
|
handleList: { |
||||||
|
type: Function, |
||||||
|
required: false |
||||||
|
}, |
||||||
|
handleEdit: { |
||||||
|
type: Function, |
||||||
|
required: false |
||||||
|
}, |
||||||
|
handleSubmit: { |
||||||
|
type: Function, |
||||||
|
required: false |
||||||
|
}, |
||||||
|
handleReset: { |
||||||
|
type: Function, |
||||||
|
required: false |
||||||
|
}, |
||||||
|
handleDelete: { |
||||||
|
type: Function, |
||||||
|
required: false |
||||||
|
}, |
||||||
|
handleAdd: { |
||||||
|
type: Function, |
||||||
|
required: false |
||||||
|
}, |
||||||
|
title: { |
||||||
|
type: String, |
||||||
|
required: false |
||||||
|
}, |
||||||
|
isLoading: { |
||||||
|
type: Boolean, |
||||||
|
required: false, |
||||||
|
default: () => false |
||||||
|
} |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
listItem() { |
||||||
|
if (this.handleList) { |
||||||
|
this.handleList(); |
||||||
|
} |
||||||
|
}, |
||||||
|
addItem() { |
||||||
|
if (this.handleAdd) { |
||||||
|
this.handleAdd(); |
||||||
|
} |
||||||
|
}, |
||||||
|
editItem() { |
||||||
|
if (this.handleEdit) { |
||||||
|
this.handleEdit(); |
||||||
|
} |
||||||
|
}, |
||||||
|
submitItem() { |
||||||
|
if (this.handleSubmit) { |
||||||
|
this.handleSubmit(); |
||||||
|
} |
||||||
|
}, |
||||||
|
resetItem() { |
||||||
|
if (this.handleReset) { |
||||||
|
this.handleReset(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,76 @@ |
|||||||
|
<template> |
||||||
|
<v-container fluid> |
||||||
|
<v-row> |
||||||
|
<v-col cols="12" sm="6" md="6"> |
||||||
|
<v-text-field |
||||||
|
v-model="item.title" |
||||||
|
:label="$t('title')" |
||||||
|
type="text" |
||||||
|
/> |
||||||
|
</v-col> |
||||||
|
|
||||||
|
<v-col cols="12" sm="6" md="6"> |
||||||
|
<v-text-field |
||||||
|
v-model="item.code" |
||||||
|
:label="$t('code')" |
||||||
|
type="text" |
||||||
|
/> |
||||||
|
</v-col> |
||||||
|
</v-row> |
||||||
|
|
||||||
|
<v-row> |
||||||
|
<v-col cols="12" sm="6" md="6"> |
||||||
|
<v-combobox |
||||||
|
v-model="item.category" |
||||||
|
:items="categorySelectItems" |
||||||
|
:no-data-text="$t('No results')" |
||||||
|
:label="$t('category')" |
||||||
|
item-text="name" |
||||||
|
item-value="@id" |
||||||
|
chips |
||||||
|
/> |
||||||
|
</v-col> |
||||||
|
|
||||||
|
<v-row cols="12"></v-row> |
||||||
|
</v-row> |
||||||
|
|
||||||
|
</v-container> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
|
||||||
|
import { mapActions, mapGetters } from 'vuex'; |
||||||
|
import { mapFields } from 'vuex-map-fields'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'CourseFilter', |
||||||
|
props: { |
||||||
|
values: { |
||||||
|
type: Object, |
||||||
|
required: true |
||||||
|
} |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return {}; |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
this.categoryGetSelectItems(); |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
...mapActions({ |
||||||
|
categoryGetSelectItems: 'coursecategory/fetchSelectItems' |
||||||
|
}), |
||||||
|
}, |
||||||
|
|
||||||
|
computed: { |
||||||
|
...mapFields('coursecategory', { |
||||||
|
categorySelectItems: 'selectItems' |
||||||
|
}), |
||||||
|
|
||||||
|
// eslint-disable-next-line |
||||||
|
item() { |
||||||
|
return this.initialValues || this.values; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,173 @@ |
|||||||
|
<template> |
||||||
|
<v-form> |
||||||
|
<v-container fluid> |
||||||
|
<v-row> |
||||||
|
<v-col cols="12" sm="6" md="6"> |
||||||
|
<v-text-field |
||||||
|
v-model="item.title" |
||||||
|
:error-messages="titleErrors" |
||||||
|
:label="$t('title')" |
||||||
|
required |
||||||
|
@input="$v.item.title.$touch()" |
||||||
|
@blur="$v.item.title.$touch()" |
||||||
|
/> |
||||||
|
</v-col> |
||||||
|
|
||||||
|
<v-col cols="12" sm="6" md="6"> |
||||||
|
<v-text-field |
||||||
|
v-model="item.code" |
||||||
|
:error-messages="codeErrors" |
||||||
|
:label="$t('code')" |
||||||
|
required |
||||||
|
@input="$v.item.code.$touch()" |
||||||
|
@blur="$v.item.code.$touch()" |
||||||
|
/> |
||||||
|
</v-col> |
||||||
|
</v-row> |
||||||
|
|
||||||
|
<v-row> |
||||||
|
<v-col cols="12" sm="6" md="6"> |
||||||
|
<v-combobox |
||||||
|
v-model="item.category" |
||||||
|
:items="categorySelectItems" |
||||||
|
:error-messages="categoryErrors" |
||||||
|
:no-data-text="$t('No results')" |
||||||
|
:label="$t('category')" |
||||||
|
item-text="name" |
||||||
|
item-value="@id" |
||||||
|
/> |
||||||
|
</v-col> |
||||||
|
|
||||||
|
<v-col cols="12" sm="6" md="6"> |
||||||
|
<v-text-field |
||||||
|
v-model.number="item.visibility" |
||||||
|
:error-messages="visibilityErrors" |
||||||
|
:label="$t('visibility')" |
||||||
|
required |
||||||
|
@input="$v.item.visibility.$touch()" |
||||||
|
@blur="$v.item.visibility.$touch()" |
||||||
|
/> |
||||||
|
</v-col> |
||||||
|
</v-row> |
||||||
|
|
||||||
|
</v-container> |
||||||
|
</v-form> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import has from 'lodash/has'; |
||||||
|
import { validationMixin } from 'vuelidate'; |
||||||
|
import { required } from 'vuelidate/lib/validators'; |
||||||
|
import { mapActions } from 'vuex'; |
||||||
|
import { mapFields } from 'vuex-map-fields'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'CourseForm', |
||||||
|
mixins: [validationMixin], |
||||||
|
props: { |
||||||
|
values: { |
||||||
|
type: Object, |
||||||
|
required: true |
||||||
|
}, |
||||||
|
|
||||||
|
errors: { |
||||||
|
type: Object, |
||||||
|
default: () => {} |
||||||
|
}, |
||||||
|
|
||||||
|
initialValues: { |
||||||
|
type: Object, |
||||||
|
default: () => {} |
||||||
|
} |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
title: null, |
||||||
|
code: null, |
||||||
|
category: null, |
||||||
|
visibility: null, |
||||||
|
}; |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
...mapFields('coursecategory', { |
||||||
|
categorySelectItems: 'selectItems' |
||||||
|
}), |
||||||
|
|
||||||
|
// eslint-disable-next-line |
||||||
|
item() { |
||||||
|
return this.initialValues || this.values; |
||||||
|
}, |
||||||
|
|
||||||
|
titleErrors() { |
||||||
|
const errors = []; |
||||||
|
|
||||||
|
if (!this.$v.item.title.$dirty) return errors; |
||||||
|
|
||||||
|
has(this.violations, 'title') && errors.push(this.violations.title); |
||||||
|
|
||||||
|
!this.$v.item.title.required && errors.push(this.$t('Field is required')); |
||||||
|
|
||||||
|
return errors; |
||||||
|
}, |
||||||
|
codeErrors() { |
||||||
|
const errors = []; |
||||||
|
|
||||||
|
if (!this.$v.item.code.$dirty) return errors; |
||||||
|
|
||||||
|
has(this.violations, 'code') && errors.push(this.violations.code); |
||||||
|
|
||||||
|
!this.$v.item.code.required && errors.push(this.$t('Field is required')); |
||||||
|
|
||||||
|
return errors; |
||||||
|
}, |
||||||
|
categoryErrors() { |
||||||
|
const errors = []; |
||||||
|
|
||||||
|
if (!this.$v.item.category.$dirty) return errors; |
||||||
|
|
||||||
|
has(this.violations, 'category') && errors.push(this.violations.category); |
||||||
|
|
||||||
|
|
||||||
|
return errors; |
||||||
|
}, |
||||||
|
visibilityErrors() { |
||||||
|
const errors = []; |
||||||
|
|
||||||
|
if (!this.$v.item.visibility.$dirty) return errors; |
||||||
|
|
||||||
|
has(this.violations, 'visibility') && errors.push(this.violations.visibility); |
||||||
|
|
||||||
|
!this.$v.item.visibility.required && errors.push(this.$t('Field is required')); |
||||||
|
|
||||||
|
return errors; |
||||||
|
}, |
||||||
|
|
||||||
|
violations() { |
||||||
|
return this.errors || {}; |
||||||
|
} |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
this.categoryGetSelectItems(); |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
...mapActions({ |
||||||
|
categoryGetSelectItems: 'coursecategory/fetchSelectItems' |
||||||
|
}), |
||||||
|
}, |
||||||
|
validations: { |
||||||
|
item: { |
||||||
|
title: { |
||||||
|
required, |
||||||
|
}, |
||||||
|
code: { |
||||||
|
required, |
||||||
|
}, |
||||||
|
category: { |
||||||
|
}, |
||||||
|
visibility: { |
||||||
|
required, |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
<template> |
||||||
|
<router-view></router-view> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
name: 'CourseLayout' |
||||||
|
} |
||||||
|
</script> |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
<template> |
||||||
|
<v-container fluid> |
||||||
|
<v-row> |
||||||
|
<v-col cols="12" sm="6" md="6"> |
||||||
|
<v-text-field |
||||||
|
v-model="item.name" |
||||||
|
:label="$t('name')" |
||||||
|
type="text" |
||||||
|
/> |
||||||
|
</v-col> |
||||||
|
</v-row> |
||||||
|
</v-container> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'CourseCategoryFilter', |
||||||
|
props: { |
||||||
|
values: { |
||||||
|
type: Object, |
||||||
|
required: true |
||||||
|
} |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return {}; |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
}, |
||||||
|
|
||||||
|
computed: { |
||||||
|
// eslint-disable-next-line |
||||||
|
item() { |
||||||
|
return this.initialValues || this.values; |
||||||
|
} |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,113 @@ |
|||||||
|
<template> |
||||||
|
<v-form> |
||||||
|
<v-container fluid> |
||||||
|
<v-row> |
||||||
|
<v-col cols="12" sm="6" md="6"> |
||||||
|
<v-text-field |
||||||
|
v-model="item.name" |
||||||
|
:error-messages="nameErrors" |
||||||
|
:label="$t('name')" |
||||||
|
required |
||||||
|
@input="$v.item.name.$touch()" |
||||||
|
@blur="$v.item.name.$touch()" |
||||||
|
/> |
||||||
|
</v-col> |
||||||
|
|
||||||
|
<v-col cols="12" sm="6" md="6"> |
||||||
|
<v-text-field |
||||||
|
v-model="item.code" |
||||||
|
:error-messages="codeErrors" |
||||||
|
:label="$t('code')" |
||||||
|
required |
||||||
|
@input="$v.item.code.$touch()" |
||||||
|
@blur="$v.item.code.$touch()" |
||||||
|
/> |
||||||
|
</v-col> |
||||||
|
|
||||||
|
</v-row> |
||||||
|
|
||||||
|
</v-container> |
||||||
|
</v-form> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import has from 'lodash/has'; |
||||||
|
import { validationMixin } from 'vuelidate'; |
||||||
|
import { required } from 'vuelidate/lib/validators'; |
||||||
|
import { mapActions } from 'vuex'; |
||||||
|
import { mapFields } from 'vuex-map-fields'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'CourseCategoryForm', |
||||||
|
mixins: [validationMixin], |
||||||
|
props: { |
||||||
|
values: { |
||||||
|
type: Object, |
||||||
|
required: true |
||||||
|
}, |
||||||
|
|
||||||
|
errors: { |
||||||
|
type: Object, |
||||||
|
default: () => {} |
||||||
|
}, |
||||||
|
|
||||||
|
initialValues: { |
||||||
|
type: Object, |
||||||
|
default: () => {} |
||||||
|
} |
||||||
|
}, |
||||||
|
mounted() { |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
}; |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
|
||||||
|
// eslint-disable-next-line |
||||||
|
item() { |
||||||
|
return this.initialValues || this.values; |
||||||
|
}, |
||||||
|
|
||||||
|
nameErrors() { |
||||||
|
const errors = []; |
||||||
|
|
||||||
|
if (!this.$v.item.name.$dirty) return errors; |
||||||
|
|
||||||
|
has(this.violations, 'name') && errors.push(this.violations.name); |
||||||
|
|
||||||
|
!this.$v.item.name.required && errors.push(this.$t('Field is required')); |
||||||
|
|
||||||
|
return errors; |
||||||
|
}, |
||||||
|
codeErrors() { |
||||||
|
const errors = []; |
||||||
|
|
||||||
|
if (!this.$v.item.code.$dirty) return errors; |
||||||
|
|
||||||
|
has(this.violations, 'code') && errors.push(this.violations.code); |
||||||
|
|
||||||
|
!this.$v.item.code.required && errors.push(this.$t('Field is required')); |
||||||
|
|
||||||
|
return errors; |
||||||
|
}, |
||||||
|
|
||||||
|
|
||||||
|
violations() { |
||||||
|
return this.errors || {}; |
||||||
|
} |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
}, |
||||||
|
validations: { |
||||||
|
item: { |
||||||
|
name: { |
||||||
|
required, |
||||||
|
}, |
||||||
|
code: { |
||||||
|
required, |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
<template> |
||||||
|
<router-view></router-view> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
name: 'CourseCategoryLayout' |
||||||
|
} |
||||||
|
</script> |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
export default class SubmissionError extends Error { |
||||||
|
constructor (errors) { |
||||||
|
super('Submit Validation Failed'); |
||||||
|
this.errors = errors; |
||||||
|
//Error.captureStackTrace(this, this.constructor);
|
||||||
|
this.name = this.constructor.name; |
||||||
|
|
||||||
|
return this; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
import Vue from 'vue'; |
||||||
|
import VueI18n from 'vue-i18n'; |
||||||
|
import messages from './locales/en'; |
||||||
|
|
||||||
|
Vue.use(VueI18n); |
||||||
|
|
||||||
|
export default new VueI18n({ |
||||||
|
locale: process.env.VUE_APP_I18N_LOCALE || 'en', |
||||||
|
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en', |
||||||
|
messages: { |
||||||
|
en: messages |
||||||
|
} |
||||||
|
}); |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
export default { |
||||||
|
'Submit': 'Submit', |
||||||
|
'Reset': 'Reset', |
||||||
|
'Delete': 'Delete', |
||||||
|
'Edit': 'Edit', |
||||||
|
'Are you sure you want to delete this item?': 'Are you sure you want to delete this item?', |
||||||
|
'No results': 'No results', |
||||||
|
'Close': 'Close', |
||||||
|
'Cancel': 'Cancel', |
||||||
|
'Updated': 'Updated', |
||||||
|
'Field': 'Field', |
||||||
|
'Value': 'Value', |
||||||
|
'Filters': 'Filters', |
||||||
|
'Filter': 'Filter', |
||||||
|
'Data unavailable': 'Data unavailable', |
||||||
|
'Loading...': 'Loading...', |
||||||
|
'Deleted': 'Deleted', |
||||||
|
'Please, insert a value bigger than zero!': 'Please, insert a value bigger than zero!', |
||||||
|
'Please type something': 'Please type something', |
||||||
|
'Field is required': 'Field is required', |
||||||
|
'Records per page:': 'Records per page:', |
||||||
|
}; |
||||||
@ -0,0 +1,67 @@ |
|||||||
|
import Vue from "vue"; |
||||||
|
import App from "./App"; |
||||||
|
import router from "./router"; |
||||||
|
import store from "./store"; |
||||||
|
import courseCategoryService from './services/coursecategory'; |
||||||
|
import courseService from './services/course'; |
||||||
|
import makeCrudModule from './store/modules/crud'; |
||||||
|
|
||||||
|
// import '@mdi/font/css/materialdesignicons.css'
|
||||||
|
|
||||||
|
/*router.beforeEach((to, from, next) => { |
||||||
|
// hack to allow for forward slashes in path ids
|
||||||
|
if (to.fullPath.includes('%2F')) { |
||||||
|
next(to.fullPath.replace('%2F', '/')); |
||||||
|
} |
||||||
|
next(); |
||||||
|
});*/ |
||||||
|
|
||||||
|
import vuetify from './plugins/vuetify' // path to vuetify export
|
||||||
|
|
||||||
|
import ApolloClient from 'apollo-boost' |
||||||
|
const apolloClient = new ApolloClient({ |
||||||
|
// You should use an absolute URL here
|
||||||
|
uri: '/api/graphql/' |
||||||
|
}) |
||||||
|
|
||||||
|
import VueApollo from 'vue-apollo'; |
||||||
|
Vue.use(VueApollo); |
||||||
|
|
||||||
|
import Vuelidate from 'vuelidate'; |
||||||
|
import i18n from './i18n'; |
||||||
|
Vue.config.productionTip = false; |
||||||
|
Vue.use(Vuelidate); |
||||||
|
|
||||||
|
const apolloProvider = new VueApollo({ |
||||||
|
defaultClient: apolloClient, |
||||||
|
}); |
||||||
|
|
||||||
|
//import './quasar'
|
||||||
|
|
||||||
|
store.registerModule( |
||||||
|
'course', |
||||||
|
makeCrudModule({ |
||||||
|
service: courseService |
||||||
|
}) |
||||||
|
); |
||||||
|
|
||||||
|
store.registerModule( |
||||||
|
'coursecategory', |
||||||
|
makeCrudModule({ |
||||||
|
service: courseCategoryService |
||||||
|
}) |
||||||
|
); |
||||||
|
|
||||||
|
Vue.config.productionTip = false; |
||||||
|
|
||||||
|
new Vue({ |
||||||
|
vuetify, |
||||||
|
i18n, |
||||||
|
components: {App}, |
||||||
|
apolloProvider, |
||||||
|
data: {}, |
||||||
|
store, |
||||||
|
router, |
||||||
|
render: h => h(App) |
||||||
|
}). |
||||||
|
$mount("#app"); |
||||||
@ -0,0 +1,41 @@ |
|||||||
|
import NotificationMixin from './NotificationMixin'; |
||||||
|
import { formatDateTime } from '../utils/dates'; |
||||||
|
|
||||||
|
export default { |
||||||
|
mixins: [NotificationMixin], |
||||||
|
methods: { |
||||||
|
formatDateTime, |
||||||
|
onCreated(item) { |
||||||
|
this.showMessage(`${item['@id']} created`); |
||||||
|
|
||||||
|
this.$router.push({ |
||||||
|
name: `${this.$options.servicePrefix}Update`, |
||||||
|
params: { id: item['@id'] } |
||||||
|
}); |
||||||
|
}, |
||||||
|
onSendForm() { |
||||||
|
const createForm = this.$refs.createForm; |
||||||
|
createForm.$v.$touch(); |
||||||
|
if (!createForm.$v.$invalid) { |
||||||
|
this.create(createForm.$v.item.$model); |
||||||
|
} |
||||||
|
}, |
||||||
|
resetForm() { |
||||||
|
this.$refs.createForm.$v.$reset(); |
||||||
|
this.item = {}; |
||||||
|
} |
||||||
|
}, |
||||||
|
watch: { |
||||||
|
created(created) { |
||||||
|
if (!created) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
this.onCreated(created); |
||||||
|
}, |
||||||
|
|
||||||
|
error(message) { |
||||||
|
message && this.showError(message); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
@ -0,0 +1,88 @@ |
|||||||
|
import isEmpty from 'lodash/isEmpty'; |
||||||
|
import { formatDateTime } from '../utils/dates'; |
||||||
|
import NotificationMixin from './NotificationMixin'; |
||||||
|
|
||||||
|
export default { |
||||||
|
mixins: [NotificationMixin], |
||||||
|
|
||||||
|
data() { |
||||||
|
return { |
||||||
|
options: { |
||||||
|
sortBy: [], |
||||||
|
descending: false, |
||||||
|
page: 1, |
||||||
|
itemsPerPage: 15 |
||||||
|
}, |
||||||
|
filters: {} |
||||||
|
}; |
||||||
|
}, |
||||||
|
|
||||||
|
watch: { |
||||||
|
deletedItem(item) { |
||||||
|
this.showMessage(`${item['@id']} deleted.`); |
||||||
|
}, |
||||||
|
|
||||||
|
error(message) { |
||||||
|
message && this.showError(message); |
||||||
|
}, |
||||||
|
|
||||||
|
items() { |
||||||
|
this.options.totalItems = this.totalItems; |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
methods: { |
||||||
|
onUpdateOptions(props) { |
||||||
|
const { page, itemsPerPage, sortBy, descending, totalItems } = props; |
||||||
|
let params = { |
||||||
|
...this.filters |
||||||
|
}; |
||||||
|
if (itemsPerPage > 0) { |
||||||
|
params = { ...params, itemsPerPage, page }; |
||||||
|
} |
||||||
|
|
||||||
|
if (!isEmpty(sortBy)) { |
||||||
|
params[`order[${sortBy}]`] = descending ? 'desc' : 'asc'; |
||||||
|
} |
||||||
|
|
||||||
|
this.getPage(params).then(() => { |
||||||
|
this.options.sortBy = sortBy; |
||||||
|
this.options.descending = descending; |
||||||
|
this.options.itemsPerPage = itemsPerPage; |
||||||
|
this.options.totalItems = totalItems; |
||||||
|
}); |
||||||
|
}, |
||||||
|
|
||||||
|
onSendFilter() { |
||||||
|
this.resetList = true; |
||||||
|
this.onUpdateOptions(this.options); |
||||||
|
}, |
||||||
|
|
||||||
|
resetFilter() { |
||||||
|
this.filters = {}; |
||||||
|
}, |
||||||
|
|
||||||
|
addHandler() { |
||||||
|
this.$router.push({ name: `${this.$options.servicePrefix}Create` }); |
||||||
|
}, |
||||||
|
|
||||||
|
showHandler(item) { |
||||||
|
this.$router.push({ |
||||||
|
name: `${this.$options.servicePrefix}Show`, |
||||||
|
params: { id: item['@id'] } |
||||||
|
}); |
||||||
|
}, |
||||||
|
|
||||||
|
editHandler(item) { |
||||||
|
this.$router.push({ |
||||||
|
name: `${this.$options.servicePrefix}Update`, |
||||||
|
params: { id: item['@id'] } |
||||||
|
}); |
||||||
|
}, |
||||||
|
|
||||||
|
deleteHandler(item) { |
||||||
|
this.deleteItem(item).then(() => this.onUpdateOptions(this.options)); |
||||||
|
}, |
||||||
|
formatDateTime |
||||||
|
} |
||||||
|
}; |
||||||
@ -0,0 +1,37 @@ |
|||||||
|
import { mapFields } from 'vuex-map-fields'; |
||||||
|
|
||||||
|
export default { |
||||||
|
computed: { |
||||||
|
...mapFields('notifications', ['color', 'show', 'subText', 'text', 'timeout']) |
||||||
|
}, |
||||||
|
|
||||||
|
methods: { |
||||||
|
cleanState() { |
||||||
|
setTimeout(() => { |
||||||
|
this.show = false; |
||||||
|
}, this.timeout); |
||||||
|
}, |
||||||
|
|
||||||
|
showError(error) { |
||||||
|
this.showMessage(error, 'danger'); |
||||||
|
}, |
||||||
|
|
||||||
|
showMessage(message, color = 'success') { |
||||||
|
this.show = true; |
||||||
|
this.color = color; |
||||||
|
|
||||||
|
if (typeof message === 'string') { |
||||||
|
this.text = message; |
||||||
|
this.cleanState(); |
||||||
|
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
this.text = message.message; |
||||||
|
|
||||||
|
if (message.response) this.subText = message.response.data.message; |
||||||
|
|
||||||
|
this.cleanState(); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
@ -0,0 +1,47 @@ |
|||||||
|
import NotificationMixin from './NotificationMixin'; |
||||||
|
import { formatDateTime } from '../utils/dates'; |
||||||
|
|
||||||
|
export default { |
||||||
|
mixins: [NotificationMixin], |
||||||
|
created() { |
||||||
|
this.retrieve(decodeURIComponent(this.$route.params.id)); |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
item() { |
||||||
|
return this.find(decodeURIComponent(this.$route.params.id)); |
||||||
|
} |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
list() { |
||||||
|
this.$router |
||||||
|
.push({ name: `${this.$options.servicePrefix}List` }) |
||||||
|
.catch(() => {}); |
||||||
|
}, |
||||||
|
del() { |
||||||
|
this.deleteItem(this.item).then(() => { |
||||||
|
this.showMessage(`${this.item['@id']} deleted.`); |
||||||
|
this.$router |
||||||
|
.push({ name: `${this.$options.servicePrefix}List` }) |
||||||
|
.catch(() => {}); |
||||||
|
}); |
||||||
|
}, |
||||||
|
formatDateTime, |
||||||
|
editHandler() { |
||||||
|
this.$router.push({ |
||||||
|
name: `${this.$options.servicePrefix}Update`, |
||||||
|
params: { id: this.item['@id'] } |
||||||
|
}); |
||||||
|
} |
||||||
|
}, |
||||||
|
watch: { |
||||||
|
error(message) { |
||||||
|
message && this.showError(message); |
||||||
|
}, |
||||||
|
deleteError(message) { |
||||||
|
message && this.showError(message); |
||||||
|
} |
||||||
|
}, |
||||||
|
beforeDestroy() { |
||||||
|
this.reset(); |
||||||
|
} |
||||||
|
}; |
||||||
@ -0,0 +1,79 @@ |
|||||||
|
import NotificationMixin from './NotificationMixin'; |
||||||
|
import { formatDateTime } from '../utils/dates'; |
||||||
|
|
||||||
|
export default { |
||||||
|
mixins: [NotificationMixin], |
||||||
|
data() { |
||||||
|
return { |
||||||
|
item: {} |
||||||
|
}; |
||||||
|
}, |
||||||
|
created() { |
||||||
|
this.retrieve(decodeURIComponent(this.$route.params.id)); |
||||||
|
}, |
||||||
|
beforeDestroy() { |
||||||
|
this.reset(); |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
retrieved() { |
||||||
|
return this.find(decodeURIComponent(this.$route.params.id)); |
||||||
|
} |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
del() { |
||||||
|
this.deleteItem(this.retrieved).then(() => { |
||||||
|
this.showMessage(`${this.item['@id']} deleted.`); |
||||||
|
this.$router |
||||||
|
.push({ name: `${this.$options.servicePrefix}List` }) |
||||||
|
.catch(() => {}); |
||||||
|
}); |
||||||
|
}, |
||||||
|
formatDateTime, |
||||||
|
reset() { |
||||||
|
this.$refs.updateForm.$v.$reset(); |
||||||
|
this.updateReset(); |
||||||
|
this.delReset(); |
||||||
|
this.createReset(); |
||||||
|
}, |
||||||
|
|
||||||
|
onSendForm() { |
||||||
|
const updateForm = this.$refs.updateForm; |
||||||
|
updateForm.$v.$touch(); |
||||||
|
|
||||||
|
if (!updateForm.$v.$invalid) { |
||||||
|
this.update(updateForm.$v.item.$model); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
resetForm() { |
||||||
|
this.$refs.updateForm.$v.$reset(); |
||||||
|
this.item = { ...this.retrieved }; |
||||||
|
} |
||||||
|
}, |
||||||
|
watch: { |
||||||
|
deleted(deleted) { |
||||||
|
if (!deleted) { |
||||||
|
return; |
||||||
|
} |
||||||
|
this.$router |
||||||
|
.push({ name: `${this.$options.servicePrefix}List` }) |
||||||
|
.catch(() => {}); |
||||||
|
}, |
||||||
|
|
||||||
|
error(message) { |
||||||
|
message && this.showError(message); |
||||||
|
}, |
||||||
|
|
||||||
|
deleteError(message) { |
||||||
|
message && this.showError(message); |
||||||
|
}, |
||||||
|
|
||||||
|
updated(val) { |
||||||
|
this.showMessage(`${val['@id']} updated.`); |
||||||
|
}, |
||||||
|
|
||||||
|
retrieved(val) { |
||||||
|
this.item = { ...val }; |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
// src/plugins/vuetify.js
|
||||||
|
|
||||||
|
import Vue from 'vue' |
||||||
|
import Vuetify from 'vuetify' |
||||||
|
import 'vuetify/dist/vuetify.min.css' |
||||||
|
|
||||||
|
Vue.use(Vuetify) |
||||||
|
|
||||||
|
const opts = { |
||||||
|
icons: { |
||||||
|
iconfont: 'mdi' |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
export default new Vuetify(opts) |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
import Vue from 'vue' |
||||||
|
|
||||||
|
// import './styles/quasar.sass'
|
||||||
|
import '@quasar/extras/material-icons/material-icons.css' |
||||||
|
import Quasar from 'quasar/dist/quasar.umd.js' |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
export default { |
||||||
|
path: '/courses', |
||||||
|
name: 'courses', |
||||||
|
component: () => import('../components/course/Layout'), |
||||||
|
redirect: { name: 'CourseList' }, |
||||||
|
children: [ |
||||||
|
{ |
||||||
|
name: 'CourseList', |
||||||
|
path: '', |
||||||
|
component: () => import('../views/course/List') |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'CourseCreate', |
||||||
|
path: 'new', |
||||||
|
component: () => import('../views/course/Create') |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'CourseUpdate', |
||||||
|
path: ':id/edit', |
||||||
|
component: () => import('../views/course/Update') |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'CourseShow', |
||||||
|
path: ':id', |
||||||
|
component: () => import('../views/course/Show') |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
export default { |
||||||
|
path: '/course_categories', |
||||||
|
name: 'course_categories', |
||||||
|
component: () => import('../components/coursecategory/Layout'), |
||||||
|
redirect: { name: 'CourseCategoryList' }, |
||||||
|
children: [ |
||||||
|
{ |
||||||
|
name: 'CourseCategoryList', |
||||||
|
path: '', |
||||||
|
component: () => import('../views/coursecategory/List') |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'CourseCategoryCreate', |
||||||
|
path: 'new', |
||||||
|
component: () => import('../views/coursecategory/Create') |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'CourseCategoryUpdate', |
||||||
|
path: ':id/edit', |
||||||
|
component: () => import('../views/coursecategory/Update') |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: 'CourseCategoryShow', |
||||||
|
path: ':id', |
||||||
|
component: () => import('../views/coursecategory/Show') |
||||||
|
} |
||||||
|
] |
||||||
|
}; |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
import Vue from "vue"; |
||||||
|
import VueRouter from "vue-router"; |
||||||
|
|
||||||
|
Vue.use(VueRouter); |
||||||
|
|
||||||
|
import courseRoutes from './course'; |
||||||
|
import coursecategoryRoutes from './coursecategory'; |
||||||
|
import sessionRoutes from './../../quasar/router/session'; |
||||||
|
|
||||||
|
export default new VueRouter({ |
||||||
|
mode: "history", |
||||||
|
routes: [ |
||||||
|
courseRoutes, |
||||||
|
...sessionRoutes, |
||||||
|
coursecategoryRoutes, |
||||||
|
// { path: "*", redirect: "/home" }
|
||||||
|
] |
||||||
|
}); |
||||||
@ -0,0 +1,24 @@ |
|||||||
|
import fetch from '../utils/fetch'; |
||||||
|
|
||||||
|
export default function makeService(endpoint) { |
||||||
|
return { |
||||||
|
find(id) { |
||||||
|
return fetch(`${id}`); |
||||||
|
}, |
||||||
|
findAll(params) { |
||||||
|
return fetch(endpoint, params); |
||||||
|
}, |
||||||
|
create(payload) { |
||||||
|
return fetch(endpoint, { method: 'POST', body: JSON.stringify(payload) }); |
||||||
|
}, |
||||||
|
del(item) { |
||||||
|
return fetch(item['@id'], { method: 'DELETE' }); |
||||||
|
}, |
||||||
|
update(payload) { |
||||||
|
return fetch(payload['@id'], { |
||||||
|
method: 'PUT', |
||||||
|
body: JSON.stringify(payload) |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
import makeService from './api'; |
||||||
|
|
||||||
|
export default makeService('courses'); |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
import makeService from './api'; |
||||||
|
|
||||||
|
export default makeService('course_categories'); |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
import Vue from "vue"; |
||||||
|
import Vuex from "vuex"; |
||||||
|
|
||||||
|
import notifications from './modules/notifications'; |
||||||
|
//import session from './../../quasar/store/modules/session/';
|
||||||
|
|
||||||
|
Vue.use(Vuex); |
||||||
|
|
||||||
|
export default new Vuex.Store({ |
||||||
|
modules: { |
||||||
|
notifications, |
||||||
|
//session,
|
||||||
|
} |
||||||
|
}); |
||||||
@ -0,0 +1,273 @@ |
|||||||
|
import Vue from 'vue'; |
||||||
|
import { getField, updateField } from 'vuex-map-fields'; |
||||||
|
import remove from 'lodash/remove'; |
||||||
|
import SubmissionError from '../../error/SubmissionError'; |
||||||
|
|
||||||
|
const initialState = () => ({ |
||||||
|
allIds: [], |
||||||
|
byId: {}, |
||||||
|
created: null, |
||||||
|
deleted: null, |
||||||
|
error: "", |
||||||
|
isLoading: false, |
||||||
|
resetList: false, |
||||||
|
selectItems: null, |
||||||
|
totalItems: 0, |
||||||
|
updated: null, |
||||||
|
view: null, |
||||||
|
violations: null |
||||||
|
}); |
||||||
|
|
||||||
|
const handleError = (commit, e) => { |
||||||
|
commit(ACTIONS.TOGGLE_LOADING); |
||||||
|
|
||||||
|
if (e instanceof SubmissionError) { |
||||||
|
commit(ACTIONS.SET_VIOLATIONS, e.errors); |
||||||
|
// eslint-disable-next-line
|
||||||
|
commit(ACTIONS.SET_ERROR, e.errors._error); |
||||||
|
|
||||||
|
return Promise.reject(e); |
||||||
|
} |
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
commit(ACTIONS.SET_ERROR, e.message); |
||||||
|
|
||||||
|
return Promise.reject(e); |
||||||
|
}; |
||||||
|
|
||||||
|
export const ACTIONS = { |
||||||
|
ADD: 'ADD', |
||||||
|
RESET_CREATE: 'RESET_CREATE', |
||||||
|
RESET_DELETE: 'RESET_DELETE', |
||||||
|
RESET_LIST: 'RESET_LIST', |
||||||
|
RESET_SHOW: 'RESET_SHOW', |
||||||
|
RESET_UPDATE: 'RESET_UPDATE', |
||||||
|
SET_CREATED: 'SET_CREATED', |
||||||
|
SET_DELETED: 'SET_DELETED', |
||||||
|
SET_ERROR: 'SET_ERROR', |
||||||
|
SET_SELECT_ITEMS: 'SET_SELECT_ITEMS', |
||||||
|
SET_TOTAL_ITEMS: 'SET_TOTAL_ITEMS', |
||||||
|
SET_UPDATED: 'SET_UPDATED', |
||||||
|
SET_VIEW: 'SET_VIEW', |
||||||
|
SET_VIOLATIONS: 'SET_VIOLATIONS', |
||||||
|
TOGGLE_LOADING: 'TOGGLE_LOADING' |
||||||
|
}; |
||||||
|
|
||||||
|
export default function makeCrudModule({ |
||||||
|
normalizeRelations = x => x, |
||||||
|
resolveRelations = x => x, |
||||||
|
service |
||||||
|
} = {}) { |
||||||
|
return { |
||||||
|
actions: { |
||||||
|
create: ({ commit }, values) => { |
||||||
|
commit(ACTIONS.SET_ERROR, ''); |
||||||
|
commit(ACTIONS.TOGGLE_LOADING); |
||||||
|
|
||||||
|
service |
||||||
|
.create(values) |
||||||
|
.then(response => response.json()) |
||||||
|
.then(data => { |
||||||
|
commit(ACTIONS.TOGGLE_LOADING); |
||||||
|
commit(ACTIONS.ADD, data); |
||||||
|
commit(ACTIONS.SET_CREATED, data); |
||||||
|
}) |
||||||
|
.catch(e => handleError(commit, e)); |
||||||
|
}, |
||||||
|
del: ({ commit }, item) => { |
||||||
|
commit(ACTIONS.TOGGLE_LOADING); |
||||||
|
|
||||||
|
service |
||||||
|
.del(item) |
||||||
|
.then(() => { |
||||||
|
commit(ACTIONS.TOGGLE_LOADING); |
||||||
|
commit(ACTIONS.SET_DELETED, item); |
||||||
|
}) |
||||||
|
.catch(e => handleError(commit, e)); |
||||||
|
}, |
||||||
|
fetchAll: ({ commit, state }, params) => { |
||||||
|
if (!service) throw new Error('No service specified!'); |
||||||
|
|
||||||
|
commit(ACTIONS.TOGGLE_LOADING); |
||||||
|
|
||||||
|
service |
||||||
|
.findAll({ params }) |
||||||
|
.then(response => response.json()) |
||||||
|
.then(retrieved => { |
||||||
|
commit(ACTIONS.TOGGLE_LOADING); |
||||||
|
|
||||||
|
commit( |
||||||
|
ACTIONS.SET_TOTAL_ITEMS, |
||||||
|
retrieved['hydra:totalItems'] |
||||||
|
); |
||||||
|
commit(ACTIONS.SET_VIEW, retrieved['hydra:view']); |
||||||
|
|
||||||
|
if (true === state.resetList) { |
||||||
|
commit(ACTIONS.RESET_LIST); |
||||||
|
} |
||||||
|
|
||||||
|
retrieved['hydra:member'].forEach(item => { |
||||||
|
commit(ACTIONS.ADD, normalizeRelations(item)); |
||||||
|
}); |
||||||
|
}) |
||||||
|
.catch(e => handleError(commit, e)); |
||||||
|
}, |
||||||
|
fetchSelectItems: ( |
||||||
|
{ commit }, |
||||||
|
{ params = { properties: ['@id', 'name'] } } = {} |
||||||
|
) => { |
||||||
|
commit(ACTIONS.TOGGLE_LOADING); |
||||||
|
|
||||||
|
if (!service) throw new Error('No service specified!'); |
||||||
|
|
||||||
|
service |
||||||
|
.findAll({ params }) |
||||||
|
.then(response => response.json()) |
||||||
|
.then(retrieved => { |
||||||
|
commit( |
||||||
|
ACTIONS.SET_SELECT_ITEMS, |
||||||
|
retrieved['hydra:member'] |
||||||
|
); |
||||||
|
}) |
||||||
|
.catch(e => handleError(commit, e)); |
||||||
|
}, |
||||||
|
load: ({ commit }, id) => { |
||||||
|
if (!service) throw new Error('No service specified!'); |
||||||
|
|
||||||
|
commit(ACTIONS.TOGGLE_LOADING); |
||||||
|
service |
||||||
|
.find(id) |
||||||
|
.then(response => response.json()) |
||||||
|
.then(item => { |
||||||
|
commit(ACTIONS.TOGGLE_LOADING); |
||||||
|
commit(ACTIONS.ADD, normalizeRelations(item)); |
||||||
|
}) |
||||||
|
.catch(e => handleError(commit, e)); |
||||||
|
}, |
||||||
|
resetCreate: ({ commit }) => { |
||||||
|
commit(ACTIONS.RESET_CREATE); |
||||||
|
}, |
||||||
|
resetDelete: ({ commit }) => { |
||||||
|
commit(ACTIONS.RESET_DELETE); |
||||||
|
}, |
||||||
|
resetShow: ({ commit }) => { |
||||||
|
commit(ACTIONS.RESET_SHOW); |
||||||
|
}, |
||||||
|
resetUpdate: ({ commit }) => { |
||||||
|
commit(ACTIONS.RESET_UPDATE); |
||||||
|
}, |
||||||
|
update: ({ commit }, item) => { |
||||||
|
commit(ACTIONS.SET_ERROR, ''); |
||||||
|
commit(ACTIONS.TOGGLE_LOADING); |
||||||
|
|
||||||
|
service |
||||||
|
.update(item) |
||||||
|
.then(response => response.json()) |
||||||
|
.then(data => { |
||||||
|
commit(ACTIONS.TOGGLE_LOADING); |
||||||
|
commit(ACTIONS.SET_UPDATED, data); |
||||||
|
}) |
||||||
|
.catch(e => handleError(commit, e)); |
||||||
|
} |
||||||
|
}, |
||||||
|
getters: { |
||||||
|
find: state => id => { |
||||||
|
return resolveRelations(state.byId[id]); |
||||||
|
}, |
||||||
|
getField, |
||||||
|
list: (state, getters) => { |
||||||
|
return state.allIds.map(id => getters.find(id)); |
||||||
|
} |
||||||
|
}, |
||||||
|
mutations: { |
||||||
|
updateField, |
||||||
|
[ACTIONS.ADD]: (state, item) => { |
||||||
|
Vue.set(state.byId, item['@id'], item); |
||||||
|
Vue.set(state, 'isLoading', false); |
||||||
|
if (state.allIds.includes(item['@id'])) return; |
||||||
|
state.allIds.push(item['@id']); |
||||||
|
}, |
||||||
|
[ACTIONS.RESET_CREATE]: state => { |
||||||
|
Object.assign(state, { |
||||||
|
isLoading: false, |
||||||
|
error: '', |
||||||
|
created: null, |
||||||
|
violations: null |
||||||
|
}); |
||||||
|
}, |
||||||
|
[ACTIONS.RESET_DELETE]: state => { |
||||||
|
Object.assign(state, { |
||||||
|
isLoading: false, |
||||||
|
error: '', |
||||||
|
deleted: null |
||||||
|
}); |
||||||
|
}, |
||||||
|
[ACTIONS.RESET_LIST]: state => { |
||||||
|
Object.assign(state, { |
||||||
|
allIds: [], |
||||||
|
byId: {}, |
||||||
|
error: '', |
||||||
|
isLoading: false, |
||||||
|
resetList: false |
||||||
|
}); |
||||||
|
}, |
||||||
|
[ACTIONS.RESET_SHOW]: state => { |
||||||
|
Object.assign(state, { |
||||||
|
error: '', |
||||||
|
isLoading: false |
||||||
|
}); |
||||||
|
}, |
||||||
|
[ACTIONS.RESET_UPDATE]: state => { |
||||||
|
Object.assign(state, { |
||||||
|
error: '', |
||||||
|
isLoading: false, |
||||||
|
updated: null, |
||||||
|
violations: null |
||||||
|
}); |
||||||
|
}, |
||||||
|
[ACTIONS.SET_CREATED]: (state, created) => { |
||||||
|
Object.assign(state, { created }); |
||||||
|
}, |
||||||
|
[ACTIONS.SET_DELETED]: (state, deleted) => { |
||||||
|
if (!state.allIds.includes(deleted['@id'])) return; |
||||||
|
Object.assign(state, { |
||||||
|
allIds: remove(state.allIds, item => item['@id'] === deleted['@id']), |
||||||
|
byId: remove(state.byId, id => id === deleted['@id']), |
||||||
|
deleted |
||||||
|
}); |
||||||
|
}, |
||||||
|
[ACTIONS.SET_ERROR]: (state, error) => { |
||||||
|
Object.assign(state, { error, isLoading: false }); |
||||||
|
}, |
||||||
|
[ACTIONS.SET_SELECT_ITEMS]: (state, selectItems) => { |
||||||
|
Object.assign(state, { |
||||||
|
error: '', |
||||||
|
isLoading: false, |
||||||
|
selectItems |
||||||
|
}); |
||||||
|
}, |
||||||
|
[ACTIONS.SET_TOTAL_ITEMS]: (state, totalItems) => { |
||||||
|
Object.assign(state, { totalItems }); |
||||||
|
}, |
||||||
|
[ACTIONS.SET_UPDATED]: (state, updated) => { |
||||||
|
Object.assign(state, { |
||||||
|
byId: { |
||||||
|
[updated['@id']]: updated |
||||||
|
}, |
||||||
|
updated |
||||||
|
}); |
||||||
|
}, |
||||||
|
[ACTIONS.SET_VIEW]: (state, view) => { |
||||||
|
Object.assign(state, { view }); |
||||||
|
}, |
||||||
|
[ACTIONS.SET_VIOLATIONS]: (state, violations) => { |
||||||
|
Object.assign(state, { violations }); |
||||||
|
}, |
||||||
|
[ACTIONS.TOGGLE_LOADING]: state => { |
||||||
|
Object.assign(state, { error: '', isLoading: !state.isLoading }); |
||||||
|
} |
||||||
|
}, |
||||||
|
namespaced: true, |
||||||
|
state: initialState |
||||||
|
}; |
||||||
|
} |
||||||
@ -0,0 +1,18 @@ |
|||||||
|
import {getField, updateField} from 'vuex-map-fields'; |
||||||
|
|
||||||
|
export default { |
||||||
|
namespaced: true, |
||||||
|
state: { |
||||||
|
show: false, |
||||||
|
color: 'error', |
||||||
|
text: 'An error occurred', |
||||||
|
subText: '', |
||||||
|
timeout: 6000 |
||||||
|
}, |
||||||
|
getters: { |
||||||
|
getField |
||||||
|
}, |
||||||
|
mutations: { |
||||||
|
updateField |
||||||
|
} |
||||||
|
}; |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
import moment from 'moment'; |
||||||
|
|
||||||
|
const formatDateTime = function(date) { |
||||||
|
if (!date) return null; |
||||||
|
|
||||||
|
return moment(date).format('DD/MM/YYYY'); |
||||||
|
}; |
||||||
|
|
||||||
|
export { formatDateTime }; |
||||||
@ -0,0 +1,71 @@ |
|||||||
|
import isObject from 'lodash/isObject'; |
||||||
|
import { ENTRYPOINT } from '../config/entrypoint'; |
||||||
|
import SubmissionError from '../error/SubmissionError'; |
||||||
|
import { normalize } from './hydra'; |
||||||
|
|
||||||
|
const MIME_TYPE = 'application/ld+json'; |
||||||
|
|
||||||
|
const makeParamArray = (key, arr) => |
||||||
|
arr.map(val => `${key}[]=${val}`).join('&'); |
||||||
|
|
||||||
|
export default function(id, options = {}) { |
||||||
|
if ('undefined' === typeof options.headers) options.headers = new Headers(); |
||||||
|
|
||||||
|
if (null === options.headers.get('Accept')) |
||||||
|
options.headers.set('Accept', MIME_TYPE); |
||||||
|
|
||||||
|
if ( |
||||||
|
'undefined' !== options.body && |
||||||
|
!(options.body instanceof FormData) && |
||||||
|
null === options.headers.get('Content-Type') |
||||||
|
) |
||||||
|
options.headers.set('Content-Type', MIME_TYPE); |
||||||
|
|
||||||
|
if (options.params) { |
||||||
|
const params = normalize(options.params); |
||||||
|
let queryString = Object.keys(params) |
||||||
|
.map(key => |
||||||
|
Array.isArray(params[key]) |
||||||
|
? makeParamArray(key, params[key]) |
||||||
|
: `${key}=${params[key]}` |
||||||
|
) |
||||||
|
.join('&'); |
||||||
|
id = `${id}?${queryString}`; |
||||||
|
} |
||||||
|
|
||||||
|
const entryPoint = ENTRYPOINT + (ENTRYPOINT.endsWith('/') ? '' : '/'); |
||||||
|
|
||||||
|
const payload = options.body && JSON.parse(options.body); |
||||||
|
if (isObject(payload) && payload['@id']) |
||||||
|
options.body = JSON.stringify(normalize(payload)); |
||||||
|
|
||||||
|
//console.log(id); console.log(new URL(id, entryPoint));
|
||||||
|
|
||||||
|
return global.fetch(new URL(id, entryPoint), options).then(response => { |
||||||
|
if (response.ok) return response; |
||||||
|
|
||||||
|
return response.json().then( |
||||||
|
json => { |
||||||
|
const error = |
||||||
|
json['hydra:description'] || |
||||||
|
json['hydra:title'] || |
||||||
|
'An error occurred.'; |
||||||
|
|
||||||
|
if (!json.violations) throw Error(error); |
||||||
|
|
||||||
|
let errors = { _error: error }; |
||||||
|
json.violations.forEach(violation => |
||||||
|
errors[violation.propertyPath] |
||||||
|
? (errors[violation.propertyPath] += |
||||||
|
'\n' + errors[violation.propertyPath]) |
||||||
|
: (errors[violation.propertyPath] = violation.message) |
||||||
|
); |
||||||
|
|
||||||
|
throw new SubmissionError(errors); |
||||||
|
}, |
||||||
|
() => { |
||||||
|
throw new Error(response.statusText || 'An error occurred.'); |
||||||
|
} |
||||||
|
); |
||||||
|
}); |
||||||
|
} |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
import get from 'lodash/get'; |
||||||
|
import has from 'lodash/has'; |
||||||
|
import mapValues from 'lodash/mapValues'; |
||||||
|
|
||||||
|
export function normalize(data) { |
||||||
|
if (has(data, 'hydra:member')) { |
||||||
|
// Normalize items in collections
|
||||||
|
data['hydra:member'] = data['hydra:member'].map(item => normalize(item)); |
||||||
|
|
||||||
|
return data; |
||||||
|
} |
||||||
|
|
||||||
|
// Flatten nested documents
|
||||||
|
return mapValues(data, value => |
||||||
|
Array.isArray(value) |
||||||
|
? value.map(v => get(v, '@id', v)) |
||||||
|
: get(value, '@id', value) |
||||||
|
); |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
import moment from 'moment'; |
||||||
|
|
||||||
|
const date = function(value) { |
||||||
|
return moment(value).isValid(); |
||||||
|
}; |
||||||
|
|
||||||
|
export { date }; |
||||||
@ -0,0 +1,46 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<CourseForm ref="createForm" :values="item" :errors="violations" /> |
||||||
|
<Loading :visible="isLoading" /> |
||||||
|
|
||||||
|
<Toolbar :handle-submit="onSendForm" :handle-reset="resetForm"></Toolbar> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { mapActions } from 'vuex'; |
||||||
|
import { createHelpers } from 'vuex-map-fields'; |
||||||
|
import CourseForm from '../../components/course/Form'; |
||||||
|
import Loading from '../../components/Loading'; |
||||||
|
import Toolbar from '../../components/Toolbar'; |
||||||
|
import CreateMixin from '../../mixins/CreateMixin'; |
||||||
|
|
||||||
|
const servicePrefix = 'Course'; |
||||||
|
|
||||||
|
const { mapFields } = createHelpers({ |
||||||
|
getterType: 'course/getField', |
||||||
|
mutationType: 'course/updateField' |
||||||
|
}); |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'CourseCreate', |
||||||
|
servicePrefix, |
||||||
|
mixins: [CreateMixin], |
||||||
|
components: { |
||||||
|
Loading, |
||||||
|
Toolbar, |
||||||
|
CourseForm |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
item: {} |
||||||
|
}; |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
...mapFields(['error', 'isLoading', 'created', 'violations']) |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
...mapActions('course', ['create', 'reset']) |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,122 @@ |
|||||||
|
<template> |
||||||
|
<div class="course-list"> |
||||||
|
<Toolbar :handle-add="addHandler" /> |
||||||
|
|
||||||
|
<v-container grid-list-xl fluid> |
||||||
|
<v-layout row wrap> |
||||||
|
<!-- <v-flex sm12>--> |
||||||
|
<!-- <h1>Course List</h1>--> |
||||||
|
<!-- </v-flex>--> |
||||||
|
<v-flex lg12> |
||||||
|
<DataFilter :handle-filter="onSendFilter" :handle-reset="resetFilter"> |
||||||
|
<CourseFilterForm |
||||||
|
ref="filterForm" |
||||||
|
:values="filters" |
||||||
|
slot="filter" |
||||||
|
/> |
||||||
|
</DataFilter> |
||||||
|
|
||||||
|
<br /> |
||||||
|
|
||||||
|
<v-data-table |
||||||
|
v-model="selected" |
||||||
|
:headers="headers" |
||||||
|
:items="items" |
||||||
|
:items-per-page.sync="options.itemsPerPage" |
||||||
|
:loading="isLoading" |
||||||
|
:loading-text="$t('Loading...')" |
||||||
|
:options.sync="options" |
||||||
|
:server-items-length="totalItems" |
||||||
|
class="elevation-1" |
||||||
|
item-key="@id" |
||||||
|
show-select |
||||||
|
@update:options="onUpdateOptions" |
||||||
|
> |
||||||
|
<template slot="item.category" slot-scope="{ item }"> |
||||||
|
<div v-if="item['category']"> |
||||||
|
{{ item['category'].name }} |
||||||
|
</div> |
||||||
|
<div v-else> |
||||||
|
- |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<template slot="item.visibility" slot-scope="{ item }"> |
||||||
|
{{ $n(item['visibility']) }} |
||||||
|
</template> |
||||||
|
|
||||||
|
<template slot="item.expirationDate" slot-scope="{ item }"> |
||||||
|
{{ formatDateTime(item['expirationDate'], 'long') }} |
||||||
|
</template> |
||||||
|
|
||||||
|
<ActionCell |
||||||
|
slot="item.action" |
||||||
|
slot-scope="props" |
||||||
|
:handle-show="() => showHandler(props.item)" |
||||||
|
:handle-edit="() => editHandler(props.item)" |
||||||
|
:handle-delete="() => deleteHandler(props.item)" |
||||||
|
></ActionCell> |
||||||
|
</v-data-table> |
||||||
|
</v-flex> |
||||||
|
</v-layout> |
||||||
|
</v-container> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { mapActions, mapGetters } from 'vuex'; |
||||||
|
import { mapFields } from 'vuex-map-fields'; |
||||||
|
import ListMixin from '../../mixins/ListMixin'; |
||||||
|
import ActionCell from '../../components/ActionCell'; |
||||||
|
import CourseFilterForm from '../../components/course/Filter'; |
||||||
|
import DataFilter from '../../components/DataFilter'; |
||||||
|
import Toolbar from '../../components/Toolbar'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'CourseList', |
||||||
|
servicePrefix: 'Course', |
||||||
|
mixins: [ListMixin], |
||||||
|
components: { |
||||||
|
Toolbar, |
||||||
|
ActionCell, |
||||||
|
CourseFilterForm, |
||||||
|
DataFilter |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
headers: [ |
||||||
|
{ text: 'title', value: 'title' }, |
||||||
|
{ text: 'code', value: 'code' }, |
||||||
|
{ text: 'courseLanguage', value: 'Language' }, |
||||||
|
{ text: 'category', value: 'category' }, |
||||||
|
{ text: 'visibility', value: 'visibility' }, |
||||||
|
{ |
||||||
|
text: 'Actions', |
||||||
|
value: 'action', |
||||||
|
sortable: false |
||||||
|
} |
||||||
|
], |
||||||
|
selected: [] |
||||||
|
}; |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
...mapGetters('course', { |
||||||
|
items: 'list' |
||||||
|
}), |
||||||
|
...mapFields('course', { |
||||||
|
deletedItem: 'deleted', |
||||||
|
error: 'error', |
||||||
|
isLoading: 'isLoading', |
||||||
|
resetList: 'resetList', |
||||||
|
totalItems: 'totalItems', |
||||||
|
view: 'view' |
||||||
|
}) |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
...mapActions('course', { |
||||||
|
getPage: 'fetchAll', |
||||||
|
deleteItem: 'del' |
||||||
|
}) |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,119 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<Toolbar |
||||||
|
:handle-delete="del" |
||||||
|
:handle-list="list" |
||||||
|
> |
||||||
|
<template slot="left"> |
||||||
|
<v-toolbar-title v-if="item">{{ |
||||||
|
`${$options.servicePrefix} ${item['@id']}` |
||||||
|
}}</v-toolbar-title> |
||||||
|
</template> |
||||||
|
</Toolbar> |
||||||
|
<br /> |
||||||
|
<div v-if="item" class="table-course-show"> |
||||||
|
<v-simple-table> |
||||||
|
<template slot="default"> |
||||||
|
<thead> |
||||||
|
<tr> |
||||||
|
<th>Field</th> |
||||||
|
<th>Value</th> |
||||||
|
<th>Field</th> |
||||||
|
<th>Value</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody> |
||||||
|
<tr> |
||||||
|
<td><strong>{{ $t('title') }}</strong></td> |
||||||
|
<td> |
||||||
|
{{ item['title'] }} |
||||||
|
</td> |
||||||
|
|
||||||
|
<td><strong>{{ $t('code') }}</strong></td> |
||||||
|
<td> |
||||||
|
{{ item['code'] }} |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
|
||||||
|
<tr> |
||||||
|
<td><strong>{{ $t('courseLanguage') }}</strong></td> |
||||||
|
<td> |
||||||
|
{{ item['courseLanguage'] }} |
||||||
|
</td> |
||||||
|
|
||||||
|
<td><strong>{{ $t('category') }}</strong></td> |
||||||
|
<td> |
||||||
|
<div v-if="item['category']"> |
||||||
|
{{ item['category'].name }} |
||||||
|
</div> |
||||||
|
<div v-else> |
||||||
|
- |
||||||
|
</div> |
||||||
|
|
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
|
||||||
|
<tr> |
||||||
|
<td><strong>{{ $t('visibility') }}</strong></td> |
||||||
|
<td> |
||||||
|
{{ $n(item['visibility']) }} </td> |
||||||
|
|
||||||
|
<td><strong>{{ $t('departmentName') }}</strong></td> |
||||||
|
<td> |
||||||
|
{{ item['departmentName'] }} |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
|
||||||
|
<tr> |
||||||
|
<td><strong>{{ $t('departmentUrl') }}</strong></td> |
||||||
|
<td> |
||||||
|
{{ item['departmentUrl'] }} |
||||||
|
</td> |
||||||
|
|
||||||
|
<td><strong>{{ $t('expirationDate') }}</strong></td> |
||||||
|
<td> |
||||||
|
{{ formatDateTime(item['expirationDate'], 'long') }} </td> |
||||||
|
</tr> |
||||||
|
|
||||||
|
</tbody> |
||||||
|
</template> |
||||||
|
</v-simple-table> |
||||||
|
</div> |
||||||
|
|
||||||
|
<Loading :visible="isLoading" /> |
||||||
|
|
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { mapActions, mapGetters } from 'vuex'; |
||||||
|
import { mapFields } from 'vuex-map-fields'; |
||||||
|
import Loading from '../../components/Loading'; |
||||||
|
import ShowMixin from '../../mixins/ShowMixin'; |
||||||
|
import Toolbar from '../../components/Toolbar'; |
||||||
|
|
||||||
|
const servicePrefix = 'Course'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'CourseShow', |
||||||
|
servicePrefix, |
||||||
|
components: { |
||||||
|
Loading, |
||||||
|
Toolbar |
||||||
|
}, |
||||||
|
mixins: [ShowMixin], |
||||||
|
computed: { |
||||||
|
...mapFields('course', { |
||||||
|
isLoading: 'isLoading' |
||||||
|
}), |
||||||
|
...mapGetters('course', ['find']) |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
...mapActions('course', { |
||||||
|
deleteItem: 'del', |
||||||
|
reset: 'resetShow', |
||||||
|
retrieve: 'load' |
||||||
|
}) |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,67 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<v-card |
||||||
|
class="mx-auto" |
||||||
|
> |
||||||
|
<CourseForm |
||||||
|
ref="updateForm" |
||||||
|
v-if="item" |
||||||
|
:values="item" |
||||||
|
:errors="violations" |
||||||
|
/> |
||||||
|
<Loading :visible="isLoading || deleteLoading" /> |
||||||
|
<v-footer> |
||||||
|
<Toolbar |
||||||
|
:handle-submit="onSendForm" |
||||||
|
:handle-reset="resetForm" |
||||||
|
:handle-delete="del" |
||||||
|
/> |
||||||
|
</v-footer> |
||||||
|
</v-card> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { mapActions, mapGetters } from 'vuex'; |
||||||
|
import { mapFields } from 'vuex-map-fields'; |
||||||
|
import CourseForm from '../../components/course/Form.vue'; |
||||||
|
import Loading from '../../components/Loading'; |
||||||
|
import Toolbar from '../../components/Toolbar'; |
||||||
|
import UpdateMixin from '../../mixins/UpdateMixin'; |
||||||
|
|
||||||
|
const servicePrefix = 'Course'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'CourseUpdate', |
||||||
|
servicePrefix, |
||||||
|
mixins: [UpdateMixin], |
||||||
|
components: { |
||||||
|
Loading, |
||||||
|
Toolbar, |
||||||
|
CourseForm |
||||||
|
}, |
||||||
|
|
||||||
|
computed: { |
||||||
|
...mapFields('course', { |
||||||
|
deleteLoading: 'isLoading', |
||||||
|
isLoading: 'isLoading', |
||||||
|
error: 'error', |
||||||
|
updated: 'updated', |
||||||
|
violations: 'violations' |
||||||
|
}), |
||||||
|
...mapGetters('course', ['find']) |
||||||
|
|
||||||
|
}, |
||||||
|
|
||||||
|
methods: { |
||||||
|
...mapActions('course', { |
||||||
|
createReset: 'resetCreate', |
||||||
|
deleteItem: 'del', |
||||||
|
delReset: 'resetDelete', |
||||||
|
retrieve: 'load', |
||||||
|
update: 'update', |
||||||
|
updateReset: 'resetUpdate' |
||||||
|
}) |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,45 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<Toolbar :handle-submit="onSendForm" :handle-reset="resetForm"></Toolbar> |
||||||
|
<CourseCategoryForm ref="createForm" :values="item" :errors="violations" /> |
||||||
|
<Loading :visible="isLoading" /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { mapActions } from 'vuex'; |
||||||
|
import { createHelpers } from 'vuex-map-fields'; |
||||||
|
import CourseCategoryForm from '../../components/coursecategory/Form'; |
||||||
|
import Loading from '../../components/Loading'; |
||||||
|
import Toolbar from '../../components/Toolbar'; |
||||||
|
import CreateMixin from '../../mixins/CreateMixin'; |
||||||
|
|
||||||
|
const servicePrefix = 'CourseCategory'; |
||||||
|
|
||||||
|
const { mapFields } = createHelpers({ |
||||||
|
getterType: 'coursecategory/getField', |
||||||
|
mutationType: 'coursecategory/updateField' |
||||||
|
}); |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'CourseCategoryCreate', |
||||||
|
servicePrefix, |
||||||
|
mixins: [CreateMixin], |
||||||
|
components: { |
||||||
|
Loading, |
||||||
|
Toolbar, |
||||||
|
CourseCategoryForm |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
item: {} |
||||||
|
}; |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
...mapFields(['error', 'isLoading', 'created', 'violations']) |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
...mapActions('coursecategory', ['create', 'reset']) |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,104 @@ |
|||||||
|
<template> |
||||||
|
<div class="coursecategory-list"> |
||||||
|
<Toolbar :handle-add="addHandler" /> |
||||||
|
|
||||||
|
<v-container grid-list-xl fluid> |
||||||
|
<v-layout row wrap> |
||||||
|
<v-flex sm12> |
||||||
|
<h1>CourseCategory List</h1> |
||||||
|
</v-flex> |
||||||
|
<v-flex lg12> |
||||||
|
<DataFilter :handle-filter="onSendFilter" :handle-reset="resetFilter"> |
||||||
|
<CourseCategoryFilterForm |
||||||
|
ref="filterForm" |
||||||
|
:values="filters" |
||||||
|
slot="filter" |
||||||
|
/> |
||||||
|
</DataFilter> |
||||||
|
|
||||||
|
<br /> |
||||||
|
|
||||||
|
<v-data-table |
||||||
|
v-model="selected" |
||||||
|
:headers="headers" |
||||||
|
:items="items" |
||||||
|
:items-per-page.sync="options.itemsPerPage" |
||||||
|
:loading="isLoading" |
||||||
|
:loading-text="$t('Loading...')" |
||||||
|
:options.sync="options" |
||||||
|
:server-items-length="totalItems" |
||||||
|
class="elevation-1" |
||||||
|
item-key="@id" |
||||||
|
show-select |
||||||
|
@update:options="onUpdateOptions" |
||||||
|
> |
||||||
|
|
||||||
|
<ActionCell |
||||||
|
slot="item.action" |
||||||
|
slot-scope="props" |
||||||
|
:handle-show="() => showHandler(props.item)" |
||||||
|
:handle-edit="() => editHandler(props.item)" |
||||||
|
:handle-delete="() => deleteHandler(props.item)" |
||||||
|
></ActionCell> |
||||||
|
</v-data-table> |
||||||
|
</v-flex> |
||||||
|
</v-layout> |
||||||
|
</v-container> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { mapActions, mapGetters } from 'vuex'; |
||||||
|
import { mapFields } from 'vuex-map-fields'; |
||||||
|
import ListMixin from '../../mixins/ListMixin'; |
||||||
|
import ActionCell from '../../components/ActionCell'; |
||||||
|
import CourseCategoryFilterForm from '../../components/coursecategory/Filter'; |
||||||
|
import DataFilter from '../../components/DataFilter'; |
||||||
|
import Toolbar from '../../components/Toolbar'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'CourseCategoryList', |
||||||
|
servicePrefix: 'CourseCategory', |
||||||
|
mixins: [ListMixin], |
||||||
|
components: { |
||||||
|
Toolbar, |
||||||
|
ActionCell, |
||||||
|
CourseCategoryFilterForm, |
||||||
|
DataFilter |
||||||
|
}, |
||||||
|
data() { |
||||||
|
return { |
||||||
|
headers: [ |
||||||
|
{ text: 'name', value: 'name' }, |
||||||
|
{ text: 'code', value: 'code' }, |
||||||
|
//{ text: 'description', value: 'description' }, |
||||||
|
{ |
||||||
|
text: 'Actions', |
||||||
|
value: 'action', |
||||||
|
sortable: false |
||||||
|
} |
||||||
|
], |
||||||
|
selected: [] |
||||||
|
}; |
||||||
|
}, |
||||||
|
computed: { |
||||||
|
...mapGetters('coursecategory', { |
||||||
|
items: 'list' |
||||||
|
}), |
||||||
|
...mapFields('coursecategory', { |
||||||
|
deletedItem: 'deleted', |
||||||
|
error: 'error', |
||||||
|
isLoading: 'isLoading', |
||||||
|
resetList: 'resetList', |
||||||
|
totalItems: 'totalItems', |
||||||
|
view: 'view' |
||||||
|
}) |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
...mapActions('coursecategory', { |
||||||
|
getPage: 'fetchAll', |
||||||
|
deleteItem: 'del' |
||||||
|
}) |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,87 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<Toolbar :handle-edit="editHandler" :handle-delete="del"> |
||||||
|
<template slot="left"> |
||||||
|
<v-toolbar-title v-if="item">{{ |
||||||
|
`${$options.servicePrefix} ${item['@id']}` |
||||||
|
}}</v-toolbar-title> |
||||||
|
</template> |
||||||
|
</Toolbar> |
||||||
|
|
||||||
|
<br /> |
||||||
|
|
||||||
|
<div v-if="item" class="table-coursecategory-show"> |
||||||
|
<v-simple-table> |
||||||
|
<template slot="default"> |
||||||
|
<thead> |
||||||
|
<tr> |
||||||
|
<th>Field</th> |
||||||
|
<th>Value</th> |
||||||
|
|
||||||
|
<th>Field</th> |
||||||
|
<th>Value</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody> |
||||||
|
<tr> |
||||||
|
<td><strong>{{ $t('name') }}</strong></td> |
||||||
|
<td> |
||||||
|
{{ item['name'] }} |
||||||
|
</td> |
||||||
|
|
||||||
|
<td><strong>{{ $t('code') }}</strong></td> |
||||||
|
<td> |
||||||
|
{{ item['code'] }} |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
|
||||||
|
<tr> |
||||||
|
<td><strong>{{ $t('description') }}</strong></td> |
||||||
|
<td> |
||||||
|
{{ item['description'] }} |
||||||
|
</td> |
||||||
|
|
||||||
|
<td></td> |
||||||
|
</tr> |
||||||
|
|
||||||
|
</tbody> |
||||||
|
</template> |
||||||
|
</v-simple-table> |
||||||
|
</div> |
||||||
|
|
||||||
|
<Loading :visible="isLoading" /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { mapActions, mapGetters } from 'vuex'; |
||||||
|
import { mapFields } from 'vuex-map-fields'; |
||||||
|
import Loading from '../../components/Loading'; |
||||||
|
import ShowMixin from '../../mixins/ShowMixin'; |
||||||
|
import Toolbar from '../../components/Toolbar'; |
||||||
|
|
||||||
|
const servicePrefix = 'CourseCategory'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'CourseCategoryShow', |
||||||
|
servicePrefix, |
||||||
|
components: { |
||||||
|
Loading, |
||||||
|
Toolbar |
||||||
|
}, |
||||||
|
mixins: [ShowMixin], |
||||||
|
computed: { |
||||||
|
...mapFields('coursecategory', { |
||||||
|
isLoading: 'isLoading' |
||||||
|
}), |
||||||
|
...mapGetters('coursecategory', ['find']) |
||||||
|
}, |
||||||
|
methods: { |
||||||
|
...mapActions('coursecategory', { |
||||||
|
deleteItem: 'del', |
||||||
|
reset: 'resetShow', |
||||||
|
retrieve: 'load' |
||||||
|
}) |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,61 @@ |
|||||||
|
<template> |
||||||
|
<div> |
||||||
|
<Toolbar |
||||||
|
:handle-submit="onSendForm" |
||||||
|
:handle-reset="resetForm" |
||||||
|
:handle-delete="del" |
||||||
|
/> |
||||||
|
<CourseCategoryForm |
||||||
|
ref="updateForm" |
||||||
|
v-if="item" |
||||||
|
:values="item" |
||||||
|
:errors="violations" |
||||||
|
/> |
||||||
|
<Loading :visible="isLoading || deleteLoading" /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { mapActions, mapGetters } from 'vuex'; |
||||||
|
import { mapFields } from 'vuex-map-fields'; |
||||||
|
import CourseCategoryForm from '../../components/coursecategory/Form.vue'; |
||||||
|
import Loading from '../../components/Loading'; |
||||||
|
import Toolbar from '../../components/Toolbar'; |
||||||
|
import UpdateMixin from '../../mixins/UpdateMixin'; |
||||||
|
|
||||||
|
const servicePrefix = 'CourseCategory'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'CourseCategoryUpdate', |
||||||
|
servicePrefix, |
||||||
|
mixins: [UpdateMixin], |
||||||
|
components: { |
||||||
|
Loading, |
||||||
|
Toolbar, |
||||||
|
CourseCategoryForm |
||||||
|
}, |
||||||
|
|
||||||
|
computed: { |
||||||
|
...mapFields('coursecategory', { |
||||||
|
deleteLoading: 'isLoading', |
||||||
|
isLoading: 'isLoading', |
||||||
|
error: 'error', |
||||||
|
updated: 'updated', |
||||||
|
violations: 'violations' |
||||||
|
}), |
||||||
|
...mapGetters('coursecategory', ['find']) |
||||||
|
|
||||||
|
}, |
||||||
|
|
||||||
|
methods: { |
||||||
|
...mapActions('coursecategory', { |
||||||
|
createReset: 'resetCreate', |
||||||
|
deleteItem: 'del', |
||||||
|
delReset: 'resetDelete', |
||||||
|
retrieve: 'load', |
||||||
|
update: 'update', |
||||||
|
updateReset: 'resetUpdate' |
||||||
|
}) |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
module.exports = { |
||||||
|
pluginOptions: { |
||||||
|
quasar: { |
||||||
|
importStrategy: 'manual', |
||||||
|
rtlSupport: false |
||||||
|
} |
||||||
|
}, |
||||||
|
transpileDependencies: [ |
||||||
|
'quasar' |
||||||
|
] |
||||||
|
} |
||||||
Loading…
Reference in new issue