mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 12:20:31 +00:00
Custom fields feature.
This commit is contained in:
58
client/src/components/SvgIcon/index.vue
Normal file
58
client/src/components/SvgIcon/index.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
|
||||
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
|
||||
<use :xlink:href="iconName" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SvgIcon',
|
||||
props: {
|
||||
iconClass: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isExternal() {
|
||||
return Boolean(this.iconClass)
|
||||
},
|
||||
iconName() {
|
||||
return `#icon-${this.iconClass}`;
|
||||
},
|
||||
svgClass() {
|
||||
if (this.className) {
|
||||
return `svg-icon ${this.className}`;
|
||||
}
|
||||
return 'svg-icon';
|
||||
},
|
||||
styleExternalIcon() {
|
||||
return {
|
||||
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
|
||||
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.svg-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.svg-external-icon {
|
||||
background-color: currentColor;
|
||||
mask-size: cover!important;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
@@ -2,4 +2,21 @@ export default {
|
||||
login: 'Login',
|
||||
password: 'Password',
|
||||
username_password: 'Username or Email',
|
||||
with_your_account: 'With your Ratteb account.',
|
||||
if_you_forget_your_password: 'If you forgot your password, well, enter your Moosher ID than secret key to reset your password.',
|
||||
sign_in: 'Sign in',
|
||||
dashboard: 'Dashboard',
|
||||
reset_the_password: 'Reset the Password',
|
||||
enter_your_new_password: 'Enter You New Password',
|
||||
reset_password_link_is_invalid: 'The link is invalid or expired. Please reset your password again.',
|
||||
your_password_has_been_changed: 'Success Your password has been changed.',
|
||||
username_password_do_not_match: 'The username and password you entered did not match our records.',
|
||||
the_account_suspended_please_contact: 'This account is suspended, Please contact the administrator.',
|
||||
email_has_been_sent: 'An e-mail has been sent to {email} with further instructions.',
|
||||
there_is_no_email_with_that_address: 'There is no email with that address, Please try again.',
|
||||
reset_your_password: 'Reset Your Password',
|
||||
return_to_login: 'Return to Login',
|
||||
forget_password: 'Forget your password?',
|
||||
reset_password: 'Reset Password',
|
||||
email_password_do_not_match: 'Email and password do not match.',
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import Vue from 'vue';
|
||||
import {Form, FormItem, Input} from 'element-ui';
|
||||
import {Form, FormItem, Input, Tabs, TabPane, Button, Alert} from 'element-ui';
|
||||
import App from '@/App';
|
||||
import router from '@/router';
|
||||
import store from '@/store';
|
||||
import '@/plugins/icons';
|
||||
|
||||
// Plugins
|
||||
import '@/plugins/i18n';
|
||||
@@ -12,6 +13,10 @@ Vue.config.productionTip = false;
|
||||
Vue.use(Form);
|
||||
Vue.use(FormItem);
|
||||
Vue.use(Input);
|
||||
Vue.use(Tabs);
|
||||
Vue.use(TabPane);
|
||||
Vue.use(Button);
|
||||
Vue.use(Alert);
|
||||
|
||||
const app = new Vue({
|
||||
el: '#app',
|
||||
|
||||
20
client/src/mixins/Reloadable.js
Normal file
20
client/src/mixins/Reloadable.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'reloadable',
|
||||
created() {
|
||||
this.$eventBus.$on('reload-data', this.onReloadData);
|
||||
},
|
||||
methods: {
|
||||
async onReloadData() {
|
||||
this.$nprogress.start();
|
||||
await this.reloadData();
|
||||
this.$nprogress.done();
|
||||
},
|
||||
|
||||
reloadData() {},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$eventBus.$off('reload-data', this.onReloadData);
|
||||
},
|
||||
});
|
||||
@@ -1,44 +0,0 @@
|
||||
<template>
|
||||
<div class="auth-page">
|
||||
<div class="auth-page__logo">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="auth-page__card">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'auth-warpper',
|
||||
beforeRouteEnter(to, from, next) {
|
||||
document.body.classList.add('page-auth');
|
||||
next();
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
document.body.classList.remove('page-auth');
|
||||
next();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body.page-auth{
|
||||
background: red;
|
||||
}
|
||||
|
||||
.auth-page{
|
||||
width: 600px;
|
||||
|
||||
&__logo{
|
||||
|
||||
}
|
||||
|
||||
&__card{
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
207
client/src/pages/Auth/AuthContainer.vue
Normal file
207
client/src/pages/Auth/AuthContainer.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<div class="page-auth page-auth--login">
|
||||
<div class="page-auth__content-side">
|
||||
<div class="page-auth__content-header">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="page-auth__content-form">
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view></router-view>
|
||||
</transition>
|
||||
</div>
|
||||
|
||||
<AuthFooter></AuthFooter>
|
||||
</div>
|
||||
|
||||
<AuthMedia></AuthMedia>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AuthMedia from '@/pages/Auth/AuthMedia.vue';
|
||||
import AuthFooter from '@/pages/Auth/AuthFooter.vue';
|
||||
|
||||
export default {
|
||||
name: 'auth-container',
|
||||
components: {
|
||||
AuthMedia,
|
||||
AuthFooter,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page-auth{
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
|
||||
&__content-side{
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
// @include media-breakpoint-up('lg'){
|
||||
width: 62%;
|
||||
// }
|
||||
|
||||
.remember-forget-wrapper{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 20px;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.form-note{
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.btn-primary{
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&__content{
|
||||
|
||||
&-header{
|
||||
max-width: 440px;
|
||||
width: 100%;
|
||||
padding: 0 30px;
|
||||
margin: 0 auto;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 10px;
|
||||
|
||||
// @include media-breakpoint-down('xs'){
|
||||
// padding-top: 25px;
|
||||
// padding-left: 25px;
|
||||
// padding-right: 25px;
|
||||
// }
|
||||
|
||||
.icon-logo{
|
||||
// @include media-breakpoint-down('xs'){
|
||||
// width: 140px;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
&-title{
|
||||
font-size: 42px;
|
||||
font-weight: 300;
|
||||
letter-spacing: -0.025em;
|
||||
color: #253992;
|
||||
line-height: 1.2;
|
||||
padding-bottom: 23px;
|
||||
font-size: 2.4em;
|
||||
|
||||
// @include media-breakpoint-down('xs'){
|
||||
// font-size: 2.2rem;
|
||||
// }
|
||||
|
||||
small{
|
||||
font-size: 0.46em;
|
||||
display: block;
|
||||
font-weight: 400;
|
||||
color: #495463;
|
||||
letter-spacing: normal;
|
||||
margin-top: 10px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.text-success{
|
||||
color: #00d285;
|
||||
font-size: 0.41em;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&-form{
|
||||
width: 440px;
|
||||
max-width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 40px 30px 50px;
|
||||
|
||||
// @include media-breakpoint-down('xs'){
|
||||
// padding-left: 25px;
|
||||
// padding-right: 25px;
|
||||
// }
|
||||
}
|
||||
|
||||
&-footer{
|
||||
margin: 0 auto;
|
||||
width: 440px;
|
||||
max-width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 0 30px;
|
||||
padding-bottom: 25px;
|
||||
|
||||
.footer-links{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
li{
|
||||
padding: 2px 15px;
|
||||
font-size: 13px;
|
||||
color: #758698;
|
||||
display: inline-block;
|
||||
|
||||
&:first-child{
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&:last-child{
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
a{
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
|
||||
&:hover{
|
||||
color: #2c80ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Media Side **/
|
||||
&__media-side{
|
||||
display: flex;
|
||||
width: 38%;
|
||||
background-image: url('/images/cover.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
background-position: 50% 0;
|
||||
|
||||
// @include media-breakpoint-down('md'){
|
||||
// width: 38%;
|
||||
// }
|
||||
|
||||
// @include media-breakpoint-down('xs'){
|
||||
// display: none;
|
||||
// }
|
||||
}
|
||||
|
||||
&__media-overlay{
|
||||
position: absolute;
|
||||
background: #202141;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: 0.65;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
15
client/src/pages/Auth/AuthFooter.vue
Normal file
15
client/src/pages/Auth/AuthFooter.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="page-auth__content-footer">
|
||||
<ul class="footer-links">
|
||||
<li><a href="#">{{ $t('privacy_policy') }}</a></li>
|
||||
<li><a href="#">{{ $t('terms_and_conditions') }}</a></li>
|
||||
<li>© 2019 Moosher.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'auth-footer',
|
||||
}
|
||||
</script>
|
||||
11
client/src/pages/Auth/AuthMedia.vue
Normal file
11
client/src/pages/Auth/AuthMedia.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="page-auth__media-side">
|
||||
<div class="page-auth__media-overlay"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'auth-media',
|
||||
};
|
||||
</script>
|
||||
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<div id="reset-password">
|
||||
<h2 class="page-auth__content-title">
|
||||
{{ $t('reset_your_password') }}
|
||||
<small>{{ $t('if_you_forget_your_password') }}</small>
|
||||
</h2>
|
||||
|
||||
<el-form ref="form" class="form-container">
|
||||
<el-form-item :label="$t('username_password')" prop="title">
|
||||
<el-input v-model="form.crediential" :maxlength="100" name="name" required />
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<h2 class="page-auth__content-title">
|
||||
{{ $t('sign_in') }}
|
||||
<small>{{ $t('with_your_account') }}</small>
|
||||
</h2>
|
||||
|
||||
<el-alert :active="isCredentialError" :type="'error'">
|
||||
{{ $t('email_password_do_not_match') }}
|
||||
</el-alert>
|
||||
|
||||
<el-alert :isActive="isInactiveError" :type="'error'">
|
||||
{{ $t('the_account_suspended_please_contact') }}
|
||||
</el-alert>
|
||||
|
||||
<el-alert :isActive="isResetPasswordSuccess" type="success">
|
||||
{{ $t('your_password_has_been_changed') }}
|
||||
</el-alert>
|
||||
|
||||
<el-form ref="form" class="form-container">
|
||||
<el-form-item :label="$t('username_password')" prop="title">
|
||||
<el-input v-model="form.crediential" :maxlength="100" name="name" required />
|
||||
@@ -37,6 +54,17 @@ export default {
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isCredentialError() {
|
||||
return false;
|
||||
},
|
||||
isInactiveError() {
|
||||
return false;
|
||||
},
|
||||
isResetPasswordSuccess() {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['login']),
|
||||
|
||||
@@ -47,7 +75,7 @@ export default {
|
||||
const form = {};
|
||||
|
||||
this.login({ form }).then(() => {
|
||||
|
||||
this.$route.push({ name: 'dashboard.home' });
|
||||
}).catch((error) => {
|
||||
const { response } = error;
|
||||
const { data } = response;
|
||||
|
||||
14
client/src/pages/Dashboard/Accounts/AccountForm.vue
Normal file
14
client/src/pages/Dashboard/Accounts/AccountForm.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div class="">
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'account-form',
|
||||
data() {
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'products-list',
|
||||
}
|
||||
name: 'accounts-list',
|
||||
};
|
||||
</script>
|
||||
34
client/src/pages/Dashboard/Items/ItemForm.vue
Normal file
34
client/src/pages/Dashboard/Items/ItemForm.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="item-form" id="item-form">
|
||||
<el-form ref="form" class="form-container">
|
||||
<el-form-item :label="$t('item_name')" prop="title">
|
||||
<el-input v-model="form.name" :maxlength="100" name="name" required />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('SKU')" prop="title">
|
||||
<el-input v-model="form.SKU" :maxlength="100" name="SKU" required />
|
||||
</el-form-item>
|
||||
|
||||
<el-button type="primary">{{ $t('login') }}</el-button>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'item-form',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
name: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
||||
onSubmit() {
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
55
client/src/pages/Dashboard/Items/ItemsDatatable.vue
Normal file
55
client/src/pages/Dashboard/Items/ItemsDatatable.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
|
||||
<template>
|
||||
<el-table v-loading="isLoading" :data="items.list" border fit highlight-current-row>
|
||||
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import Reloadable from '@/mixins/Reloadable';
|
||||
|
||||
const STATE = {
|
||||
LOADING: 1,
|
||||
};
|
||||
export default {
|
||||
name: 'items-datatable',
|
||||
mixins: [Reloadable],
|
||||
data() {
|
||||
return {
|
||||
current_state: 0,
|
||||
page: 1,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
items: 'getItems',
|
||||
}),
|
||||
isLoading() {
|
||||
return this.current_state === STATE.LOADING;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.current_state = STATE.LOADING;
|
||||
this.fetchData();
|
||||
this.current_state = 0;
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchItems']),
|
||||
|
||||
/**
|
||||
* Handle the reload data.
|
||||
*/
|
||||
reloadData() {
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the fetch data of datatable.
|
||||
*/
|
||||
async fetchData() {
|
||||
await this.fetchItems({ page: this.page });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
27
client/src/pages/Dashboard/Items/ItemsList.vue
Normal file
27
client/src/pages/Dashboard/Items/ItemsList.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<el-card>
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||
<el-tab-pane label="User" name="first" :lazy="true">User</el-tab-pane>
|
||||
<el-tab-pane label="Config" name="second" :lazy="true">Config</el-tab-pane>
|
||||
<el-tab-pane label="Role" name="third" :lazy="true">Role</el-tab-pane>
|
||||
<el-tab-pane label="Task" name="fourth" :lazy="true">Task</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'products-list',
|
||||
data() {
|
||||
return {
|
||||
activeName: 'first',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleClick(tab, event) {
|
||||
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<div class="">
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'item-category-form',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table v-loading="isLoading" :data="items.list" border fit highlight-current-row>
|
||||
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'items-categories',
|
||||
data() {
|
||||
return {
|
||||
current_state: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
categories: 'getItemsCategories',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchItemsCategories']),
|
||||
|
||||
/**
|
||||
* Handle the reload data of the current view.
|
||||
*/
|
||||
async reloadData() {
|
||||
await this.fetchData();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle the fetch data of the datatable.
|
||||
*/
|
||||
async fetchData() {
|
||||
await this.fetchItemsCategories();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
0
client/src/pages/Dashboard/Roles/RoleForm.vue
Normal file
0
client/src/pages/Dashboard/Roles/RoleForm.vue
Normal file
0
client/src/pages/Dashboard/Roles/RolesList.vue
Normal file
0
client/src/pages/Dashboard/Roles/RolesList.vue
Normal file
0
client/src/pages/Dashboard/Users/UserForm.vue
Normal file
0
client/src/pages/Dashboard/Users/UserForm.vue
Normal file
14
client/src/pages/Dashboard/Users/UsersList.vue
Normal file
14
client/src/pages/Dashboard/Users/UsersList.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div>Users List</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'users-list',
|
||||
afterRouteEnter(to, from, next) {
|
||||
debugger;
|
||||
this.$store.commit('setPageTitle', this.$t('users_list'));
|
||||
next();
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -3,9 +3,7 @@ import Vue from 'vue';
|
||||
export default {
|
||||
|
||||
get(resource, params) {
|
||||
return Vue.axios.get(`api/${resource}`, params).catch((error) => {
|
||||
throw new Error(`[Moosher] ApiService ${error}`);
|
||||
});
|
||||
return Vue.axios.get(`api/${resource}`, params);
|
||||
},
|
||||
|
||||
post(resource, params) {
|
||||
@@ -21,8 +19,6 @@ export default {
|
||||
},
|
||||
|
||||
delete(resource) {
|
||||
return Vue.axios.delete(`api/${resource}`).catch((error) => {
|
||||
throw new Error(`[Moosher] ApiService ${error}`);
|
||||
});
|
||||
return Vue.axios.delete(`api/${resource}`);
|
||||
}
|
||||
};
|
||||
|
||||
9
client/src/plugins/icons.js
Normal file
9
client/src/plugins/icons.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import Vue from 'vue';
|
||||
import SvgIcon from '@/components/SvgIcon';
|
||||
|
||||
// register globally
|
||||
Vue.component('svg-icon', SvgIcon)
|
||||
|
||||
const req = require.context('../../static/icons', false, /\.svg$/);
|
||||
const requireAll = requireContext => requireContext.keys().map(requireContext);
|
||||
requireAll(req);
|
||||
@@ -3,8 +3,8 @@ const routes = [
|
||||
{
|
||||
name: 'auth',
|
||||
path: '/',
|
||||
component: () => import(/* webpackChunkName: "auth" */
|
||||
'@/pages/Auth/Auth.vue'),
|
||||
component: () => import(/* webpackChunkName: "auth_container" */
|
||||
'@/pages/Auth/AuthContainer.vue'),
|
||||
children: [
|
||||
{
|
||||
name: 'login',
|
||||
@@ -32,6 +32,56 @@ const routes = [
|
||||
component: () => import(/* webpackChunkName: "dashboard_home" */
|
||||
'@/pages/Dashboard/Home.vue'),
|
||||
},
|
||||
|
||||
/**
|
||||
* Items. (Products/Services).
|
||||
* --------------------------------
|
||||
*/
|
||||
{
|
||||
name: 'dashboard.items.list',
|
||||
path: '/items',
|
||||
component: () => import(/* webpackChunkName: "items_list" */
|
||||
'@/pages/Dashboard/Items/ItemsList.vue'),
|
||||
accessControl: { resource: 'items', permissions: ['view'] },
|
||||
},
|
||||
{
|
||||
name: 'dashboard.items.new',
|
||||
path: '/items/new',
|
||||
component: () => import(/* webpackChunkName: "items_form" */
|
||||
'@/pages/Dashboard/Items/ItemForm.vue'),
|
||||
accessControl: { resource: 'items', permissions: ['create'] },
|
||||
},
|
||||
|
||||
/**
|
||||
* Accounts
|
||||
* ---------------------------
|
||||
*/
|
||||
{
|
||||
name: 'dashboard.accounts.list',
|
||||
path: '/accounts/list',
|
||||
component: () => import(/* webpackChunkName: "accounts_list" */
|
||||
'@/pages/Dashboard/Accounts/AccountsList.vue'),
|
||||
accessControl: { resource: 'accounts', permissions: ['view'] },
|
||||
},
|
||||
|
||||
/**
|
||||
* Users.
|
||||
* --------------------------------
|
||||
*/
|
||||
{
|
||||
name: 'dashboard.users.list',
|
||||
path: '/users',
|
||||
component: () => import(/* webpackChunkName: "users_list" */
|
||||
'@/pages/Dashboard/Users/UsersList.vue'),
|
||||
accessControl: { resource: 'users', permissions: ['view'] },
|
||||
},
|
||||
{
|
||||
name: 'dasboard.user.new',
|
||||
path: '/users',
|
||||
component: () => import(/* webpackChunkName: "user_form" */
|
||||
'@/pages/Dashboard/Users/UserForm.vue'),
|
||||
accessControl: { resource: 'users', permissions: ['create'] },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -7,6 +7,9 @@ const state = {
|
||||
|
||||
pageTitle: 'Welcome',
|
||||
actions: [],
|
||||
|
||||
contentState: 0,
|
||||
sidebarOpened: true,
|
||||
};
|
||||
|
||||
const getters = {
|
||||
@@ -23,4 +26,16 @@ const actions = {
|
||||
},
|
||||
};
|
||||
|
||||
export default { state, actions, getters };
|
||||
const mutations = {
|
||||
toggleSidebar() {
|
||||
state.sidebarOpened = !state.sidebarOpened;
|
||||
|
||||
if (state.sidebarOpened) {
|
||||
localStorage.set('sidebarStatus', 1)
|
||||
} else {
|
||||
localStorage.set('sidebarStatus', 0)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export default { state, actions, mutations, getters };
|
||||
|
||||
@@ -33,7 +33,7 @@ const actions = {
|
||||
return ApiService.post('auth/send_reset_password', { email });
|
||||
},
|
||||
|
||||
newPassword(, { form }) {
|
||||
newPassword(null, { form }) {
|
||||
return ApiService.post('auth/new_password', form);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -22,9 +22,7 @@ const actions = {
|
||||
commit('setItems', data);
|
||||
|
||||
if (count) {
|
||||
commit('setSidebarItemCount', {
|
||||
name: 'customers', count,
|
||||
});
|
||||
commit('setSidebarItemCount', { name: 'customers', count });
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
28
client/src/store/modules/errorLog.js
Normal file
28
client/src/store/modules/errorLog.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const state = {
|
||||
logs: [],
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
ADD_ERROR_LOG: (s, log) => {
|
||||
s.logs.push(log);
|
||||
},
|
||||
CLEAR_ERROR_LOG: (s) => {
|
||||
s.logs.splice(0);
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
addErrorLog({ commit }, log) {
|
||||
commit('ADD_ERROR_LOG', log);
|
||||
},
|
||||
clearErrorLog({ commit }) {
|
||||
commit('CLEAR_ERROR_LOG');
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
mutations,
|
||||
actions,
|
||||
};
|
||||
@@ -3,11 +3,15 @@ import ApiService from '@/plugins/api-service';
|
||||
const state = {
|
||||
list: {},
|
||||
details: [],
|
||||
categories: {},
|
||||
categoriesDetails: [],
|
||||
};
|
||||
|
||||
const getters = {
|
||||
getItems: s => s.list,
|
||||
getItem: s => id => s.details.find(i => i.id === id),
|
||||
getItemsCategories: s => s.categories,
|
||||
getItemCategory: s => id => s.categoriesDetails.find(i => i.id === id),
|
||||
};
|
||||
|
||||
const actions = {
|
||||
@@ -54,6 +58,41 @@ const actions = {
|
||||
async updateItem({}, { form, id }) {
|
||||
return ApiService.post(`items/${id}`, form);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches items categories paged list.
|
||||
*/
|
||||
async fetchItemsCategories({}, { query }) {
|
||||
return ApiService.get('/items_categories', { params: query });
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch details of the given item category.
|
||||
*/
|
||||
async fetchItemCategory({}, { id }) {
|
||||
return ApiService.get(`/item/${id}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete the given item category.
|
||||
*/
|
||||
async deleteItemCategory({}, { id }) {
|
||||
return ApiService.delete(`/items_categories/${id}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Post a new item category.
|
||||
*/
|
||||
async newItemCategory({}, { form }) {
|
||||
return ApiService.post('/items_categories', form);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update details of the given item category.
|
||||
*/
|
||||
async updateItemCategory({}, { id, form }) {
|
||||
return ApiService.post(`/items_categories/${id}`, form);
|
||||
},
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
@@ -66,6 +105,15 @@ const mutations = {
|
||||
s.details = s.details.filter(i => i.id !== item.id);
|
||||
s.details.push(item);
|
||||
},
|
||||
|
||||
setItemCategories(s, categories) {
|
||||
s.categories = categories;
|
||||
},
|
||||
|
||||
setItemCategory(s, category) {
|
||||
s.categoriesDetails = s.categoriesDetails.filter(i => i.id !== category.id);
|
||||
s.categoriesDetails.push(category);
|
||||
},
|
||||
};
|
||||
|
||||
export default { state, actions, mutations, getters };
|
||||
|
||||
0
client/src/store/modules/roles.js
Normal file
0
client/src/store/modules/roles.js
Normal file
@@ -9,7 +9,7 @@ const state = {
|
||||
},
|
||||
{
|
||||
name: 'Products',
|
||||
to: 'dashboard.home',
|
||||
to: 'dashboard.items.list',
|
||||
},
|
||||
{
|
||||
name: 'Customers',
|
||||
@@ -23,12 +23,54 @@ const state = {
|
||||
name: 'Reports',
|
||||
to: 'dashboard.home',
|
||||
},
|
||||
{
|
||||
name: 'Users',
|
||||
to: 'dashboard.users.list',
|
||||
children: {
|
||||
name: 'New User',
|
||||
to: 'dashboard.user.new',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Accounting',
|
||||
to: 'dashboard.accounts.list',
|
||||
},
|
||||
],
|
||||
|
||||
quickActions: [
|
||||
{
|
||||
route: 'dashboard.items.list',
|
||||
actions: [
|
||||
{
|
||||
dialog: 'global-search',
|
||||
label: 'Search',
|
||||
icon: 'search',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
route: 'dashboard.items.list',
|
||||
actions: [
|
||||
{
|
||||
dialog: 'test',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const getters = {
|
||||
getSidebarItems: s => s.items,
|
||||
getSidebarItem: s => name => s.items.find(item => item.name === name),
|
||||
|
||||
getAllQuickActions: s => s.quickActions,
|
||||
getQuickActions: s => (route) => {
|
||||
const foundDefault = s.quickActions.find(q => q.default === true);
|
||||
const found = s.quickActions.find(q => q.route === route);
|
||||
const defaultActions = foundDefault ? foundDefault.actions : [];
|
||||
|
||||
return found ? [...defaultActions, found.actions] : defaultActions;
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
|
||||
38
client/src/utils.js
Normal file
38
client/src/utils.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const title = 'Ratteb';
|
||||
|
||||
export const getPageTitle = (pageTitle) => {
|
||||
if (pageTitle) {
|
||||
return `${pageTitle} - ${title}`;
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
// const clipboardSuccess = () => {
|
||||
// Vue.prototype.$message({
|
||||
// message: 'Copy successfully',
|
||||
// type: 'success',
|
||||
// duration: 1500,
|
||||
// });
|
||||
// };
|
||||
|
||||
// const clipboardError = () => {
|
||||
// Vue.prototype.$message({
|
||||
// message: 'Copy failed',
|
||||
// type: 'error',
|
||||
// });
|
||||
// };
|
||||
|
||||
export const handleClipboard = (text, event) => {
|
||||
// const clipboard = new Clipboard(event.target, {
|
||||
// text: () => text
|
||||
// })
|
||||
// clipboard.on('success', () => {
|
||||
// clipboardSuccess()
|
||||
// clipboard.destroy()
|
||||
// })
|
||||
// clipboard.on('error', () => {
|
||||
// clipboardError()
|
||||
// clipboard.destroy()
|
||||
// })
|
||||
// clipboard.onClick(event)
|
||||
}
|
||||
14
client/src/views/Dialogs/SearchDialog.vue
Normal file
14
client/src/views/Dialogs/SearchDialog.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'search-dialog',
|
||||
data() {
|
||||
return {
|
||||
current_state: 0,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="sidebar__menu-item" :class="computedClasses">
|
||||
<router-link class="sidebar__menu-item-anchor" :to="to">
|
||||
<router-link class="sidebar__menu-item-anchor" :to="{name: to}">
|
||||
<span class="title">{{ name }}</span>
|
||||
<span v-if="count" class="count">{{ count }}</span>
|
||||
</router-link>
|
||||
@@ -18,6 +18,7 @@ export default {
|
||||
to: String,
|
||||
icon: String,
|
||||
children: Array,
|
||||
count: [Number, Boolean],
|
||||
},
|
||||
computed: {
|
||||
computedClasses() {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
v-for="(item, index) in sidebarItems" :key="index"
|
||||
:to="item.to"
|
||||
:name="item.name"
|
||||
:count="item.count"
|
||||
:count="item.count || false"
|
||||
:children="item.children"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
</div>
|
||||
|
||||
<div class="topbar__actions">
|
||||
|
||||
<div class="topbar__actions-list">
|
||||
<div v-for="(action, index) in actions" :key="index">
|
||||
<button @click.prevent="onClickItem(action)">{{ action.label }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -22,8 +26,18 @@ export default {
|
||||
computed: {
|
||||
...mapGetters({
|
||||
pageTitle: 'getPageTitle',
|
||||
quickActions: 'getQuickActions',
|
||||
}),
|
||||
actions() {
|
||||
return this.quickActions(this.$route.name) || [];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
onClickItem(action) {
|
||||
return action;
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user