Esqueleto presupuesto

This commit is contained in:
2021-05-10 23:34:58 -05:00
parent a05bc18d5a
commit e3abbfd0dc
17 changed files with 677 additions and 29 deletions

View File

@@ -0,0 +1,83 @@
<template>
<div class="fixed-plugin" v-click-outside="closeDropDown">
<div class="dropdown show-dropdown" :class="{ show: isOpen }">
<a data-toggle="dropdown" class="settings-icon">
<i class="fa fa-plus fa-2x" @click="toggleDropDown"> </i>
</a>
<span class="dropdown-menu" :class="{ show: isOpen }">
<card>
<template slot="header">
<img v-if="isUpdate === false" src="img//card-primary.png" alt="" />
<img v-else src="img//card-info.png" alt="" />
</template>
<base-input
label="Fecha"
v-model="newIngreso.fecha"
type="Date"
required
></base-input>
<base-input
label="Descripcion"
v-model="newIngreso.detalle"
></base-input>
<base-input
label="Valor"
type="Number"
v-model="newIngreso.valor"
></base-input>
<base-button
v-if="isUpdate === false"
type="primary"
class="mb-3 col-12"
size="lg"
@click="saveIngreso()"
>Guardar</base-button
>
<base-button
v-else
type="info"
class="mb-3 col-12"
size="lg"
@click="updateIngreso(newIngreso._id)"
>Actualizar</base-button
>
</card>
</span>
</div>
</div>
</template>
<script>
export default {
name: "Fingreso",
props: ["newIngreso", "saveIngreso", "isUpdate", "updateIngreso", "openForm"],
data() {
return {
isOpen: this.openForm,
};
},
methods: {
toggleDropDown() {
this.isOpen = !this.isOpen;
},
closeDropDown() {
this.isOpen = false;
},
},
};
</script>
<style scoped lang="scss">
.settings-icon {
cursor: pointer;
}
.badge-vue {
background-color: #ffffff;
}
.color_fondo {
background-color: #ffffff;
}
</style>

View File

@@ -0,0 +1,70 @@
<template>
<!-- <div class="fixed-plugin" v-click-outside="closeDropDown"> -->
<div class="dropdown show-dropdown" :class="{ show: isOpen }">
<a data-toggle="dropdown" class="settings-icon">
<i class="btn btn-info" @click="toggleDropDown">Nuevo </i>
</a>
<span class="dropdown-menu" :class="{ show: isOpen }">
<card>
<template slot="header">
<img src="img//card-info.png" alt="" />
</template>
<base-input
label="Fecha"
v-model="newPresupuesto.fecha"
type="Date"
required
></base-input>
<base-input
label="Descripcion"
v-model="newPresupuesto.detalle"
></base-input>
<base-button
type="info"
class="mb-3 col-12"
size="lg"
@click="savePresupuesto()"
>Guardar</base-button
>
</card>
</span>
</div>
<!-- </div> -->
</template>
<script>
export default {
name: "Fpresupuesto",
props: ["newPresupuesto", "savePresupuesto"],
data() {
return {
isOpen: false,
};
},
methods: {
toggleDropDown() {
this.isOpen = !this.isOpen;
},
closeDropDown() {
this.isOpen = false;
},
},
};
</script>
<style scoped lang="scss">
.settings-icon {
cursor: pointer;
}
.badge-vue {
background-color: #ffffff;
}
.color_fondo {
background-color: #ffffff;
}
</style>

View File

@@ -16,11 +16,11 @@
<span class="navbar-toggler-bar bar3"></span> <span class="navbar-toggler-bar bar3"></span>
</button> </button>
</div> </div>
<a class="navbar-brand ml-xl-3 ml-5" href="#pablo">{{ routeName }}</a> <!-- <a class="navbar-brand ml-xl-3 ml-5" href="#">{{ routeName }}</a> -->
</div> </div>
<ul class="navbar-nav" :class="$rtl.isRTL ? 'mr-auto' : 'ml-auto'"> <ul class="navbar-nav" :class="$rtl.isRTL ? 'mr-auto' : 'ml-auto'">
<div class="search-bar input-group" @click="searchModalVisible = true"> <!-- <div class="search-bar input-group" @click="searchModalVisible = true">
<button <button
class="btn btn-link" class="btn btn-link"
id="search-button" id="search-button"
@@ -29,9 +29,10 @@
> >
<i class="tim-icons icon-zoom-split"></i> <i class="tim-icons icon-zoom-split"></i>
</button> </button>
<!-- You can choose types of search input -->
</div>
<modal </div> -->
<!-- <modal
:show.sync="searchModalVisible" :show.sync="searchModalVisible"
class="modal-search" class="modal-search"
id="searchModal" id="searchModal"
@@ -46,7 +47,7 @@
id="inlineFormInputGroup" id="inlineFormInputGroup"
placeholder="SEARCH" placeholder="SEARCH"
/> />
</modal> </modal> -->
<base-dropdown <base-dropdown
tag="li" tag="li"
:menu-on-right="!$rtl.isRTL" :menu-on-right="!$rtl.isRTL"
@@ -62,15 +63,15 @@
<b class="caret d-none d-lg-block d-xl-block"></b> <b class="caret d-none d-lg-block d-xl-block"></b>
<p class="d-lg-none">Log out</p> <p class="d-lg-none">Log out</p>
</template> </template>
<li class="nav-link"> <!-- <li class="nav-link">
<a href="#" class="nav-item dropdown-item">Profile</a> <a href="#" class="nav-item dropdown-item">Profile</a>
</li> </li> -->
<li class="nav-link"> <li class="nav-link">
<a href="#" class="nav-item dropdown-item">Settings</a> <NuxtLink to="/configuracion" class="dropdown-item">Configuración</NuxtLink>
</li> </li>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<li class="nav-link"> <li class="nav-link">
<a @click="salir()" class="nav-item dropdown-item">Log out</a> <a @click="salir()" class="nav-item dropdown-item">Salir</a>
</li> </li>
</base-dropdown> </base-dropdown>
</ul> </ul>

View File

@@ -120,6 +120,10 @@ export default {
return this.$slots.default; return this.$slots.default;
} }
}, },
mounted() {
this.$store.dispatch("getCategorias");
this.$store.dispatch("getMetodos");
},
methods: { methods: {
toggleMenu() { toggleMenu() {
this.$emit('change', !this.show); this.$emit('change', !this.show);

View File

@@ -59,7 +59,8 @@ export default {
// Axios module configuration (https://go.nuxtjs.dev/config-axios) // Axios module configuration (https://go.nuxtjs.dev/config-axios)
axios: { axios: {
baseURL: "http://127.0.0.1:4000/api" //baseURL: "http://192.168.1.111:4000/api"
baseURL:"http://localhost:4000/api"
}, },
/* /*

View File

@@ -34,6 +34,7 @@
prop="categoria" prop="categoria"
label="Categoria" label="Categoria"
sortable sortable
></el-TableColumn> ></el-TableColumn>
<el-TableColumn> <el-TableColumn>
<div slot-scope="{ row, $index }"> <div slot-scope="{ row, $index }">
@@ -108,14 +109,23 @@ export default {
}; };
}, },
mounted() { mounted() {
// this.$store.dispatch("getCategorias");
//this.$store.dispatch("getMetodos");
this.newCompra.fecha = this.$store.state.fecha; this.newCompra.fecha = this.$store.state.fecha;
this.$store.dispatch("getCategorias");
this.$store.dispatch("getMetodos");
this.categorias = this.$store.state.categorias; this.categorias = this.$store.state.categorias;
this.metodos_pago = this.$store.state.metodos_de_pago; this.metodos_pago = this.$store.state.metodos_de_pago;
this.getCompras(); this.getCompras();
}, },
methods: { methods: {
cell(row, column, cellValue, index) { cell(row, column, cellValue, index) {
return this.formatMoneda(cellValue); return this.formatMoneda(cellValue);
@@ -203,9 +213,10 @@ export default {
this.newCompra.categoria = ""; this.newCompra.categoria = "";
this.isUpdate = false; this.isUpdate = false;
}, },
getIcon(name) { getIcon(row, column, cellValue, index) {
const found = this.$store.state.categorias.find((ic) => ic.name === name); const found = this.$store.state.categorias.filter((ic) => ic.name === cellValue)[0];
console.log(found); //console.log(found);
return `<i class="${found.icon}"></i>${cellValue}`
}, },
formatMoneda(dato) { formatMoneda(dato) {

View File

@@ -1,17 +1,258 @@
<template> <template>
<div> <div>
<Prueba /> <div class="row">
<card class="col-12">
<el-table
:data="
ingresos.filter(
(data) =>
!search ||
data.detalle.toLowerCase().includes(search.toLowerCase()) ||
data.fecha.toLowerCase().includes(search.toLowerCase())
)
"
border
empty-text="No hay compras realizadas"
stripe
:summary-method="getSummaries"
show-summary
style="width: 100%"
>
<el-TableColumn prop="fecha" label="Fecha" sortable></el-TableColumn>
<el-TableColumn prop="detalle" label="Detalle" sortable>
</el-TableColumn>
<el-TableColumn prop="valor" label="Valor" sortable :formatter="cell">
</el-TableColumn>
<el-TableColumn>
<div slot-scope="{ row, $index }">
<el-tooltip content="Edit" effect="light">
<base-button
type="success"
icon
size="sm"
class="btn-link"
@click="updateIngresoClic($index)"
>
<i class="el-icon-edit"></i>
</base-button>
</el-tooltip>
<el-tooltip content="Delete" effect="light">
<base-button
type="danger"
icon
size="sm"
class="btn-link"
@click="deleteIngreso(row._id)"
>
<i class="el-icon-delete-solid"></i>
</base-button>
</el-tooltip>
</div>
<template slot="header" slot-scope="scope">
<el-input v-model="search" size="mini" placeholder="Buscar" />
</template>
</el-TableColumn>
</el-table>
</card>
<Fingreso
:newIngreso="newIngreso"
:saveIngreso="saveIngreso"
:updateIngreso="updateIngreso"
:isUpdate="isUpdate"
:openForm="openForm"
/>
</div>
</div> </div>
</template> </template>
<script> <script>
import { Table, TableColumn } from "element-ui";
import { Select, Option } from "element-ui";
export default { export default {
middleware: 'authenticated', middleware: "authenticated",
components: {
[Table.name]: Table,
[TableColumn.name]: TableColumn,
[Option.name]: Option,
[Select.name]: Select,
},
data() {
return {
newIngreso: {
fecha: "",
detalle: "",
valor: 0,
},
ingresos: [],
search: "",
isUpdate: false,
openForm: false,
};
},
mounted() {
// this.$store.dispatch("getCategorias");
//this.$store.dispatch("getMetodos");
this.newIngreso.fecha = this.$store.state.fecha;
this.getIngresos();
},
methods: {
cell(row, column, cellValue, index) {
return this.formatMoneda(cellValue);
},
getIngresos() {
const axiosHeader = {
headers: {
token: this.$store.state.auth.token,
},
};
this.$axios
.get("/ingreso", axiosHeader)
.then((res) => {
console.log(res.data.data);
this.ingresos = res.data.data;
})
.catch((e) => console.log(e));
},
saveIngreso() {
if (this.checkFormulario() === false) {
return;
} }
const axiosHeader = {
headers: {
token: this.$store.state.auth.token,
},
};
const toSend = this.newIngreso;
console.log(axiosHeader.data);
this.$axios
.post("/ingreso", toSend, axiosHeader)
.then((res) => {
this.clearFormIngreso();
console.log(res.data.status);
this.getIngresos();
})
.catch((e) => console.log(e));
},
updateIngreso(id) {
const axiosHeader = {
headers: {
token: this.$store.state.auth.token,
},
};
const toSend = this.newIngreso;
console.log(axiosHeader.data);
this.$axios
.put("/ingreso", toSend, axiosHeader)
.then((res) => {
this.clearFormIngreso();
console.log(res.data.status);
this.getIngresos();
})
.catch((e) => console.log(e));
},
updateIngresoClic(id) {
this.isUpdate = true;
this.newIngreso = JSON.parse(JSON.stringify(this.ingresos[id]));
this.openForm = !this.openForm;
},
deleteIngreso(id) {
const axiosHeader = {
headers: {
token: this.$store.state.auth.token,
},
params: {
id: id,
},
};
this.$axios
.delete("/ingreso", axiosHeader)
.then((res) => {
console.log(res.data);
this.getIngresos();
this.clearFormIngreso();
})
.catch((e) => console.log(e));
},
clearFormIngreso() {
this.newIngreso._id = "";
this.newIngreso.detalle = "";
this.newIngreso.valor = 0;
this.isUpdate = false;
},
formatMoneda(dato) {
var num = dato;
if (!isNaN(num)) {
num = num
.toString()
.split("")
.reverse()
.join("")
.replace(/(?=\d*\.?)(\d{3})/g, "$1.");
num = num.split("").reverse().join("").replace(/^[\.]/, "");
//return '$' +num;
return `$ ${num}`;
}
},
getSummaries(param) {
const { columns, data } = param;
const sums = [];
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = "Valor total";
return;
}
const values = data.map((item) => Number(item[column.property]));
if (!values.every((value) => isNaN(value))) {
sums[index] = this.formatMoneda(
values.reduce((prev, curr) => {
const value = Number(curr);
if (!isNaN(value)) {
return prev + curr;
} else {
return prev;
}
}, 0)
);
} else {
sums[index] = "";
}
});
return sums;
},
checkFormulario() {
if (this.newIngreso.valor === 0) {
this.$notify({
type: "danger",
icon: "tim-icons icon-alert-circle-exc",
message: "el campo valor no puede estar en cero :(",
});
return false;
}
if (this.newIngreso.detalle === "") {
this.$notify({
type: "danger",
icon: "tim-icons icon-alert-circle-exc",
message: "el campo detalle no puede estar vacio :(",
});
return false;
}
return true;
},
},
};
</script> </script>
<style> <style>
</style> </style>

View File

@@ -1,14 +1,177 @@
<template> <template>
<h1>Presupuesto</h1> <div>
<card>
<div class="row">
<base-input class="col-6">
<select class="form-control">
<option>Presupuesto</option>
<option>Presupuesto 2</option>
</select>
</base-input>
<div class="row pull-right pull-buttom">
<div class="col-12">
<Fpresupuesto
:newPresupuesto="newPresupuesto"
:savePresupuesto="savePresupuesto"
/>
</div>
</div>
</div>
</card>
<card title="Presupuesto">
<div class="row">
<div class="col-6">
<el-table
:data="selectedPresupuesto.datos"
border
empty-text="No hay items"
stripe
style="width: 100%"
height="300"
>
<el-TableColumn prop="detalle" label="Detalle" sortable>
</el-TableColumn>
<el-TableColumn prop="valor" label="Valor" sortable>
</el-TableColumn>
<el-TableColumn>
<div slot-scope="{ row, $index }">
<el-tooltip content="Delete" effect="light">
<base-button
type="danger"
icon
size="sm"
class="btn-link"
@click="deleteIngreso(row._id)"
>
<i class="el-icon-delete-solid"></i>
</base-button>
</el-tooltip>
</div>
</el-TableColumn>
</el-table>
</div>
<div class="col-6">
<base-input v-model="newItem.detalle" label="Descripción">
</base-input>
<base-input v-model="newItem.valor" type="Number">
</base-input>
<base-input>
<select class="form-control" v-model="newItem.tipo">
<option>Ingreso</option>
<option>Egreso</option>
</select>
</base-input>
<base-button
type="info"
class="mb-3 col-12"
size="lg"
@click="addItem()"
>Guardar</base-button
>
</div>
</div>
</card>
<card>
<div class="row">
<div class="col-4">
<b>Ingresos: </b> {{formatMoneda(totalIngresos)}}
</div>
<div class="col-4">
<b>Egreso:</b> <span>{{formatMoneda(totalEgresos)}} </span>
</div>
<div class="col-4">
<b>Total:<span :class="[total<0?'text-danger':'text-success']"> {{formatMoneda(total)}}</span></b>
</div>
</div>
</card>
</div>
</template> </template>
<script> <script>
export default { import { Table, TableColumn } from "element-ui";
middleware: 'authenticated',
export default {
components: {
[Table.name]: Table,
[TableColumn.name]: TableColumn,
},
middleware: "authenticated",
data() {
return {
newPresupuesto: {
fecha: "",
detalle: "",
},
selectedPresupuesto:{
_id:"",
fecha:"",
nombrePresupuesto:"",
datos:[]
},
newItem: {
detalle:"",
valor: 0,
tipo:"Egreso"
},
presupuestos: [],
totalIngresos:0,
totalEgresos:0,
total:0
};
},
methods: {
savePresupuesto() {},
addItem() {
this.selectedPresupuesto.datos.push(JSON.parse(JSON.stringify(this.newItem)))
this.sumItems()
},
sumItems(){
this.totalIngresos=this.selectedPresupuesto.datos.reduce((acc,x) => x.tipo ==="Ingreso"?acc + Number(x.valor):acc,0)
this.totalEgresos = this.selectedPresupuesto.datos.reduce((acc,x) => x.tipo==="Egreso"?acc + Number(x.valor):acc,0)
this.total = this.totalIngresos - this.totalEgresos
},
formatMoneda(dato) {
var num = dato;
if (!isNaN(num)) {
num = Math.abs(num)
.toString()
.split("")
.reverse()
.join("")
.replace(/(?=\d*\.?)(\d{3})/g, "$1.");
num = num.split("").reverse().join("").replace(/^[\.]/, "");
return `$ ${num}`;
} }
},
},
mounted() {
this.newPresupuesto.fecha = this.$store.state.fecha;
},
};
</script> </script>
<style> <style>
</style> </style>

BIN
APP/static/favicdon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

BIN
APP/static/img/apple-icon.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 805 B

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
APP/static/img/apple-icone.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

View File

@@ -62,7 +62,6 @@ export const actions = {
this.$axios.get("/categoria", axiosHeader) this.$axios.get("/categoria", axiosHeader)
.then(res => { .then(res => {
this.commit("setCategorias", res.data.data); this.commit("setCategorias", res.data.data);
@@ -92,3 +91,4 @@ export const actions = {
} }

View File

@@ -26,6 +26,7 @@ app.use(cors());
//Rutas //Rutas
app.use('/api',require('./routes/users')); app.use('/api',require('./routes/users'));
app.use('/api',require('./routes/compras')) app.use('/api',require('./routes/compras'))
app.use('/api',require('./routes/ingresos'))
app.use('/api',require('./routes/categorias')) app.use('/api',require('./routes/categorias'))
app.use('/api',require('./routes/metodos_pago')) app.use('/api',require('./routes/metodos_pago'))
app.disable('x-powered-by'); app.disable('x-powered-by');

73
routes/ingresos.js Normal file
View File

@@ -0,0 +1,73 @@
const router = require("express").Router();
const Ingreso = require("../models/ingresos");
const { checkAuth } = require("../middlewares/authentication");
router.get("/ingreso", checkAuth, async (req, res) => {
var Ingresos;
Ingresos = await Ingreso.find({ user: req.userData._id }).sort({
fecha: "desc",
});
return res.send(
{
status:"ok",
data:Ingresos
}
)
});
router.post("/ingreso", checkAuth, async (req, res) => {
const { fecha, detalle, valor} = req.body;
const newIngreso = new Ingreso({
fecha,
detalle,
valor
});
console.log(newIngreso)
newIngreso.user = req.userData._id;
await newIngreso.save();
res.json({
status:"OK"
})
});
router.put("/ingreso", checkAuth, async (req, res) => {
const { _id, fecha, detalle, valor } = req.body;
const Ingreso_edit = await Ingreso.findOne({ _id: _id });
await Ingreso_edit.updateOne({ fecha, detalle, valor});
res.json({
status:"OK"
})
});
router.delete("/ingreso", checkAuth, async (req, res) => {
try{
const userId = req.userData._id;
const id = req.query.id;
const resultado = await Ingreso.deleteOne({user:userId,_id:id});
return res.json({status:"ok",data: resultado})
}
catch(error){
console.log(error);
return res.status(500).json({status:"fail",error:error})
}
});
module.exports = router;