
models folder and open the budget.js file. Add a description field for the model: description: { type: String, required: true }, app/api and open the file budget.js , which is located in it. Here we are going to edit the data storage function, store , so that new documents are processed correctly, add the edit function, which allows editing documents, add the remove function, which is needed to remove documents, and add the getByState function, which allows filtering documents. Here is the complete code of the file. To view it, expand the corresponding block. In the future, large code snippets will be issued in the same way. const mongoose = require('mongoose'); const api = {}; api.store = (User, Budget, Client, Token) => (req, res) => { if (Token) { Client.findOne({ _id: req.body.client }, (error, client) => { if (error) res.status(400).json(error); if (client) { const budget = new Budget({ client_id: req.body.client, user_id: req.query.user_id, client: client.name, state: req.body.state, description: req.body.description, title: req.body.title, total_price: req.body.total_price, items: req.body.items }); budget.save(error => { if (error) return res.status(400).json(error) res.status(200).json({ success: true, message: "Budget registered successfully" }) }) } else { res.status(400).json({ success: false, message: "Invalid client" }) } }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } api.getAll = (User, Budget, Token) => (req, res) => { if (Token) { Budget.find({ user_id: req.query.user_id }, (error, budget) => { if (error) return res.status(400).json(error); res.status(200).json(budget); return true; }) } else return res.status(403).send({ success: false, message: 'Unauthorized' }); } api.getAllFromClient = (User, Budget, Token) => (req, res) => { if (Token) { Budget.find({ client_id: req.query.client_id }, (error, budget) => { if (error) return res.status(400).json(error); res.status(200).json(budget); return true; }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } api.index = (User, Budget, Client, Token) => (req, res) => { if (Token) { User.findOne({ _id: req.query.user_id }, (error, user) => { if (error) res.status(400).json(error); if (user) { Budget.findOne({ _id: req.query._id }, (error, budget) => { if (error) res.status(400).json(error); res.status(200).json(budget); }) } else { res.status(400).json({ success: false, message: "Invalid budget" }) } }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } api.edit = (User, Budget, Client, Token) => (req, res) => { if (Token) { User.findOne({ _id: req.query.user_id }, (error, user) => { if (error) res.status(400).json(error); if (user) { Budget.findOneAndUpdate({ _id: req.body._id }, req.body, (error, budget) => { if (error) res.status(400).json(error); res.status(200).json(budget); }) } else { res.status(400).json({ success: false, message: "Invalid budget" }) } }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } api.getByState = (User, Budget, Client, Token) => (req, res) => { if (Token) { User.findOne({ _id: req.query.user_id }, (error, user) => { if (error) res.status(400).json(error); if (user) { Budget.find({ state: req.query.state }, (error, budget) => { console.log(budget) if (error) res.status(400).json(error); res.status(200).json(budget); }) } else { res.status(400).json({ success: false, message: "Invalid budget" }) } }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } api.remove = (User, Budget, Client, Token) => (req, res) => { if (Token) { Budget.remove({ _id: req.query._id }, (error, removed) => { if (error) res.status(400).json(error); res.status(200).json({ success: true, message: 'Removed successfully' }); }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } module.exports = api; api folder: const mongoose = require('mongoose'); const api = {}; api.store = (User, Client, Token) => (req, res) => { if (Token) { User.findOne({ _id: req.query.user_id }, (error, user) => { if (error) res.status(400).json(error); if (user) { const client = new Client({ user_id: req.query.user_id, name: req.body.name, email: req.body.email, phone: req.body.phone, }); client.save(error => { if (error) return res.status(400).json(error); res.status(200).json({ success: true, message: "Client registration successful" }); }) } else { res.status(400).json({ success: false, message: "Invalid client" }) } }) } else return res.status(403).send({ success: false, message: 'Unauthorized' }); } api.getAll = (User, Client, Token) => (req, res) => { if (Token) { Client.find({ user_id: req.query.user_id }, (error, client) => { if (error) return res.status(400).json(error); res.status(200).json(client); return true; }) } else return res.status(403).send({ success: false, message: 'Unauthorized' }); } api.index = (User, Client, Token) => (req, res) => { if (Token) { User.findOne({ _id: req.query.user_id }, (error, user) => { if (error) res.status(400).json(error); if (user) { Client.findOne({ _id: req.query._id }, (error, client) => { if (error) res.status(400).json(error); res.status(200).json(client); }) } else { res.status(400).json({ success: false, message: "Invalid client" }) } }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } api.edit = (User, Client, Token) => (req, res) => { if (Token) { User.findOne({ _id: req.query.user_id }, (error, user) => { if (error) res.status(400).json(error); if (user) { Client.findOneAndUpdate({ _id: req.body._id }, req.body, (error, client) => { if (error) res.status(400).json(error); res.status(200).json(client); }) } else { res.status(400).json({ success: false, message: "Invalid client" }) } }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } api.remove = (User, Client, Token) => (req, res) => { if (Token) { User.findOne({ _id: req.query.user_id }, (error, user) => { if (error) res.status(400).json(error); if (user) { Client.remove({ _id: req.query._id }, (error, removed) => { if (error) res.status(400).json(error); res.status(200).json({ success: true, message: 'Removed successfully' }); }) } else { res.status(400).json({ success: false, message: "Invalid client" }) } }) } else return res.status(401).send({ success: false, message: 'Unauthorized' }); } module.exports = api; routes folder and open the budget.js file: const passport = require('passport'), config = require('@config'), models = require('@BudgetManager/app/setup'); module.exports = (app) => { const api = app.BudgetManagerAPI.app.api.budget; app.route('/api/v1/budget') .post(passport.authenticate('jwt', config.session), api.store(models.User, models.Budget, models.Client, app.get('budgetsecret'))) .get(passport.authenticate('jwt', config.session), api.getAll(models.User, models.Budget, app.get('budgetsecret'))) .get(passport.authenticate('jwt', config.session), api.getAllFromClient(models.User, models.Budget, app.get('budgetsecret'))) .delete(passport.authenticate('jwt', config.session), api.remove(models.User, models.Budget, models.Client, app.get('budgetsecret'))) app.route('/api/v1/budget/single') .get(passport.authenticate('jwt', config.session), api.index(models.User, models.Budget, models.Client, app.get('budgetsecret'))) .put(passport.authenticate('jwt', config.session), api.edit(models.User, models.Budget, models.Client, app.get('budgetsecret'))) app.route('/api/v1/budget/state') .get(passport.authenticate('jwt', config.session), api.getByState(models.User, models.Budget, models.Client, app.get('budgetsecret'))) } client.js file, which is located in the same folder: const passport = require('passport'), config = require('@config'), models = require('@BudgetManager/app/setup'); module.exports = (app) => { const api = app.BudgetManagerAPI.app.api.client; app.route('/api/v1/client') .post(passport.authenticate('jwt', config.session), api.store(models.User, models.Client, app.get('budgetsecret'))) .get(passport.authenticate('jwt', config.session), api.getAll(models.User, models.Client, app.get('budgetsecret'))) .delete(passport.authenticate('jwt', config.session), api.remove(models.User, models.Client, app.get('budgetsecret'))) app.route('/api/v1/client/single') .get(passport.authenticate('jwt', config.session), api.index(models.User, models.Client, app.get('budgetsecret'))) .put(passport.authenticate('jwt', config.session), api.edit(models.User, models.Client, app.get('budgetsecret'))) } index.js , located inside the router folder. ... // Global components import Header from '@/components/Header' import List from '@/components/List/List' import Create from '@/components/pages/Create' // Register components Vue.component('app-header', Header) Vue.component('list', List) Vue.component('create', Create) Vue.use(Router) const router = new Router({ routes: [ { path: '/', name: 'Home', components: { default: Home, header: Header, list: List, create: Create } }, { path: '/login', name: 'Authentication', component: Authentication } ] }) … Create component and assigned it as the Home component of the route (we will create the component itself below).Create component. Go to the components/pages folder and create a new file Create.vue . <template> <div class="l-create-page"> <budget-creation v-if="budgetCreation && !editPage" slot="budget-creation" :clients="clients" :saveBudget="saveBudget"></budget-creation> <client-creation v-if="!budgetCreation && !editPage" slot="client-creation" :saveClient="saveClient"></client-creation> <budget-edit v-else-if="budgetEdit && editPage" slot="budget-creation" :clients="clients" :selectedBudget="budget" :fixClientNameAndUpdate="fixClientNameAndUpdate"> </budget-edit> <client-edit v-else-if="!budgetEdit && editPage" slot="client-creation" :selectedClient="client" :updateClient="updateClient"> </client-edit> </div> </template> <script> import BudgetCreation from './../Creation/BudgetCreation' import ClientCreation from './../Creation/ClientCreation' import BudgetEdit from './../Creation/BudgetEdit' import ClientEdit from './../Creation/ClientEdit' export default { props: [ 'budgetCreation', 'clients', 'saveBudget', 'saveClient', 'budget', 'client', 'updateClient', 'fixClientNameAndUpdate', 'editPage', 'budgetEdit' ], components: { 'budget-creation': BudgetCreation, 'client-creation': ClientCreation, 'budget-edit': BudgetEdit, 'client-edit': ClientEdit } } </script> budget-creation . It represents the component that we will use to create new financial documents. It will only be visible if the budgetCreation property budgetCreation set to true , and editPage is set to false , we pass it all our clients and the saveBudget method.client-creation . This is the component used to create new customers. It will only be visible when the budgetCreation property budgetCreation set to false , and the editPage is also set to false . Here we pass the saveClient method.budget-edit . This is the component that is used to edit the selected document. We see it only when the budgetEdit and editPage set to true . Here we transfer all clients, the selected financial document and the fixClientNameAndUpdate method.budgetEdit property budgetEdit set to false and the editPage is set to true . We give him the selected client and the updateClient method.components folder and create a new folder in it, giving it the name Creation . In this folder, create a component file BudgetCreation.vue . <template> <div class="l-budget-creation"> <v-layout row wrap> <span class="md-budget-state-hint uppercased white--text">status</span> <v-flex xs12 md2> <v-select label="Status" :items="states" v-model="budget.state" > </v-select> </v-flex> <v-flex xs12 md9 offset-md1> <v-select label="Client" :items="clients" v-model="budget.client" item-text="name" item-value="_id" > </v-select> </v-flex> <v-flex xs12 md12> <v-text-field label="Title" v-model="budget.title" required color="light-blue lighten-1"> </v-text-field> <v-text-field label="Description" v-model="budget.description" textarea required color="light-blue lighten-1"> </v-text-field> </v-flex> <v-layout row wrap v-for="item in budget.items" class="l-budget-item" :key="item.id"> <v-flex xs12 md1> <v-btn block dark color="red lighten-1" @click.native="removeItem(item)">Remove</v-btn> </v-flex> <v-flex xs12 md3 offset-md1> <v-text-field label="Title" box dark v-model="item.title" required color="light-blue lighten-1"> </v-text-field> </v-flex> <v-flex xs12 md1 offset-md1> <v-text-field label="Price" box dark prefix="$" v-model="item.price" required color="light-blue lighten-1"> </v-text-field> </v-flex> <v-flex xs12 md2 offset-md1> <v-text-field label="Quantity" box dark min="0" v-model="item.quantity" type="number" required color="light-blue lighten-1"> </v-text-field> </v-flex> <v-flex xs12 md2> <span class="md-budget-item-subtotal white--text">ITEM PRICE $ {{ item.subtotal }}</span> </v-flex> </v-layout> <v-flex xs12 md2 offset-md10> <v-btn block color="md-add-item-btn light-blue lighten-1" @click.native="addItem()">Add item</v-btn> </v-flex> <v-flex xs12 md2 offset-md10> <span class="md-budget-item-total white--text">TOTAL $ {{ budget.total_price }}</span> </v-flex> <v-flex xs12 md2 offset-md10> <v-btn block color="md-add-item-btn green lighten-1" @click.native="saveBudget(budget)">Save</v-btn> </v-flex> </v-layout> </div> </template> v-select element to the template to set the state of the document, then v-select to select the client we need. Next, we have a v-text-field for entering the title of the document and a v-text-field for displaying the description.budget.items through the elements of budget.items , which gives us the opportunity to add elements to the document and remove them from it. Here there is a red button that allows you to call the function removeItem , passing it the item that you want to remove.v-text-fields , intended, respectively, for the name of the goods, the price per unit and quantity.span element, in which the subtotal is displayed, subtotal , which is the product of the quantity and price of the product.addItem function, the span element, which shows the total cost of all items that are in the document (the sum of the subtotal indicators of all items), and the green button that is used to save the document to the database. by calling the saveBudget function with passing it, as a parameter, the document we want to save. <script> export default { props: ['clients', 'saveBudget'], data () { return { budget: { title: null, description: null, state: 'writing', client: null, get total_price () { let value = 0 this.items.forEach(({ subtotal }) => { value += parseInt(subtotal) }) return value }, items: [ { title: null, quantity: 0, price: 0, get subtotal () { return this.quantity * this.price } } ] }, states: [ 'writing', 'editing', 'pending', 'approved', 'denied', 'waiting' ] } }, methods: { addItem () { const items = this.budget.items const item = { title: '', quantity: 0, price: 0, get subtotal () { return this.quantity * this.price } } items.push(item) }, removeItem (selected) { const items = this.budget.items items.forEach((item, index) => { if (item === selected) { items.splice(index, 1) } }) } } } </script> clients and saveBudget . The source of these properties is the Home component.budget . It is used to create a document, we can add values to it and save it in the database. This object has the properties title (title), description (description), state (state, the default is set to the value of writing ), client (client), total_price (total value of the document), and an array of items . Products have title (title), quantity (quantity), price (price) and subtotal (subtotal).states , states . Its values are used to set the state of the document. These states are: writing , editing , pending , approved , denied and waiting .addItem (to add products) and removeItem (to remove them).addItem method is addItem , which adds items to the items array inside the budget object.removeItem method does the opposite. Namely - when you click on the red button, the specified item is deleted from the items array. <style lang="scss"> @import "./../../assets/styles"; .uppercased { text-transform: uppercase; } .l-budget-creation { label, input, .icon, .input-group__selections__comma, textarea { color: #29b6f6!important; } .input-group__details { &:before { background-color: $border-color-input !important; } } .input-group__input { border-color: $border-color-input !important; .input-group--text-field__prefix { margin-bottom: 3px !important; } } .input-group--focused { .input-group__input { border-color: #29b6f6!important; } } } .md-budget-state-hint { margin: 10px 0; display: block; width: 100%; } .md-budget-state { background-color: rgba(41, 182, 246, .6); display: flex; height: 35px; width: 100%; font-size: 14px; align-items: center; justify-content: center; border-radius: 2px; margin: 10px 0 15px; } .l-budget-item { align-items: center; } .md-budget-item-subtotal { font-size: 16px; text-align: center; display: block; } .md-budget-item-total { font-size: 22px; text-align: center; display: block; width: 100%; margin: 30px 0 10px; } .md-add-item-btn { margin-top: 30px !important; display: block; } .list__tile__title, .input-group__selections { text-transform: uppercase !important; } </style> BudgetCreation component BudgetCreation . We are just as done above, we consider it in parts. If you have dealt with the device component of the BudgetCreation , you can easily understand the principles of the ClientCreation component. <template> <div class="l-client-creation"> <v-layout row wrap> <v-flex xs12 md4> <v-text-field label="Name" v-model="client.name" required color="green lighten-1"> </v-text-field> </v-flex> <v-flex xs12 md3 offset-md1> <v-text-field label="Email" v-model="client.email" required color="green lighten-1"> </v-text-field> </v-flex> <v-flex xs12 md3 offset-md1> <v-text-field label="Phone" v-model="client.phone" required mask="phone" color="green lighten-1"> </v-text-field> </v-flex> <v-flex xs12 md2 offset-md10> <v-btn block color="md-add-item-btn green lighten-1" @click.native="saveClient(client)">Save</v-btn> </v-flex> </v-layout> </div> </template> <script> export default { props: ['saveClient'], data () { return { client: { name: null, email: null, phone: null } } } } </script> <style lang="scss"> @import "./../../assets/styles"; .uppercased { text-transform: uppercase; } .l-client-creation { label, input, .icon, .input-group__selections__comma, textarea { color: #66bb6a!important; } .input-group__details { &:before { background-color: $border-color-input !important; } } .input-group__input { border-color: $border-color-input !important; .input-group--text-field__prefix { margin-bottom: 3px !important; } } .input-group--focused { .input-group__input { border-color: #66bb6a!important; } } } </style> BudgetEdit component.BudgetCreation component already considered. Consider its component parts. <template> <div class="l-budget-creation"> <v-layout row wrap> <span class="md-budget-state-hint uppercased white--text">status</span> <v-flex xs12 md2> <v-select label="Status" :items="states" v-model="budget.state" > </v-select> </v-flex> <v-flex xs12 md9 offset-md1> <v-select label="Client" :items="clients" v-model="budget.client_id" item-text="name" item-value="_id" > </v-select> </v-flex> <v-flex xs12 md12> <v-text-field label="Title" v-model="budget.title" required color="light-blue lighten-1"> </v-text-field> <v-text-field label="Description" v-model="budget.description" textarea required color="light-blue lighten-1"> </v-text-field> </v-flex> <v-layout row wrap v-for="item in budget.items" class="l-budget-item" :key="item.id"> <v-flex xs12 md1> <v-btn block dark color="red lighten-1" @click.native="removeItem(item)">Remove</v-btn> </v-flex> <v-flex xs12 md3 offset-md1> <v-text-field label="Title" box dark v-model="item.title" required color="light-blue lighten-1"> </v-text-field> </v-flex> <v-flex xs12 md1 offset-md1> <v-text-field label="Price" box dark prefix="$" v-model="item.price" required color="light-blue lighten-1"> </v-text-field> </v-flex> <v-flex xs12 md2 offset-md1> <v-text-field label="Quantity" box dark min="0" v-model="item.quantity" type="number" required color="light-blue lighten-1"> </v-text-field> </v-flex> <v-flex xs12 md2> <span class="md-budget-item-subtotal white--text">ITEM PRICE $ {{ item.subtotal }}</span> </v-flex> </v-layout> <v-flex xs12 md2 offset-md10> <v-btn block color="md-add-item-btn light-blue lighten-1" @click.native="addItem()">Add item</v-btn> </v-flex> <v-flex xs12 md2 offset-md10> <span class="md-budget-item-total white--text">TOTAL $ {{ budget.total_price }}</span> </v-flex> <v-flex xs12 md2 offset-md10> <v-btn block color="md-add-item-btn green lighten-1" @click.native="fixClientNameAndUpdate(budget)">Update</v-btn> </v-flex> </v-layout> </div> </template> BudgetEdit and BudgetCreation component templates is in the save button and the associated logic. Namely, in the BudgetCreation Save is written on it, it calls the saveBudget method. In BudgetEdit this button carries the Update text on it and calls the fixClientNameAndUpdate method. <script> export default { props: ['clients', 'fixClientNameAndUpdate', 'selectedBudget'], data () { return { budget: { title: null, description: null, state: 'pending', client: null, get total_price () { let value = 0 this.items.forEach(({ subtotal }) => { value += parseInt(subtotal) }) return value }, items: [ { title: null, quantity: 0, price: null, get subtotal () { return this.quantity * this.price } } ] }, states: [ 'writing', 'editing', 'pending', 'approved', 'denied', 'waiting' ] } }, mounted () { this.parseBudget() }, methods: { addItem () { const items = this.budget.items const item = { title: '', quantity: 0, price: 0, get subtotal () { return this.quantity * this.price } } items.push(item) }, removeItem (selected) { const items = this.budget.items items.forEach((item, index) => { if (item === selected) { items.splice(index, 1) } }) }, parseBudget () { for (let key in this.selectedBudget) { if (key !== 'total' && key !== 'items') { this.budget[key] = this.selectedBudget[key] } if (key === 'items') { const items = this.selectedBudget.items const buildItems = item => ({ title: item.title, quantity: item.quantity, price: item.price, get subtotal () { return this.quantity * this.price } }) const parseItems = items => items.map(buildItems) this.budget.items = parseItems(items) } } } } } </script> clients , fixClientNameAndUpdate and selectedBudget . The data here is the same as in the BudgetCreation component. Namely, there is the Budget object and the states array.parseBudget method, which we will discuss below. And finally, there is a methods object, in which there are the addItem and removeItem methods already familiar to you from the BudgetCreation component, as well as the new parseBudget method. This method is used to set the value of the budget object to that passed in the selectedBudget property, but we also use it to calculate subtotals for the goods in the document and the total amount for the document. <style lang="scss"> @import "./../../assets/styles"; .uppercased { text-transform: uppercase; } .l-budget-creation { label, input, .icon, .input-group__selections__comma, textarea { color: #29b6f6!important; } .input-group__details { &:before { background-color: $border-color-input !important; } } .input-group__input { border-color: $border-color-input !important; .input-group--text-field__prefix { margin-bottom: 3px !important; } } .input-group--focused { .input-group__input { border-color: #29b6f6!important; } } } .md-budget-state-hint { margin: 10px 0; display: block; width: 100%; } .md-budget-state { background-color: rgba(41, 182, 246, .6); display: flex; height: 35px; width: 100%; font-size: 14px; align-items: center; justify-content: center; border-radius: 2px; margin: 10px 0 15px; } .l-budget-item { align-items: center; } .md-budget-item-subtotal { font-size: 16px; text-align: center; display: block; } .md-budget-item-total { font-size: 22px; text-align: center; display: block; width: 100%; margin: 30px 0 10px; } .md-add-item-btn { margin-top: 30px !important; display: block; } .list__tile__title, .input-group__selections { text-transform: uppercase !important; } </style> ClientCreation . The main difference is that instead of the saveClient method, the updateClient method is updateClient . Consider the device component ClientEdit . <template> <div class="l-client-creation"> <v-layout row wrap> <v-flex xs12 md4> <v-text-field label="Name" v-model="client.name" required color="green lighten-1"> </v-text-field> </v-flex> <v-flex xs12 md3 offset-md1> <v-text-field label="Email" v-model="client.email" required color="green lighten-1"> </v-text-field> </v-flex> <v-flex xs12 md3 offset-md1> <v-text-field label="Phone" v-model="client.phone" required mask="phone" color="green lighten-1"> </v-text-field> </v-flex> <v-flex xs12 md2 offset-md10> <v-btn block color="md-add-item-btn green lighten-1" @click.native="updateClient(client)">Update</v-btn> </v-flex> </v-layout> </div> </template> <script> export default { props: ['updateClient', 'selectedClient'], data () { return { client: { name: null, email: null, phone: null } } }, mounted () { this.client = this.selectedClient } } </script> <style lang="scss"> @import "./../../assets/styles"; .uppercased { text-transform: uppercase; } .l-client-creation { label, input, .icon, .input-group__selections__comma, textarea { color: #66bb6a!important; } .input-group__details { &:before { background-color: $border-color-input !important; } } .input-group__input { border-color: $border-color-input !important; .input-group--text-field__prefix { margin-bottom: 3px !important; } } .input-group--focused { .input-group__input { border-color: #66bb6a!important; } } } </style> ListBody .ListBody.vue <template> <section class="l-list-body"> <div class="md-list-item" v-if="data != null && parsedBudgets === 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 != item.client_id"> {{ info }} </div> <div :class="budgetsVisible ? 'l-budget-actions white--text' : 'l-client-actions white--text'"> <v-btn small flat color="yellow accent-1" @click.native="getItemAndEdit(item)"> <v-icon>mode_edit</v-icon> </v-btn> <v-btn small flat color="red lighten-1" @click.native="deleteItem(item, data, budgetsVisible)"> <v-icon>delete_forever</v-icon> </v-btn> </div> </div> <div class="md-list-item" v-if="parsedBudgets !== null" v-for="item in parsedBudgets"> <div :class="budgetsVisible ? 'md-budget-info white--text' : 'md-client-info white--text'" v-for="info in item" v-if="info != item._id && info != item.client_id"> {{ info }} </div> <div :class="budgetsVisible ? 'l-budget-actions white--text' : 'l-client-actions white--text'"> <v-btn small flat color="yellow accent-1" @click.native="getItemAndEdit(item)"> <v-icon>mode_edit</v-icon> </v-btn> <v-btn small flat color="red lighten-1" @click.native="deleteItem(item, data, budgetsVisible)"> <v-icon>delete_forever</v-icon> </v-btn> </div> </div> </section> </template> v-if md-list-item : parsedBudgets === null getItemAndEdit deleteItem , , budgetsVisible .md-item-list , . <script> export default { props: ['data', 'budgetsVisible', 'deleteItem', 'getBudget', 'getClient', 'parsedBudgets'], methods: { getItemAndEdit (item) { !item.phone ? this.getBudget(item) : this.getClient(item) } } } </script> data : , , .budgetsVisible : , , true false .deleteItem : , , , .getBudget : , , .getClient : , .parsedBudgets : , .getItemAndEdit . , , , , , , , . <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> ListBody , 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="searchValue" 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 @change="selectState"> </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> v-model searchValue .v-select , change selectState . <script> import Authentication from '@/components/pages/Authentication' export default { props: ['budgetsVisible', 'selectState', 'search'], data () { return { searchValue: '', status: '', statusItems: [ 'all', 'approved', 'denied', 'waiting', 'writing', 'editing' ] } }, watch: { 'searchValue': function () { this.$emit('input', this.searchValue) } }, created () { this.searchValue = this.search }, methods: { submitSignout () { Authentication.signout(this, '/login') } } } </script> selectState , , search , . search searchValue statusItems . <script> import Authentication from '@/components/pages/Authentication' export default { props: ['budgetsVisible', 'selectState', 'search'], data () { return { searchValue: '', status: '', statusItems: [ 'all', 'approved', 'denied', 'waiting', 'writing', 'editing' ] } }, watch: { 'searchValue': function () { this.$emit('input', this.searchValue) } }, created () { this.searchValue = this.search }, methods: { submitSignout () { Authentication.signout(this, '/login') } } } </script> Header , Home . <template> <main class="l-home-page"> <app-header :budgetsVisible="budgetsVisible" @toggleVisibleData="budgetsVisible = !budgetsVisible; budgetCreation = !budgetCreation" :selectState="selectState" :search="search" v-model="search"> </app-header> <div class="l-home"> <h4 class="white--text text-xs-center my-0"> Focus Budget Manager </h4> <list v-if="listPage"> <list-header slot="list-header" :headers="budgetsVisible ? budgetHeaders : clientHeaders"></list-header> <list-body slot="list-body" :budgetsVisible="budgetsVisible" :data="budgetsVisible ? budgets : clients" :search="search" :deleteItem="deleteItem" :getBudget="getBudget" :getClient="getClient" :parsedBudgets="parsedBudgets"> </list-body> </list> <create v-else-if="createPage" :budgetCreation="budgetCreation" :budgetEdit="budgetEdit" :editPage="editPage" :clients="clients" :budget="budget" :client="client" :saveBudget="saveBudget" :saveClient="saveClient" :fixClientNameAndUpdate="fixClientNameAndUpdate" :updateClient="updateClient"> </create> </div> <v-snackbar :timeout="timeout" bottom="bottom" :color="snackColor" v-model="snackbar"> {{ message }} </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" @click.native="budgetCreation = true; listPage = false; editPage = false; createPage = true"> <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" @click.native="budgetCreation = false; listPage = false; editPage = false; createPage = true"> <v-icon>account_circle</v-icon> </v-btn> <span>Add new Client</span> </v-tooltip> <v-tooltip left> <v-btn color="purple lighten-2" dark small fab slot="activator" @click.native="budgetCreation = false; listPage = true; budgetsVisible = true"> <v-icon>assessment</v-icon> </v-btn> <span>List Budgets</span> </v-tooltip> <v-tooltip left> <v-btn color="deep-orange lighten-2" dark small fab slot="activator" @click.native="budgetCreation = false; listPage = true; budgetsVisible = false;"> <v-icon>supervisor_account</v-icon> </v-btn> <span>List Clients</span> </v-tooltip> </v-speed-dial> </v-fab-transition> </main> </template> budgetsVisible , selectState , search toggleVisibleData , , toggleVisibleData , v-model search .list v-if , , . , list-body .create , list , , . , .v-fab-transition , , . <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 { parsedBudgets: null, budget: null, client: null, state: null, search: null, budgets: [], clients: [], budgetHeaders: ['Client', 'Title', 'Status', 'Actions'], clientHeaders: ['Client', 'Email', 'Phone', 'Actions'], budgetsVisible: true, snackbar: false, timeout: 6000, message: '', fab: false, listPage: true, createPage: true, editPage: false, budgetCreation: true, budgetEdit: true, snackColor: 'red lighten-1' } }, mounted () { this.getAllBudgets() this.getAllClients() this.hidden = false }, watch: { 'search': function () { if (this.search !== null || this.search !== '') { const searchTerm = this.search const regex = new RegExp(`^(${searchTerm})`, 'g') const results = this.budgets.filter(budget => budget.client.match(regex)) this.parsedBudgets = results } else { this.parsedBudgets = null } } }, 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', 'client_id') }).catch(error => { this.errorHandler(error) }) }, 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', 'email', '_id', 'phone') }).catch(error => { this.errorHandler(error) }) }, getBudget (budget) { Axios.get(`${BudgetManagerAPI}/api/v1/budget/single`, { headers: { 'Authorization': Authentication.getAuthenticationHeader(this) }, params: { user_id: this.$cookie.get('user_id'), _id: budget._id } }).then(({data}) => { this.budget = data this.enableEdit('budget') }).catch(error => { this.errorHandler(error) }) }, getClient (client) { Axios.get(`${BudgetManagerAPI}/api/v1/client/single`, { headers: { 'Authorization': Authentication.getAuthenticationHeader(this) }, params: { user_id: this.$cookie.get('user_id'), _id: client._id } }).then(({data}) => { this.client = data this.enableEdit('client') }).catch(error => { this.errorHandler(error) }) }, enableEdit (type) { if (type === 'budget') { this.listPage = false this.budgetEdit = true this.budgetCreation = false this.editPage = true } else if (type === 'client') { this.listPage = false this.budgetEdit = false this.budgetCreation = false this.editPage = true } }, saveBudget (budget) { Axios.post(`${BudgetManagerAPI}/api/v1/budget`, budget, { headers: { 'Authorization': Authentication.getAuthenticationHeader(this) }, params: { user_id: this.$cookie.get('user_id') } }) .then(res => { this.resetFields(budget) this.snackbar = true this.message = res.data.message this.snackColor = 'green lighten-1' this.getAllBudgets() }) .catch(error => { this.errorHandler(error) }) }, fixClientNameAndUpdate (budget) { this.clients.find(client => { if (client._id === budget.client_id) { budget.client = client.name } }) this.updateBudget(budget) }, updateBudget (budget) { Axios.put(`${BudgetManagerAPI}/api/v1/budget/single`, budget, { headers: { 'Authorization': Authentication.getAuthenticationHeader(this) }, params: { user_id: this.$cookie.get('user_id') } }) .then(() => { this.snackbar = true this.message = 'Budget updated' this.snackColor = 'green lighten-1' this.listPage = true this.budgetCreation = false this.budgetsVisible = true this.getAllBudgets() }) .catch(error => { this.errorHandler(error) }) }, updateClient (client) { Axios.put(`${BudgetManagerAPI}/api/v1/client/single`, client, { headers: { 'Authorization': Authentication.getAuthenticationHeader(this) }, params: { user_id: this.$cookie.get('user_id') } }) .then(() => { this.snackbar = true this.message = 'Client updated' this.snackColor = 'green lighten-1' this.listPage = true this.budgetCreation = false this.budgetsVisible = false this.getAllClients() }) .catch(error => { this.errorHandler(error) }) }, saveClient (client) { Axios.post(`${BudgetManagerAPI}/api/v1/client`, client, { headers: { 'Authorization': Authentication.getAuthenticationHeader(this) }, params: { user_id: this.$cookie.get('user_id') } }) .then(res => { this.resetFields(client) this.snackbar = true this.message = res.data.message this.snackColor = 'green lighten-1' this.getAllClients() }) .catch(error => { this.errorHandler(error) }) }, deleteItem (selected, items, api) { let targetApi = '' api ? targetApi = 'budget' : targetApi = 'client' Axios.delete(`${BudgetManagerAPI}/api/v1/${targetApi}`, { headers: { 'Authorization': Authentication.getAuthenticationHeader(this) }, params: { user_id: this.$cookie.get('user_id'), _id: selected._id } }) .then(() => { this.removeItem(selected, items) }) .then(() => { api ? this.getAllBudgets() : this.getAllClients() }) .catch(error => { this.errorHandler(error) }) }, errorHandler (error) { const status = error.response.status this.snackbar = true this.snackColor = 'red lighten-1' if (status === 404) { this.message = 'Invalid request' } else if (status === 401 || status === 403) { this.message = 'Unauthorized' } else if (status === 400) { this.message = 'Invalid or missing information' } else { this.message = error.message } }, removeItem (selected, items) { items.forEach((item, index) => { if (item === selected) { items.splice(index, 1) } }) }, dataParser (targetedArray, ...options) { let parsedData = [] targetedArray.forEach(item => { let parsedItem = {} options.forEach(option => (parsedItem[option] = item[option])) parsedData.push(parsedItem) }) return parsedData }, resetFields (item) { for (let key in item) { item[key] = null if (key === 'quantity' || key === 'price') { item[key] = 0 } item['items'] = [] } }, selectState (state) { this.state = state state === 'all' ? this.getAllBudgets() : this.getBudgetsByState(state) }, getBudgetsByState (state) { Axios.get(`${BudgetManagerAPI}/api/v1/budget/state`, { headers: { 'Authorization': Authentication.getAuthenticationHeader(this) }, params: { user_id: this.$cookie.get('user_id'), state } }).then(({data}) => { this.budgets = this.dataParser(data, '_id', 'client', 'title', 'state', 'client_id') }).catch(error => { this.errorHandler(error) }) } } } </script> parsedBudgets : , .budget : , .client : , .state : , , .search : , .budgets : , API.clients : , API.budgetHeaders : , .clientHeaders : , , .budgetsVisible : , .snackbar : .timeout : - .message : , .fab : , false .listPage : , , true .createPage : , , false .editPage : , , false .budgetCreation : , , true .budgetEdit : , , true .snackColor : .mounted , .watch . @mrmonkeytech , ( ).getAllBudgets dataParser , errorHandler catch . getAllClients .getBudget getClient , API.enableEdit , , , .saveBudget saveClient , , .fixClientNameAndUpdate , ID , updateBudget .updateBudget .updateClient .deleteItem . , selected , items ( ), api .errorHandler .removeItem deleteItem , .dataParser , , .resetFields . , , .selectState v-select Header .getBudgetsByState selectState , . <style lang="scss"> @import "./../../assets/styles"; .l-home { background-color: $background-color; margin: 25px auto; padding: 15px; min-width: 272px; } .snack__content { justify-content: center !important; } </style> 

Source: https://habr.com/ru/post/346784/
All Articles