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