Budget
folder to the List
. In addition, rename the three components that are in this folder. Namely, the BudgetList
will now be named List
, the BudgetListHeader
will be called the ListHeader
, and the BudgetListBody — ListBody
. As a result, the folder and component files should look like the one below.List
and bring it to this form: <template> <section class="l-list-container"> <slot name="list-header"></slot> <slot name="list-body"></slot> </section> </template> <script> export default {} </script>
ListHeader
component ListHeader
and make the following changes to it: <template> <header class="l-list-header"> <div class="md-list-header white--text" v-if="headers != null" v-for="header in headers"> {{ header }} </div> </header> </template> <script> export default { props: ['headers'] } </script> <style lang="scss"> @import "./../../assets/styles"; .l-list-header { display: none; width: 100%; @media (min-width: 601px) { margin: 25px 0 0; display: flex; } .md-list-header { width: 100%; background-color: $background-color; border: 1px solid $border-color-input; padding: 0 15px; display: flex; height: 45px; align-items: center; justify-content: center; font-size: 22px; @media (min-width: 601px) { justify-content: flex-start; } } } </style>
props
). So we can reuse this component on other pages.ListBody
component: <template> <section class="l-list-body"> <div class="md-list-item" v-if="data != null" v-for="item in data"> <div class="md-info white--text" v-for="info in item" v-if="info != item._id"> {{ info }} </div> <div class="l-actions"> <v-btn small flat color="light-blue lighten-1"> <v-icon small>visibility</v-icon> </v-btn> <v-btn small flat color="yellow accent-1"> <v-icon>mode_edit</v-icon> </v-btn> <v-btn small flat color="red lighten-1"> <v-icon>delete_forever</v-icon> </v-btn> </div> </div> </section> </template> <script> export default { props: ['data'] } </script> <style lang="scss"> @import "./../../assets/styles"; .l-list-body { display: flex; flex-direction: column; .md-list-item { width: 100%; display: flex; flex-direction: column; margin: 15px 0; @media (min-width: 960px) { flex-direction: row; margin: 0; } .md-info { flex-basis: 25%; width: 100%; background-color: rgba(0, 175, 255, 0.45); border: 1px solid $border-color-input; padding: 0 15px; display: flex; height: 35px; align-items: center; justify-content: center; &:first-of-type, &:nth-of-type(2) { text-transform: capitalize; } &:nth-of-type(3) { text-transform: uppercase; } @media (min-width: 601px) { justify-content: flex-start; } } .l-actions { flex-basis: 25%; display: flex; background-color: rgba(0, 175, 255, 0.45); border: 1px solid $border-color-input; align-items: center; justify-content: center; .btn { min-width: 45px !important; margin: 0 5px !important; } } } } </style>
Home
component file and edit it: <template> <main class="l-home-page"> <app-header></app-header> <div class="l-home"> <h4 class="white--text text-xs-center my-0"> Focus Budget Manager </h4> <list> <list-header slot="list-header" :headers="budgetHeaders"></list-header> <list-body slot="list-body" :data="budgets"></list-body> </list> </div> <v-snackbar :timeout="timeout" bottom="bottom" color="red lighten-1" v-model="snackbar"> {{ message }} </v-snackbar> </main> </template> <script> import Axios from 'axios' import Authentication from '@/components/pages/Authentication' import ListHeader from './../List/ListHeader' import ListBody from './../List/ListBody' const BudgetManagerAPI = `http://${window.location.hostname}:3001` export default { components: { 'list-header': ListHeader, 'list-body': ListBody }, data () { return { budgets: [], clients: [], budgetHeaders: ['Client', 'Title', 'Status', 'Actions'], clientHeaders: ['Client', 'Email', 'Phone', 'Actions'], snackbar: false, timeout: 6000, message: '' } }, mounted () { this.getAllBudgets() }, methods: { getAllBudgets () { Axios.get(`${BudgetManagerAPI}/api/v1/budget`, { headers: { 'Authorization': Authentication.getAuthenticationHeader(this) }, params: { user_id: this.$cookie.get('user_id') } }).then(({data}) => { this.budgets = this.dataParser(data, '_id', 'client', 'title', 'state') }).catch(error => { this.snackbar = true this.message = error.message }) }, dataParser (targetedArray, ...options) { let parsedData = [] targetedArray.forEach(item => { let parsedItem = {} options.forEach(option => (parsedItem[option] = item[option])) parsedData.push(parsedItem) }) return parsedData } } } </script> <style lang="scss" scoped> @import "./../../assets/styles"; .l-home { background-color: $background-color; margin: 25px auto; padding: 15px; min-width: 272px; } </style>
snackbar
, which makes it possible to show error messages in case we fail to get the data. In addition, we added a new array for the component data, budgetHeaders
, as well as the data needed for the snackbar
.budgetHearders
to display list headings. In addition, we made some changes to the getAllBudgets
method: getAllBudgets () { Axios.get(`${BudgetManagerAPI}/api/v1/budget`, { headers: { 'Authorization': Authentication.getAuthenticationHeader(this) }, params: { user_id: this.$cookie.get('user_id') } }).then(({data}) => { this.budgets = this.dataParser(data, '_id', 'client', 'title', 'state') }).catch(error => { this.snackbar = true this.message = error.message }) },
dataParser (targetedArray, ...options) { let parsedData = [] targetedArray.forEach(item => { let parsedItem = {} options.forEach(option => (parsedItem[option] = item[option])) parsedData.push(parsedItem) }) return parsedData }
options
array using the extension operator.parsedItem
object.options
array; after its preparation is completed, it will be placed into the parsedData
array, which we return from this method.snackbar
.router
folder and open index.js
: import Vue from 'vue' import Router from 'vue-router' import * as Auth from '@/components/pages/Authentication' // Pages import Home from '@/components/pages/Home' import Authentication from '@/components/pages/Authentication/Authentication' // Global components import Header from '@/components/Header' import List from '@/components/List/List' // Register components Vue.component('app-header', Header) Vue.component('list', List) Vue.use(Router) const router = new Router({ routes: [ { path: '/', name: 'Home', components: { default: Home, header: Header, list: List } }, { path: '/login', name: 'Authentication', component: Authentication } ] }) router.beforeEach((to, from, next) => { if (to.path !== '/login') { if (Auth.default.user.authenticated) { next() } else { router.push('/login') } } else { next() } }) export default router
router.beforeEach
, since we are going to protect any route other than login
, we remove the meta
from the route of the Home
page.Home
page. Therefore, let’s go back to the Home
component and create a new array in the component data, giving it the name clients
, and also create the clientHeaders
array and the clientHeaders
logical variable: return { budgets: [], clients: [], budgetHeaders: ['Client', 'Title', 'Status', 'Actions'], clientHeaders: ['Client', 'Email', 'Phone', 'Actions'], budgetsVisible: true, snackbar: false, timeout: 6000, message: '' }
getAllClients () { Axios.get(`${BudgetManagerAPI}/api/v1/client`, { headers: { 'Authorization': Authentication.getAuthenticationHeader(this) }, params: { user_id: this.$cookie.get('user_id') } }).then(({data}) => { this.clients = this.dataParser(data, '_id', 'client', 'email', 'phone') }).catch(error => { this.snackbar = true this.message = error.message }) },
mounted () { this.getAllBudgets() this.getAllClients() },
Home
component: <template> <main class="l-home-page"> <app-header :budgetsVisible="budgetsVisible" @toggleVisibleData="budgetsVisible = !budgetsVisible"></app-header> <div class="l-home"> <h4 class="white--text text-xs-center my-0"> Focus Budget Manager </h4> <list> <list-header slot="list-header" :headers="budgetsVisible ? budgetHeaders : clientHeaders"></list-header> <list-body slot="list-body" :budgetsVisible="budgetsVisible" :data="budgetsVisible ? budgets : clients"> </list-body> </list> </div> <v-snackbar :timeout="timeout" bottom="bottom" color="red lighten-1" v-model="snackbar"> {{ message }} </v-snackbar> </main> </template> <script> import Axios from 'axios' import Authentication from '@/components/pages/Authentication' import ListHeader from './../List/ListHeader' import ListBody from './../List/ListBody' const BudgetManagerAPI = `http://${window.location.hostname}:3001` export default { components: { 'list-header': ListHeader, 'list-body': ListBody }, data () { return { budgets: [], clients: [], budgetHeaders: ['Client', 'Title', 'Status', 'Actions'], clientHeaders: ['Client', 'Email', 'Phone', 'Actions'], budgetsVisible: false, snackbar: false, timeout: 6000, message: '' } }, mounted () { this.getAllBudgets() this.getAllClients() }, methods: { getAllBudgets () { Axios.get(`${BudgetManagerAPI}/api/v1/budget`, { headers: { 'Authorization': Authentication.getAuthenticationHeader(this) }, params: { user_id: this.$cookie.get('user_id') } }).then(({data}) => { this.budgets = this.dataParser(data, '_id', 'client', 'title', 'state') }).catch(error => { this.snackbar = true this.message = error.message }) }, getAllClients () { Axios.get(`${BudgetManagerAPI}/api/v1/client`, { headers: { 'Authorization': Authentication.getAuthenticationHeader(this) }, params: { user_id: this.$cookie.get('user_id') } }).then(({data}) => { this.clients = this.dataParser(data, 'name', 'client', 'email', 'phone') }).catch(error => { this.snackbar = true this.message = error.message }) }, dataParser (targetedArray, ...options) { let parsedData = [] targetedArray.forEach(item => { let parsedItem = {} options.forEach(option => (parsedItem[option] = item[option])) parsedData.push(parsedItem) }) return parsedData } } } </script> <style lang="scss" scoped> @import "./../../assets/styles"; .l-home { background-color: $background-color; margin: 25px auto; padding: 15px; min-width: 272px; } </style>
Header
and, in addition, we use this variable in the ternary comparison operator to display the necessary data. The variable toggleVisibleData
also gets into toggleVisibleData
, where we invert the value of budgetsVisible
. The reason why we pass properties to Header
is that, thanks to this approach, we can make some more improvements, which we will discuss below. In addition, in the list-header
and list-body
slots we use ternary comparison operators.Header
: <template> <header class="l-header-container"> <v-layout row wrap :class="budgetsVisible ? 'l-budgets-header' : 'l-clients-header'"> <v-flex xs12 md5> <v-text-field v-model="search" label="Search" append-icon="search" :color="budgetsVisible ? 'light-blue lighten-1' : 'green lighten-1'"> </v-text-field> </v-flex> <v-flex xs12 offset-md1 md1> <v-btn block :color="budgetsVisible ? 'light-blue lighten-1' : 'green lighten-1'" @click.native="$emit('toggleVisibleData')"> {{ budgetsVisible ? "Clients" : "Budgets" }} </v-btn> </v-flex> <v-flex xs12 offset-md1 md2> <v-select label="Status" :color="budgetsVisible ? 'light-blue lighten-1' : 'green lighten-1'" v-model="status" :items="statusItems" single-line> </v-select> </v-flex> <v-flex xs12 offset-md1 md1> <v-btn block color="red lighten-1 white--text" @click.native="submitSignout()">Sign out</v-btn> </v-flex> </v-layout> </header> </template> <script> import Authentication from '@/components/pages/Authentication' export default { props: ['budgetsVisible'], data () { return { search: '', status: '', statusItems: [ 'All', 'Approved', 'Denied', 'Waiting', 'Writing', 'Editing' ] } }, methods: { submitSignout () { Authentication.signout(this, '/login') } } } </script> <style lang="scss"> @import "./../assets/styles"; .l-header-container { background-color: $background-color; margin: 0 auto; padding: 0 15px; min-width: 272px; .l-budgets-header { label, input, .icon, .input-group__selections__comma { color: #29b6f6!important; } } .l-clients-header { label, input, .icon, .input-group__selections__comma { color: #66bb6a!important; } } .input-group__details { &:before { background-color: $border-color-input !important; } } .btn { margin-top: 15px; } } </style>
budgetsVisible
variable. If the documents are visible, the heading will have a light blue color, if not - green.budgetVisible
value, budgetVisible
, the corresponding event handler is called, changing the state of the logical variable.ListBody
's do the ListBody
component: <template> <section class="l-list-body"> <div class="md-list-item" v-if="data != null" v-for="item in data"> <div :class="budgetsVisible ? 'md-budget-info white--text' : 'md-client-info white--text'" v-for="info in item" v-if="info != item._id"> {{ info }} </div> <div :class="budgetsVisible ? 'l-budget-actions white--text' : 'l-client-actions white--text'"> <v-btn small flat color="light-blue lighten-1"> <v-icon small>visibility</v-icon> </v-btn> <v-btn small flat color="yellow accent-1"> <v-icon>mode_edit</v-icon> </v-btn> <v-btn small flat color="red lighten-1"> <v-icon>delete_forever</v-icon> </v-btn> </div> </div> </section> </template> <script> export default { props: ['data', 'budgetsVisible'] } </script> <style lang="scss"> @import "./../../assets/styles"; .l-list-body { display: flex; flex-direction: column; .md-list-item { width: 100%; display: flex; flex-direction: column; margin: 15px 0; @media (min-width: 960px) { flex-direction: row; margin: 0; } .md-budget-info { flex-basis: 25%; width: 100%; background-color: rgba(0, 175, 255, 0.45); border: 1px solid $border-color-input; padding: 0 15px; display: flex; height: 35px; align-items: center; justify-content: center; &:first-of-type, &:nth-of-type(2) { text-transform: capitalize; } &:nth-of-type(3) { text-transform: uppercase; } @media (min-width: 601px) { justify-content: flex-start; } } .md-client-info { @extend .md-budget-info; background-color: rgba(102, 187, 106, 0.45)!important; &:nth-of-type(2) { text-transform: none; } } .l-budget-actions { flex-basis: 25%; display: flex; background-color: rgba(0, 175, 255, 0.45); border: 1px solid $border-color-input; align-items: center; justify-content: center; .btn { min-width: 45px !important; margin: 0 5px !important; } } .l-client-actions { @extend .l-budget-actions; background-color: rgba(102, 187, 106, 0.45)!important; } } } </style>
Header
component code.Home
component code, add the following code below the v-snackbar
: <v-fab-transition> <v-speed-dial v-model="fab" bottom right fixed direction="top" transition="scale-transition"> <v-btn slot="activator" color="red lighten-1" dark fab v-model="fab"> <v-icon>add</v-icon> <v-icon>close</v-icon> </v-btn> <v-tooltip left> <v-btn color="light-blue lighten-1" dark small fab slot="activator"> <v-icon>assignment</v-icon> </v-btn> <span>Add new Budget</span> </v-tooltip> <v-tooltip left> <v-btn color="green lighten-1" dark small fab slot="activator"> <v-icon>account_circle</v-icon> </v-btn> <span>Add new Client</span> </v-tooltip> </v-speed-dial> </v-fab-transition>
Home
component: data () { return { budgets: [], clients: [], budgetHeaders: ['Client', 'Title', 'Status', 'Actions'], clientHeaders: ['Client', 'Email', 'Phone', 'Actions'], budgetsVisible: true, snackbar: false, timeout: 6000, message: '', fab: false } },
fab
, which is used to indicate whether the floating button is active or not.Source: https://habr.com/ru/post/342402/
All Articles