Merge pull request 'remove frontend' (#1) from remove_frontend into main

Reviewed-on: #1
This commit is contained in:
mono 2025-02-08 15:04:22 -05:00
commit 0b1f83431f
58 changed files with 3 additions and 7820 deletions

View File

@ -1,2 +1,2 @@
DON CONFIAO
============
DON CONFIAO DJANGO BACKEND
==========================

View File

@ -1,13 +1,4 @@
services:
nginx:
build:
context: ./
dockerfile: nginx.Dockerfile
ports:
- "7000:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./tienda_ilusion/don_confiao/static/frontend:/var/www/frontend/
django:
build:
context: ./
@ -15,13 +6,5 @@ services:
volumes:
- ./tienda_ilusion:/app/
ports:
- "7001:9090"
frontend:
build:
context: ./
dockerfile: vuetify.Dockerfile
volumes:
- ./tienda_ilusion/don_confiao/frontend/don-confiao:/app/
ports:
- "7003:3000"
- "7000:9090"

View File

@ -1,10 +0,0 @@
FROM nginx:latest
# Copiamos el archivo de configuración NGINX
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Establecemos la variable de entorno para el proxy inverso
ENV DJANGO_PROXY_URL http://django:8000
# Creamos un directorio estático
RUN mkdir -p /var/www/frontend

View File

@ -1,25 +0,0 @@
server {
listen 80;
server_name donconfiao.org;
# location /frontend {
# alias /var/www/frontend/;
# autoindex on;
# }
location /frontend {
proxy_pass http://frontend:3000/frontend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://django:9090;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

View File

@ -1,22 +0,0 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -1,79 +0,0 @@
# Vuetify (Default)
This is the official scaffolding tool for Vuetify, designed to give you a head start in building your new Vuetify application. It sets up a base template with all the necessary configurations and standard directory structure, enabling you to begin development without the hassle of setting up the project from scratch.
## ❗️ Important Links
- 📄 [Docs](https://vuetifyjs.com/)
- 🚨 [Issues](https://issues.vuetifyjs.com/)
- 🏬 [Store](https://store.vuetifyjs.com/)
- 🎮 [Playground](https://play.vuetifyjs.com/)
- 💬 [Discord](https://community.vuetifyjs.com)
## 💿 Install
Set up your project using your preferred package manager. Use the corresponding command to install the dependencies:
| Package Manager | Command |
|---------------------------------------------------------------|----------------|
| [yarn](https://yarnpkg.com/getting-started) | `yarn install` |
| [npm](https://docs.npmjs.com/cli/v7/commands/npm-install) | `npm install` |
| [pnpm](https://pnpm.io/installation) | `pnpm install` |
| [bun](https://bun.sh/#getting-started) | `bun install` |
After completing the installation, your environment is ready for Vuetify development.
## ✨ Features
- 🖼️ **Optimized Front-End Stack**: Leverage the latest Vue 3 and Vuetify 3 for a modern, reactive UI development experience. [Vue 3](https://v3.vuejs.org/) | [Vuetify 3](https://vuetifyjs.com/en/)
- 🗃️ **State Management**: Integrated with [Pinia](https://pinia.vuejs.org/), the intuitive, modular state management solution for Vue.
- 🚦 **Routing and Layouts**: Utilizes Vue Router for SPA navigation and vite-plugin-vue-layouts for organizing Vue file layouts. [Vue Router](https://router.vuejs.org/) | [vite-plugin-vue-layouts](https://github.com/JohnCampionJr/vite-plugin-vue-layouts)
- ⚡ **Next-Gen Tooling**: Powered by Vite, experience fast cold starts and instant HMR (Hot Module Replacement). [Vite](https://vitejs.dev/)
- 🧩 **Automated Component Importing**: Streamline your workflow with unplugin-vue-components, automatically importing components as you use them. [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components)
These features are curated to provide a seamless development experience from setup to deployment, ensuring that your Vuetify application is both powerful and maintainable.
## 💡 Usage
This section covers how to start the development server and build your project for production.
### Starting the Development Server
To start the development server with hot-reload, run the following command. The server will be accessible at [http://localhost:3000](http://localhost:3000):
```bash
yarn dev
```
(Repeat for npm, pnpm, and bun with respective commands.)
> Add NODE_OPTIONS='--no-warnings' to suppress the JSON import warnings that happen as part of the Vuetify import mapping. If you are on Node [v21.3.0](https://nodejs.org/en/blog/release/v21.3.0) or higher, you can change this to NODE_OPTIONS='--disable-warning=5401'. If you don't mind the warning, you can remove this from your package.json dev script.
### Building for Production
To build your project for production, use:
```bash
yarn build
```
(Repeat for npm, pnpm, and bun with respective commands.)
Once the build process is completed, your application will be ready for deployment in a production environment.
## 💪 Support Vuetify Development
This project is built with [Vuetify](https://vuetifyjs.com/en/), a UI Library with a comprehensive collection of Vue components. Vuetify is an MIT licensed Open Source project that has been made possible due to the generous contributions by our [sponsors and backers](https://vuetifyjs.com/introduction/sponsors-and-backers/). If you are interested in supporting this project, please consider:
- [Requesting Enterprise Support](https://support.vuetifyjs.com/)
- [Sponsoring John on Github](https://github.com/users/johnleider/sponsorship)
- [Sponsoring Kael on Github](https://github.com/users/kaelwd/sponsorship)
- [Supporting the team on Open Collective](https://opencollective.com/vuetify)
- [Becoming a sponsor on Patreon](https://www.patreon.com/vuetify)
- [Becoming a subscriber on Tidelift](https://tidelift.com/subscription/npm/vuetify)
- [Making a one-time donation with Paypal](https://paypal.me/vuetify)
## 📑 License
[MIT](http://opensource.org/licenses/MIT)
Copyright (c) 2016-present Vuetify, LLC

View File

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Don Confiao</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@ -1,20 +0,0 @@
{
"compilerOptions": {
"allowJs": true,
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "bundler",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,39 +0,0 @@
{
"name": "don-confiao",
"version": "0.0.0",
"scripts": {
"dev": "vite --host 0.0.0.0",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --fix --ignore-path .gitignore"
},
"dependencies": {
"@mdi/font": "7.4.47",
"core-js": "^3.37.1",
"roboto-fontface": "*",
"vee-validate": "^4.14.6",
"vue": "^3.4.31",
"vuetify": "^3.6.11"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.5",
"eslint": "^8.57.0",
"eslint-config-standard": "^17.1.0",
"eslint-config-vuetify": "^1.0.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.4.0",
"eslint-plugin-vue": "^9.27.0",
"pinia": "^2.1.7",
"sass": "1.77.6",
"unplugin-auto-import": "^0.17.6",
"unplugin-fonts": "^1.1.1",
"unplugin-vue-components": "^0.27.2",
"unplugin-vue-router": "^0.10.0",
"vite": "^5.3.3",
"vite-plugin-vue-layouts": "^0.11.0",
"vite-plugin-vuetify": "^2.0.3",
"vue-router": "^4.4.0"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

View File

@ -1,19 +0,0 @@
<template>
<v-app>
<NavBar />
<v-main>
<router-view />
</v-main>
</v-app>
</template>
<script>
import NavBar from './components/NavBar.vue';
export default {
name: 'App',
components: {
NavBar,
},
}
</script>

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"/></svg>

Before

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@ -1,6 +0,0 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M261.126 140.65L164.624 307.732L256.001 466L377.028 256.5L498.001 47H315.192L261.126 140.65Z" fill="#1697F6"/>
<path d="M135.027 256.5L141.365 267.518L231.64 111.178L268.731 47H256H14L135.027 256.5Z" fill="#AEDDFF"/>
<path d="M315.191 47C360.935 197.446 256 466 256 466L164.624 307.732L315.191 47Z" fill="#1867C0"/>
<path d="M268.731 47C76.0026 47 141.366 267.518 141.366 267.518L268.731 47Z" fill="#7BC6FF"/>
</svg>

Before

Width:  |  Height:  |  Size: 526 B

View File

@ -1,79 +0,0 @@
<template>
<v-footer height="40" app>
<a
v-for="item in items"
:key="item.title"
:href="item.href"
:title="item.title"
class="d-inline-block mx-2 social-link"
rel="noopener noreferrer"
target="_blank"
>
<v-icon
:icon="item.icon"
:size="item.icon === '$vuetify' ? 24 : 16"
/>
</a>
<div
class="text-caption text-disabled"
style="position: absolute; right: 16px;"
>
&copy; 2016-{{ (new Date()).getFullYear() }} <span class="d-none d-sm-inline-block">Vuetify, LLC</span>
<a
class="text-decoration-none on-surface"
href="https://vuetifyjs.com/about/licensing/"
rel="noopener noreferrer"
target="_blank"
>
MIT License
</a>
</div>
</v-footer>
</template>
<script setup>
const items = [
{
title: 'Vuetify Documentation',
icon: `$vuetify`,
href: 'https://vuetifyjs.com/',
},
{
title: 'Vuetify Support',
icon: 'mdi-shield-star-outline',
href: 'https://support.vuetifyjs.com/',
},
{
title: 'Vuetify X',
icon: ['M2.04875 3.00002L9.77052 13.3248L1.99998 21.7192H3.74882L10.5519 14.3697L16.0486 21.7192H22L13.8437 10.8137L21.0765 3.00002H19.3277L13.0624 9.76874L8.0001 3.00002H2.04875ZM4.62054 4.28821H7.35461L19.4278 20.4308H16.6937L4.62054 4.28821Z'],
href: 'https://x.com/vuetifyjs',
},
{
title: 'Vuetify GitHub',
icon: `mdi-github`,
href: 'https://github.com/vuetifyjs/vuetify',
},
{
title: 'Vuetify Discord',
icon: ['M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z'],
href: 'https://community.vuetifyjs.com/',
},
{
title: 'Vuetify Reddit',
icon: `mdi-reddit`,
href: 'https://reddit.com/r/vuetifyjs',
},
]
</script>
<style scoped lang="sass">
.social-link :deep(.v-icon)
color: rgba(var(--v-theme-on-background), var(--v-disabled-opacity))
text-decoration: none
transition: .2s ease-in-out
&:hover
color: rgba(25, 118, 210, 1)
</style>

View File

@ -1,58 +0,0 @@
<template>
<v-dialog v-model="dialog" max-width="400">
<v-card>
<v-card-title>Calcular Devuelta</v-card-title>
<v-card-text>
<v-text-field
v-model.number="purchase"
label="Total de la compra"
type="number"
prefix="$"
readonly
></v-text-field>
<v-text-field
v-model.number="money"
label="Dinero"
type="number"
prefix="$"
></v-text-field>
<v-text-field
v-model.number="change_cash"
label="Devuelta"
type="number"
prefix="$"
readonly
></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="dialog = false">Cerrar</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
props: {
total_purchase: {
type: Number,
required: true
}
},
data() {
return {
dialog: false,
money: null,
}
},
computed: {
purchase() {
return this.total_purchase
},
change_cash() {
return (this.money || 0) - this.total_purchase
},
},
}
</script>

View File

@ -1,50 +0,0 @@
<template>
<v-dialog v-model="dialog" persistent>
<v-card>
<v-card-title>
Ingrese el código
</v-card-title>
<v-card-text>
<v-form id="code-form" @submit.prevent="verifyCode">
<v-text-field v-model="code" label="Código" type="password" autocomplete="off" />
</v-form>
</v-card-text>
<v-card-actions>
<v-btn type="submit" form="code-form">Aceptar</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
import { inject } from 'vue';
export default {
data() {
return {
api: inject('api'),
dialog: true,
code: '',
};
},
methods: {
verifyCode() {
this.api.isValidAdminCode(this.code)
.then(data => {
if (data['validCode']) {
this.$emit('code-verified', true);
this.dialog = false;
} else {
alert('Código incorrecto');
this.$emit('code-verified', false);
}
})
.catch(error => {
alert('Error al validar el código');
this.$emit('code-verified', false);
console.error(error);
});
}
},
}
</script>

View File

@ -1,99 +0,0 @@
<template>
<v-dialog v-model="showModal" max-width="600px">
<v-card>
<v-card-title>
<span class="headline">Información del Cliente</span>
</v-card-title>
<v-card-text>
<v-form ref="form" v-model="valid">
<v-text-field
v-model="customer.name"
label="Nombre"
:rules="[rules.required]"
></v-text-field>
<v-text-field
v-model="customer.address"
label="Direccion"
:rules="[rules.required]"
required
></v-text-field>
<v-text-field
v-model="customer.email"
label="Correo Electrónico"
:rules="[rules.required, rules.email]"
required
></v-text-field>
<v-text-field
v-model="customer.phone"
label="Teléfono"
:rules="[rules.required]"
required
></v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="closeModal">Cancelar</v-btn>
<v-btn color="blue darken-1" text @click="submitForm">Guardar</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
data() {
return {
showModal: false,
api: inject('api'),
valid: false,
customer: {
name: '',
address: '',
email: '',
phone: ''
},
rules: {
required: value => !!value || 'Este campo es requerido.',
email: value => {
const pattern = /^[^ ]+@[^ ]+\.[a-z]{2,3}$/;
return pattern.test(value) || 'El correo no es válido.';
}
}
};
},
methods: {
openModal() {
this.showModal = true;
},
closeModal() {
this.showModal = false;
this.resetForm();
},
async submitForm() {
console.log(this.customer)
if (this.$refs.form.validate()) {
this.api.createCustomer(this.customer)
.then(data => {
console.log('Cliente Guardado:', data);
this.$emit('customerCreated', data);
this.closeModal();
})
.catch(error => console.error('Error:', error));
}
},
resetForm() {
this.customer = {
name: '',
address: '',
email: '',
phone: ''
};
this.$refs.form.reset();
},
}
};
</script>
<style scoped>
</style>

View File

@ -1,26 +0,0 @@
<template>
<span>{{ formattedValue }}</span>
</template>
<script>
export default {
props: {
value: {
type: Number,
required: true
},
locale: {
type: String,
default: 'es-CO',
},
currency: {
type: String,
default: 'COP',
},
},
computed: {
formattedValue() {
return new Intl.NumberFormat(this.locale, { style: 'currency', currency: this.currency }).format(this.value);
},
},
}
</script>

View File

@ -1,157 +0,0 @@
<template>
<v-container class="fill-height">
<v-responsive
class="align-centerfill-height mx-auto"
max-width="900"
>
<v-img
class="mb-4"
height="150"
src="@/assets/logo.png"
/>
<div class="text-center">
<div class="text-body-2 font-weight-light mb-n1">Welcome to</div>
<h1 class="text-h2 font-weight-bold">Vuetify</h1>
</div>
<div class="py-4" />
<v-row>
<v-col cols="12">
<v-card
class="py-4"
color="surface-variant"
image="https://cdn.vuetifyjs.com/docs/images/one/create/feature.png"
prepend-icon="mdi-rocket-launch-outline"
rounded="lg"
variant="outlined"
>
<template #image>
<v-img position="top right" />
</template>
<template #title>
<h2 class="text-h5 font-weight-bold">Get started</h2>
</template>
<template #subtitle>
<div class="text-subtitle-1">
Replace this page by removing <v-kbd>{{ `<HelloWorld />` }}</v-kbd> in <v-kbd>pages/index.vue</v-kbd>.
</div>
</template>
<v-overlay
opacity=".12"
scrim="primary"
contained
model-value
persistent
/>
</v-card>
</v-col>
<v-col cols="6">
<v-card
append-icon="mdi-open-in-new"
class="py-4"
color="surface-variant"
href="https://vuetifyjs.com/"
prepend-icon="mdi-text-box-outline"
rel="noopener noreferrer"
rounded="lg"
subtitle="Learn about all things Vuetify in our documentation."
target="_blank"
title="Documentation"
variant="text"
>
<v-overlay
opacity=".06"
scrim="primary"
contained
model-value
persistent
/>
</v-card>
</v-col>
<v-col cols="6">
<v-card
append-icon="mdi-open-in-new"
class="py-4"
color="surface-variant"
href="https://vuetifyjs.com/introduction/why-vuetify/#feature-guides"
prepend-icon="mdi-star-circle-outline"
rel="noopener noreferrer"
rounded="lg"
subtitle="Explore available framework Features."
target="_blank"
title="Features"
variant="text"
>
<v-overlay
opacity=".06"
scrim="primary"
contained
model-value
persistent
/>
</v-card>
</v-col>
<v-col cols="6">
<v-card
append-icon="mdi-open-in-new"
class="py-4"
color="surface-variant"
href="https://vuetifyjs.com/components/all"
prepend-icon="mdi-widgets-outline"
rel="noopener noreferrer"
rounded="lg"
subtitle="Discover components in the API Explorer."
target="_blank"
title="Components"
variant="text"
>
<v-overlay
opacity=".06"
scrim="primary"
contained
model-value
persistent
/>
</v-card>
</v-col>
<v-col cols="6">
<v-card
append-icon="mdi-open-in-new"
class="py-4"
color="surface-variant"
href="https://discord.vuetifyjs.com"
prepend-icon="mdi-account-group-outline"
rel="noopener noreferrer"
rounded="lg"
subtitle="Connect with Vuetify developers."
target="_blank"
title="Community"
variant="text"
>
<v-overlay
opacity=".06"
scrim="primary"
contained
model-value
persistent
/>
</v-card>
</v-col>
</v-row>
</v-responsive>
</v-container>
</template>
<script setup>
//
</script>

View File

@ -1,19 +0,0 @@
<template>
<div class="modal">
<div class="head">
<p>Nuevo Movimiento</p>
<img @click="close" src="@/assets/close-icon.svg" alt="cerrar"/>
</div>
<div class="body">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { defineEmits } from "vue";
const emit = defineEmits(["close"]);
const close = () => emit("close");
</script>

View File

@ -1,47 +0,0 @@
<template>
<v-app-bar color="primary" prominent>
<v-app-bar-nav-icon variant="text" @click.stop="drawer = !drawer"></v-app-bar-nav-icon>
<v-toolbar-title>Menu</v-toolbar-title>
<v-spacer></v-spacer>
<template v-if="$vuetify.display.mdAndUp">
<v-btn icon="mdi-magnify" variant="text"></v-btn>
<v-btn icon="mdi-filter" variant="text"></v-btn>
</template>
<v-btn icon="mdi-dots-vertical" variant="text"></v-btn>
</v-app-bar>
<v-navigation-drawer v-model="drawer"
:location="$vuetify.display.mobile ? 'bottom' : undefined"
temporary>
<v-list>
<v-list-item v-for="item in menuItems" :key="item.title" @click="navigate(item.route)">
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-navigation-drawer>
</template>
<script>
export default {
name: 'NavBar',
data: () => ({
drawer: false,
group: null,
menuItems: [
{ title: 'Inicio', route: '/'},
{ title: 'Comprar', route:'/comprar'},
{ title: 'Cuadrar tarro', route: '/cuadrar_tarro'},
{ title: 'Cuadres de tarro', route: '/cuadres_de_tarro'},
],
}),
watch: {
group () {
this.drawer = false
},
},
methods: {
navigate(route) {
this.$router.push(route);
},
}
}
</script>

View File

@ -1,342 +0,0 @@
<template>
<v-container>
<v-form ref="purchase" v-model="valid" @change="onFormChange">
<v-row>
<v-col>
<v-autocomplete
v-model="purchase.customer"
:items="filteredClients"
:search="client_search"
no-data-text="No se hallaron clientes"
item-title="name"
item-value="id"
@update:model-value="onFormChange"
label="Cliente"
:rules="[rules.required]"
required
class="mr-4"
></v-autocomplete>
<v-btn color="primary" @click="openModal">Agregar Cliente</v-btn>
<CreateCustomerModal ref="customerModal" @customerCreated="handleNewCustomer"/>
</v-col>
<v-col lg="4">
<v-text-field
v-model="purchase.date"
label="Fecha"
type="datetime-local"
:rules="[rules.required]"
required
readonly
></v-text-field>
</v-col>
</v-row>
<v-textarea
v-model="purchase.notes"
label="Notas"
rows="2"
></v-textarea>
<v-divider></v-divider>
<v-container>
<v-toolbar>
<v-toolbar-title secondary>Productos</v-toolbar-title>
</v-toolbar>
<v-container v-for="(line, index) in purchase.saleline_set" :key="line.id">
<v-row>
<v-col
lg="9">
<v-autocomplete
v-model="line.product"
:items="filteredProducts"
:search="product_search"
@update:modelValue="onProductChange(index)"
no-data-text="No se hallaron productos"
item-title="name"
item-value="id"
item-subtitle="Price"
label="Producto"
:rules="[rules.required]"
required
>
<template v-slot:item="{ props, item }">
<v-list-item v-bind="props" :title="item.raw.name" :subtitle="formatPrice(item.raw.price)"></v-list-item>
</template>
</v-autocomplete>
</v-col>
<v-col
lg="2"
>
<v-text-field
v-model.number="line.quantity"
label="Cantidad"
type="number"
:rules="[rules.required,rules.positive]"
required
></v-text-field>
</v-col>
</v-row>
<v-row>
<v-col>
<v-text-field
v-model.number="line.unit_price"
label="Precio"
type="number"
:rules="[rules.required]"
prefix="$"
required
readonly
></v-text-field>
</v-col>
<v-col>
<v-text-field
v-model="line.measuring_unit"
label="UdM"
persistent-placeholder="true"
readonly
></v-text-field>
</v-col>
<v-col>
<v-text-field
type="number"
:value="calculateSubtotal(line)"
label="Subtotal"
prefix="$"
readonly
disable
persistent-placeholder="true"
></v-text-field>
</v-col>
<v-col>
<v-btn @click="removeLine(index)" color="red">Eliminar</v-btn>
</v-col>
</v-row>
<v-alert type="warning" :duration="2000" closable v-model="show_alert_lines">
No se puede eliminar la única línea.
</v-alert>
</v-container>
<v-btn @click="addLine" color="blue">Agregar</v-btn>
</v-container>
<v-divider></v-divider>
<v-text-field
:value="calculateTotal"
label="Total"
prefix="$"
readonly
persistent-placeholder="true"
></v-text-field>
<v-container v-if="calculateTotal > 0">
<v-select
:items="payment_methods"
v-model="purchase.payment_method"
item-title="text"
item-value="value"
label="Pago en"
:rules="[rules.required]"
required
></v-select>
<v-btn @click="openCasherModal" v-if="purchase.payment_method === 'CASH'">Calcular Devuelta</v-btn>
<CasherModal :total_purchase="calculateTotal" ref="casherModal"</CasherModal>
</v-container>
<v-btn @click="submit" color="green">Comprar</v-btn>
<v-alert type="error" :duration="2000" closable v-model="show_alert_purchase">
Verifique los campos obligatorios.
</v-alert>
</v-form>
</v-container>
</template>
<script>
import CustomerForm from './CreateCustomerModal.vue';
import CasherModal from './CasherModal.vue';
import { inject } from 'vue';
export default {
name: 'DonConfiao',
components: {
CustomerForm,
CasherModal,
},
props: {
msg: String
},
data() {
return {
api: inject('api'),
valid: false,
form_changed: false,
show_alert_lines: false,
show_alert_purchase: false,
client_search: '',
product_search: '',
payment_methods: null,
purchase: {
date: this.getCurrentDate(),
customer: null,
notes: '',
payment_method: null,
saleline_set: [{product:'', unit_price: 0, quantity: 0, unit: ''}],
},
rules: {
required: value => !!value || 'Requerido.',
positive: value => value > 0 || 'La cantidad debe ser mayor que 0.',
},
menuItems: [
{ title: 'Inicio', route: '/'},
{ title: 'Compras', route:'/compras'},
],
clients: [],
products: [],
};
},
created() {
this.fetchClients();
this.fetchProducts();
this.fetchPaymentMethods();
},
watch: {
group () {
this.drawer = false
},
},
beforeMount() {
window.addEventListener('beforeunload', this.confirmLeave);
},
beforeDestroy() {
window.removeEventListener('beforeunload', this.confirmLeave);
},
computed: {
calculateTotal() {
return this.purchase.saleline_set.reduce((total, saleline) => {
return total + this.calculateSubtotal(saleline);
}, 0);
},
filteredClients() {
return this.clients.filter(client => {
if (this.client_search === '') {
return [];
} else {
return client.name.toLowerCase().includes(this.client_search.toLowerCase());
}
});
},
filteredProducts() {
return this.products.filter(product => {
if (this.product_search === '') {
return [];
} else {
return product.name.toLowerCase().includes(this.product_search.toLowerCase());
}
});
},
},
methods: {
openModal() {
this.$refs.customerModal.openModal();
},
onFormChange() {
this.form_changed = true;
},
openCasherModal() {
this.$refs.casherModal.dialog = true
},
confirmLeave(event) {
if (this.form_changed) {
const message = '¿seguro que quieres salir? Perderas la información diligenciada';
event.preventDefault();
event.returnValue = message;
return message;
}
},
getCurrentDate() {
const today = new Date();
const gmtOffSet = -5;
const localDate = new Date(today.getTime() + (gmtOffSet * 60 * 60 * 1000));
// Formatear la fecha y hora en el formato YYYY-MM-DDTHH:MM
const formattedDate = localDate.toISOString().slice(0,16);
return formattedDate;
},
onProductChange(index) {
const selectedProductId = this.purchase.saleline_set[index].product;
const selectedProduct = this.products.find(p => p.id == selectedProductId);
this.purchase.saleline_set[index].unit_price = selectedProduct.price;
this.purchase.saleline_set[index].measuring_unit = selectedProduct.measuring_unit;
},
fetchClients() {
this.api.getCustomers()
.then(data => {
this.clients = data;
})
.catch(error => {
console.error(error);
});
},
handleNewCustomer(newCustomer){
this.clients.push(newCustomer);
this.purchase.customer = newCustomer.id;
},
fetchProducts() {
this.api.getProducts()
.then(data => {
this.products = data;
})
.catch(error => {
console.error(error);
});
},
fetchPaymentMethods() {
this.api.getPaymentMethods()
.then(data => {
this.payment_methods = data;
})
.catch(error => {
console.error(error);
});
},
addLine() {
this.purchase.saleline_set.push({ product: '', unit_price: 0, quantity:0, measuring_unit: ''});
},
removeLine(index) {
if (this.purchase.saleline_set.length > 1) {
this.purchase.saleline_set.splice(index, 1);
} else {
this.show_alert_lines = true;
setTimeout(() => {
this.show_alert_lines = false;
}, 2000);
}
},
calculateSubtotal(line) {
return line.unit_price * line.quantity;
},
async submit() {
this.$refs.purchase.validate();
if (this.valid) {
this.api.createPurchase(this.purchase)
.then(data => {
console.log('Compra enviada:', data);
this.$router.push({
path: "/summary_purchase",
query : {id: parseInt(data.id)}
});
})
.catch(error => console.error('Error al enviarl la compra:', error));
} else {
this.show_alert_purchase = true;
setTimeout(() => {
this.show_alert_purchase = false;
}, 4000);
}
},
navigate(route) {
this.$router.push(route);
},
formatPrice(price) {
return new Intl.NumberFormat('es-ES', { style: 'currency', currency: 'COP' }).format(price);
},
},
mounted() {
this.fetchClients();
}
};
</script>
<style>
</style>

View File

@ -1,35 +0,0 @@
# Components
Vue template files in this folder are automatically imported.
## 🚀 Usage
Importing is handled by [unplugin-vue-components](https://github.com/unplugin/unplugin-vue-components). This plugin automatically imports `.vue` files created in the `src/components` directory, and registers them as global components. This means that you can use any component in your application without having to manually import it.
The following example assumes a component located at `src/components/MyComponent.vue`:
```vue
<template>
<div>
<MyComponent />
</div>
</template>
<script lang="ts" setup>
//
</script>
```
When your template is rendered, the component's import will automatically be inlined, which renders to this:
```vue
<template>
<div>
<MyComponent />
</div>
</template>
<script lang="ts" setup>
import MyComponent from '@/components/MyComponent.vue'
</script>
```

View File

@ -1,200 +0,0 @@
<template>
<v-container>
<v-toolbar>
<v-toolbar-title> Cuadre del Tarro </v-toolbar-title>
</v-toolbar>
<v-card>
<v-card-text>
<v-form ref="taker" v-model="valid">
<v-text-field
v-model="reconciliation.date_time"
label="Fecha"
type="datetime-local"
:rules="[rules.required]"
required
readonly
></v-text-field>
<v-text-field
v-model="reconciliation.reconcilier"
label="Cajero"
:rules="[rules.required]"
required
></v-text-field>
<v-text-field
v-model="reconciliation.total_cash_purchases"
label="Total Ventas en efectivo"
:rules="[rules.required]"
prefix="$"
type="number"
readonly
></v-text-field>
<v-text-field
v-model="reconciliation.cash_taken"
label="Dinero Recogido"
:rules="[rules.required]"
prefix="$"
type="number"
></v-text-field>
<v-text-field
v-model="reconciliation.cash_discrepancy"
label="Descuadre"
:rules="[rules.integer]"
prefix="$"
type="number"
></v-text-field>
<v-btn @click="submit" color="green">Recoger Dinero</v-btn>
</v-form>
</v-card-text>
</v-card>
<v-tabs v-model="selectedTab">
<v-tab
v-for="(purchases, payment_method) in summary.purchases"
:key="payment_method"
:value="payment_method"
>
{{ payment_method }}&nbsp; <CurrencyText :value="totalByMethod(payment_method)"</CurrencyText>
</v-tab>
</v-tabs>
<v-tabs-window v-model="selectedTab">
<v-card>
<v-card-text>
<v-tabs-window-item
v-for="(purchases, payment_method) in summary.purchases"
:key="payment_method"
:value="payment_method"
>
<v-data-table-virtual
:headers="summary.headers"
:items="summary.purchases[payment_method]"
>
<template v-slot:item.id="{ item }">
<v-btn @click="openSummaryModal(item.id)">{{ item.id }}</v-btn>
</template>
<template v-slot:item.total="{ item }">
<CurrencyText :value="parseFloat(item.total)"></CurrencyText>
</template>
</v-data-table-virtual>
</v-tabs-window-item>
<SummaryPurchaseModal :id="selectedPurchaseId" ref="summaryModal" />
</v-card-text>
</v-card>
</v-tabs-window>
</v-container>
</template>
<script>
import { inject } from 'vue';
import CurrencyText from './CurrencyText.vue';
import SummaryPurchaseModal from './SummaryPurchaseModal.vue';
export default {
name: 'ReconciliationJar',
props: {
msg: String,
},
components: {
SummaryPurchaseModal,
},
data () {
return {
api: inject('api'),
valid: null,
selectedPurchaseId: null,
selectedTab: 'CASH',
reconciliation: {
date_time: '',
total_cash_purchases: 0,
cash_taken: 0,
cash_discrepancy: 0,
other_totals: {
},
cash_purchases: [],
},
summary: {
headers: [
{title: 'Id', value: 'id'},
{title: 'Fecha', value: 'date'},
{title: 'Cliente', value: 'customer.name'},
{title: 'Total', value: 'total'},
],
purchases: {},
},
rules: {
required: value => !!value || 'Requerido.',
integer: value => !!value || value === 0 || 'Requerido.',
},
};
},
mounted() {
this.fetchPurchases();
this.reconciliation.date_time = this.getCurrentDate();
},
watch: {
'reconciliation.cash_taken'() {
this.updateDiscrepancy();
},
},
methods: {
totalByMethod(method) {
if (method in this.summary.purchases) {
return this.summary.purchases[method].reduce((a, b) => a + parseFloat(b.total), 0);
}
return 0;
},
idsBymethod(method) {
if (method in this.summary.purchases) {
return this.summary.purchases[method].map(purchase => purchase.id)
}
return [];
},
processOtherMethods() {
for (const method of Object.keys(this.summary.purchases)) {
if (method !== 'CASH') {
this.reconciliation.other_totals[method] = {
total: this.totalByMethod(method),
purchases: this.idsBymethod(method),
}
}
}
},
updateDiscrepancy() {
this.reconciliation.cash_discrepancy = (this.reconciliation.total_cash_purchases || 0 ) - (this.reconciliation.cash_taken || 0);
},
getCurrentDate() {
const today = new Date();
const gmtOffSet = -5;
const localDate = new Date(today.getTime() + (gmtOffSet * 60 * 60 * 1000));
// Formatear la fecha y hora en el formato YYYY-MM-DDTHH:MM
const formattedDate = localDate.toISOString().slice(0,16);
return formattedDate;
},
openSummaryModal(id) {
this.selectedPurchaseId = id;
this.$refs.summaryModal.dialog = true;
},
fetchPurchases() {
this.api.getPurchasesForReconciliation()
.then(data => {
this.summary.purchases = data;
this.reconciliation.cash_purchases = this.idsBymethod('CASH');
this.reconciliation.total_cash_purchases = this.totalByMethod('CASH');
this.processOtherMethods();
})
.catch(error => {
console.error(error);
});
},
async submit() {
this.$refs.taker.validate();
if (this.valid) {
this.api.createReconciliationJar(this.reconciliation)
.then(data => {
console.log('Cuadre enviado:', data);
this.$router.push({path: "/"});
})
.catch(error => console.error('Error:', error));
}
}
},
}
</script>

View File

@ -1,64 +0,0 @@
<template>
<v-container>
<v-toolbar>
<v-toolbar-title> Cuadres del Tarro </v-toolbar-title>
</v-toolbar>
<v-card>
<v-card-text>
<v-data-table-server
v-model:items-per-page="itemsPerPage"
:headers="headers"
:items="serverItems"
:items-length="totalItems"
:loading="loading"
:search="search"
@update:options="loadItems"
>
<template v-slot:item.id="{ item }">
<v-btn @click="openSummaryModal(item.id)">{{ item.id }}</v-btn>
</template>
</v-data-table-server>
<SummaryReconciliationModal :id="selectedReconciliationId" ref="summaryModal" />
</v-card-text>
</v-card>
</v-container>
</template>
<script>
export default {
data() {
return {
api: inject('api'),
selectedReconciliationId: null,
itemsPerPage: 10,
headers: [
{ title: 'Acciones', key: 'id'},
{ title: 'Fecha', key: 'date_time'},
{ title: 'Reconciliador', key: 'reconcilier'},
{ title: 'Total Compras Efectivo', key: 'total_cash_purchases'},
{ title: 'Recogido', key: 'cash_taken'},
{ title: 'Descuadre', key: 'cash_discrepancy'},
],
search: '',
serverItems: [],
loading: true,
totalItems: 0,
}
},
methods: {
loadItems ({page, itemsPerPage}) {
this.loading = true;
this.api.getListReconcliations(page, itemsPerPage)
.then(data => {
this.serverItems = data['results'];
this.totalItems = data['count'];
this.loading = false;
})
.catch(error => console.log('Error:', error));
},
openSummaryModal(id) {
this.selectedReconciliationId = id.toString();
this.$refs.summaryModal.dialog = true;
},
},
}
</script>

View File

@ -1,140 +0,0 @@
<template>
<v-container>
<v-toolbar>
<v-toolbar-title> Cuadre de Tarro: {{ id }}</v-toolbar-title>
</v-toolbar>
<v-card>
<v-card-text>
<v-text-field
v-model="reconciliation.date_time"
label="Fecha"
required
readonly
></v-text-field>
<v-text-field
v-model="reconciliation.reconcilier"
label="Cajero"
required
readonly
></v-text-field>
<v-text-field
v-model="reconciliation.total_cash_purchases"
label="Total Ventas en efectivo"
prefix="$"
type="number"
readonly
></v-text-field>
<v-text-field
v-model="reconciliation.cash_taken"
label="Dinero Recogido"
prefix="$"
type="number"
></v-text-field>
<v-text-field
v-model="reconciliation.cash_discrepancy"
label="Descuadre"
prefix="$"
type="number"
></v-text-field>
<v-tabs v-model="tab">
<v-tab
v-for="(elements, paymentMethod) in purchases"
:key="paymentMethod"
>
{{ paymentMethod }}&nbsp; <CurrencyText :value="elements.total"</CurrencyText>
</v-tab>
</v-tabs>
<v-tabs-window v-model="tab">
<v-tabs-window-item
v-for="(elements, paymentMethod) in purchases"
:key="paymentMethod"
>
<v-table>
<thead>
<tr>
<th>Id</th>
<th>Fecha</th>
<th>Cliente</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr v-for="purchase in elements.purchases" :key="purchase.id">
<td><v-btn @click="openSummaryModal(purchase.id)">{{ purchase.id }}</v-btn></td>
<td>{{ purchase.date }}</td>
<td>{{ purchase.customer }}</td>
<td><CurrencyText :value="purchase.total"</CurrencyText></td>
</tr>
</tbody>
</v-table>
</v-tabs-window-item>
</v-tabs-window>
<SummaryPurchaseModal :id="selectedPurchaseId" ref="summaryModal" />
</v-card-text>
</v-card>
</v-container>
</template>
<script>
import { inject } from 'vue';
export default {
name: 'ReconciliationJar View',
props: {
msg: String,
id: {
type: String,
required: true
}
},
data () {
return {
tab: '0',
selectedPurchaseId: null,
api: inject('api'),
valid: null,
reconciliation: {
},
purchases: {},
};
},
created() {
if (this.id) {
this.fetchReconciliation(this.id);
} else {
console.error('No se proporcionó ID');
}
},
methods: {
fetchReconciliation(reconciliationId) {
this.api.getReconciliation(reconciliationId)
.then(data => {
this.reconciliation = data;
this.groupPurchases();
})
.catch(error => console.error(error));
},
groupPurchases() {
if (this.reconciliation.Sales) {
this.purchases = this.reconciliation.Sales.reduce((grouped, sale) => {
const paymentMethod = sale.payment_method;
if (!grouped[paymentMethod]) {
grouped[paymentMethod] = {
purchases: [],
total: 0,
};
}
grouped[paymentMethod].purchases.push(sale);
grouped[paymentMethod].total += sale.total;
return grouped;
}, {});
}
},
openSummaryModal(id) {
this.selectedPurchaseId = id;
this.$refs.summaryModal.dialog = true;
},
},
}
</script>

View File

@ -1,5 +0,0 @@
<template>
<strong class="text-red-darken-4">
<slot></slot>
</strong>
</template>

View File

@ -1,107 +0,0 @@
<template>
<v-container>
<v-container v-show="!id">
<v-toolbar>
<v-toolbar-title> No se indicó Id de la compra</v-toolbar-title>
</v-toolbar>
</v-container>
<v-container v-show="id">
<v-toolbar>
<v-toolbar-title> Resumen de la compra {{ id }}</v-toolbar-title>
</v-toolbar>
<v-list>
<v-list-item>
<v-list-item-title>Fecha:</v-list-item-title>
<v-list-item-subtitle>{{ purchase.date }}</v-list-item-subtitle>
</v-list-item>
<v-list-item>
<v-list-item-title>Cliente:</v-list-item-title>
<v-list-item-subtitle v-if="purchase.customer">{{ purchase.customer.name }}</v-list-item-subtitle>
</v-list-item>
<v-list-item>
<v-list-item-title>Pagado en:</v-list-item-title>
<v-list-item-subtitle v-if="purchase.payment_method">{{ purchase.payment_method }}</v-list-item-subtitle>
</v-list-item>
<v-list-item>
<v-list-item-title>Total:</v-list-item-title>
<v-list-item-subtitle v-if="purchase.lines">{{ currencyFormat(calculateTotal(purchase.lines)) }}</v-list-item-subtitle>
</v-list-item>
</v-list>
<v-data-table-virtual
:headers="headers"
:items="purchase.lines"
>
<template v-slot:item.unit_price="{ item }">
{{ currencyFormat(item.unit_price) }}
</template>
<template v-slot:item.subtotal="{ item }">
{{ currencyFormat(calculateSubtotal(item.unit_price, item.quantity)) }}
</template>
</v-data-table-virtual>
<div class="text-center">
<v-btn :to="{ path: 'comprar' }" color="green">Ir a Comprar</v-btn>
</div>
</v-container>
</v-container>
</template>
<script>
import { inject } from 'vue';
export default {
name: 'SummaryPurchase',
props: {
msg: String,
id: Number
},
data () {
return {
api: inject('api'),
purchase: {},
headers: [
{ title: 'Producto', value: 'product.name' },
{ title: 'Precio', value: 'unit_price' },
{ title: 'Cantidad', value: 'quantity' },
{ title: 'Subtotal', value: 'subtotal' },
],
};
},
created() {
if (this.id) {
this.fetchPurchase(this.id);
} else {
console.error('No se proporcionó un ID de compra.');
}
},
methods: {
fetchPurchase(purchaseId) {
this.api.getSummaryPurchase(purchaseId)
.then(data => {
this.purchase = data;
})
.catch(error => {
console.error(error);
});
},
currencyFormat(value) {
return new Intl.NumberFormat('es-CO', { style: 'currency', currency: 'COP' }).format(value);
},
calculateSubtotal(price, quantity) {
price = parseFloat(price || 0);
quantity = parseFloat(quantity || 0);
return price * quantity;
},
calculateTotal(lines) {
let total = 0;
lines.forEach(line => {
total += this.calculateSubtotal(line.unit_price, line.quantity);
});
return total;
}
},
};
</script>
<style>
</style>

View File

@ -1,30 +0,0 @@
<template>
<v-dialog v-model="dialog" max-width="400">
<v-card>
<v-card-text>
<SummaryPurchase :id="id"/>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="dialog = false">Cerrar</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: 'SummaryPurchase Modal',
props: {
id: {
type: Number,
required: true,
}
},
data() {
return {
dialog: false,
}
},
}
</script>

View File

@ -1,30 +0,0 @@
<template>
<v-dialog v-model="dialog" max-width="400">
<v-card>
<v-card-text>
<SummaryPurchase :id="id"/>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="dialog = false">Cerrar</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: 'SummaryPurchase Modal',
props: {
id: {
type: Number,
required: true,
}
},
data() {
return {
dialog: false,
}
},
}
</script>

View File

@ -1,31 +0,0 @@
<template>
<v-dialog v-model="dialog" max-width="400">
resumen
<v-card>
<v-card-text>
<ReconciliationJarView :id="id"/>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="dialog = false">Cerrar</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: 'Summary Reconciliation Modal',
props: {
id: {
type: String,
required: true,
}
},
data() {
return {
dialog: false,
}
},
}
</script>

View File

@ -1,34 +0,0 @@
<template>
<v-container >
<v-responsive>
<v-toolbar>
<v-toolbar-title>Don Confiao te atiende</v-toolbar-title>
</v-toolbar>
<v-card>
<v-card-title>Hacer parte de la tienda la ilusión</v-card-title>
<v-card-text>
Recuerda que participando de esta tienda le apuestas a la economía solidaria, al mercado justo, a la alimentación sana, al campesinado colombiano y a un mundo mejor.
</v-card-text>
</v-card>
<v-card>
<v-card-title>En desarrollo</v-card-title>
<v-card-text>
Don confiao apenas esta entendiendo como funciona esta tienda y por ahora <ResaltedText>solo puede atender las compras de contado</ResaltedText>, ya sea en efectivo o consignación.
<v-alert type="warning">
Si no vas a pagar tu compra recuerda que debes hacerlo en la planilla manual</v-alert>
</v-card-text>
</v-card>
<v-card>
<v-card-title>A comprar</v-card-title>
<v-card-text>
El siguiente botón te permitirá registrar tu compra. Cuando finalices te pedimos que ingrese el número de la compra, la fecha y el valor en la planilla física.
<div class="text-center">
<v-btn :to="{ path: 'comprar' }" color="green">Ir a Comprar</v-btn>
</div>
</v-card-text>
</v-card>
</v-responsive>
</v-container>
</template>

View File

@ -1,5 +0,0 @@
# Layouts
Layouts are reusable components that wrap around pages. They are used to provide a consistent look and feel across multiple pages.
Full documentation for this feature can be found in the Official [vite-plugin-vue-layouts](https://github.com/JohnCampionJr/vite-plugin-vue-layouts) repository.

View File

@ -1,13 +0,0 @@
<template>
<v-app>
<v-main>
<router-view />
</v-main>
<AppFooter />
</v-app>
</template>
<script setup>
//
</script>

View File

@ -1,26 +0,0 @@
/**
* main.js
*
* Bootstraps Vuetify and other plugins then mounts the App`
*/
// Plugins
import { registerPlugins } from '@/plugins'
// Components
import App from './App.vue'
import ApiImplementation from './services/api-implementation';
// Composables
import { createApp } from 'vue'
process.env.API_IMPLEMENTATION = 'django';
let apiImplementation = new ApiImplementation();
const api = apiImplementation.getApi();
const app = createApp(App);
app.provide('api', api);
registerPlugins(app)
app.mount('#app')

View File

@ -1,5 +0,0 @@
# Pages
Vue components created in this folder will automatically be converted to navigatable routes.
Full documentation for this feature can be found in the Official [unplugin-vue-router](https://github.com/posva/unplugin-vue-router) repository.

View File

@ -1,7 +0,0 @@
<template>
<Purchase />
</template>
<script setup>
//
</script>

View File

@ -1,20 +0,0 @@
<template>
<div>
<CodeDialog @code-verified="(verified) => showComponent = verified"/>
</div>
<ReconciliationJar v-if="showComponent" />
</template>
<script >
import CodeDialog from '../components/CodeDialog.vue'
export default {
data() {
return {
showComponent: false,
}
},
components: { CodeDialog },
methods: {},
}
</script>

View File

@ -1,20 +0,0 @@
<template>
<div>
<CodeDialog @code-verified="(verified) => showComponent = verified" />
</div>
<ReconciliationJarIndex v-if="showComponent" />
</template>
<script>
import CodeDialog from '../components/CodeDialog.vue'
export default {
data() {
return {
showComponent: false,
}
},
components: { CodeDialog },
methods: {},
}
</script>

View File

@ -1,6 +0,0 @@
<template>
<Wellcome />
</template>
<script setup>
</script>

View File

@ -1,7 +0,0 @@
<template>
<SummaryPurchase :id="$route.query.id"/>
</template>
<script setup>
//
</script>

View File

@ -1,3 +0,0 @@
# Plugins
Plugins are a way to extend the functionality of your Vue application. Use this folder for registering plugins that you want to use globally.

View File

@ -1,17 +0,0 @@
/**
* plugins/index.js
*
* Automatically included in `./src/main.js`
*/
// Plugins
import vuetify from './vuetify'
import pinia from '@/stores'
import router from '@/router'
export function registerPlugins (app) {
app
.use(vuetify)
.use(router)
.use(pinia)
}

View File

@ -1,19 +0,0 @@
/**
* plugins/vuetify.js
*
* Framework documentation: https://vuetifyjs.com`
*/
// Styles
import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/styles'
// Composables
import { createVuetify } from 'vuetify'
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
export default createVuetify({
theme: {
defaultTheme: 'light',
},
})

View File

@ -1,37 +0,0 @@
/**
* router/index.ts
*
* Automatic routes for `./src/pages/*.vue`
*/
// Composables
import { createRouter, createWebHistory } from 'vue-router/auto'
import { setupLayouts } from 'virtual:generated-layouts'
import { routes } from 'vue-router/auto-routes'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: setupLayouts(routes),
})
// Workaround for https://github.com/vitejs/vite/issues/11804
router.onError((err, to) => {
if (err?.message?.includes?.('Failed to fetch dynamically imported module')) {
if (!localStorage.getItem('vuetify:dynamic-reload')) {
console.log('Reloading page to fix dynamic import error')
localStorage.setItem('vuetify:dynamic-reload', 'true')
location.assign(to.fullPath)
} else {
console.error('Dynamic import error, reloading page did not fix it', err)
}
} else {
console.error(err)
}
})
router.isReady().then(() => {
localStorage.removeItem('vuetify:dynamic-reload')
})
export default router

View File

@ -1,21 +0,0 @@
import DjangoApi from './django-api';
import Api from './api';
class ApiImplementation {
constructor() {
const implementation = process.env.API_IMPLEMENTATION;
let apiImplementation;
if (implementation === 'django') {
apiImplementation = new DjangoApi();
} else {
throw new Error("API implementation don't configured");
}
this.api = new Api(apiImplementation);
}
getApi() {
return this.api;
}
}
export default ApiImplementation;

View File

@ -1,51 +0,0 @@
class Api {
constructor (apiImplementation) {
this.apiImplementation = apiImplementation;
}
getCustomers() {
return this.apiImplementation.getCustomers();
}
getProducts() {
return this.apiImplementation.getProducts();
}
getPaymentMethods() {
return this.apiImplementation.getPaymentMethods();
}
getSummaryPurchase(purchaseId) {
return this.apiImplementation.getSummaryPurchase(purchaseId);
}
getPurchasesForReconciliation() {
return this.apiImplementation.getPurchasesForReconciliation();
}
getListReconcliations(page=1, itemsPerPage=10) {
return this.apiImplementation.getListReconcliations(page, itemsPerPage);
}
getReconciliation(reconciliationId) {
return this.apiImplementation.getReconciliation(reconciliationId);
}
isValidAdminCode(code) {
return this.apiImplementation.isValidAdminCode(code);
}
createPurchase(purchase) {
return this.apiImplementation.createPurchase(purchase);
}
createReconciliationJar(reconciliation) {
return this.apiImplementation.createReconciliationJar(reconciliation);
}
createCustomer(customer) {
return this.apiImplementation.createCustomer(customer);
}
}
export default Api;

View File

@ -1,97 +0,0 @@
class DjangoApi {
getCustomers() {
const url = '/don_confiao/api/customers/';
return this.getRequest(url);
}
getProducts() {
const url = '/don_confiao/api/products/';
return this.getRequest(url);
}
getPaymentMethods() {
const url = '/don_confiao/payment_methods/all/select_format';
return this.getRequest(url);
}
getSummaryPurchase(purchaseId) {
const url = `/don_confiao/resumen_compra_json/${purchaseId}`;
return this.getRequest(url);
}
getPurchasesForReconciliation() {
const url = '/don_confiao/purchases/for_reconciliation';
return this.getRequest(url);
}
getListReconcliations(page, itemsPerPage) {
const url = `/don_confiao/api/reconciliate_jar/?page=${page}&page_size=${itemsPerPage}`;
return this.getRequest(url);
}
getReconciliation(reconciliationId) {
const url = `/don_confiao/api/reconciliate_jar/${reconciliationId}/`;
return this.getRequest(url);
}
isValidAdminCode(code) {
const url = `/don_confiao/api/admin_code/validate/${code}`
return this.getRequest(url)
}
createPurchase(purchase) {
const url = '/don_confiao/api/sales/';
return this.postRequest(url, purchase);
}
createReconciliationJar(reconciliation) {
const url = '/don_confiao/reconciliate_jar';
return this.postRequest(url, reconciliation);
}
createCustomer(customer) {
const url = '/don_confiao/api/customers/';
return this.postRequest(url, customer);
}
getRequest(url) {
return new Promise ((resolve, reject) => {
fetch(url)
.then(response => response.json())
.then(data => {
resolve(data);
})
.catch(error => {
reject(error);
});
});
}
postRequest(url, content) {
return new Promise((resolve, reject) => {
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(content)
})
.then(response => {
if (!response.ok) {
reject(new Error(`Error ${response.status}: ${response.statusText}`));
} else {
response.json().then(data => {
if (!data) {
reject(new Error('La respuesta no es un JSON válido'));
} else {
resolve(data);
}
});
}
})
.catch(error => reject(error));
});
}
}
export default DjangoApi;

View File

@ -1,5 +0,0 @@
# Store
Pinia stores are used to store reactive state and expose actions to mutate it.
Full documentation for this feature can be found in the Official [Pinia](https://pinia.esm.dev/) repository.

View File

@ -1,8 +0,0 @@
// Utilities
import { defineStore } from 'pinia'
export const useAppStore = defineStore('app', {
state: () => ({
//
}),
})

View File

@ -1,4 +0,0 @@
// Utilities
import { createPinia } from 'pinia'
export default createPinia()

View File

@ -1,3 +0,0 @@
# Styles
This directory is for configuring the styles of the application.

View File

@ -1,10 +0,0 @@
/**
* src/styles/settings.scss
*
* Configures SASS variables and Vuetify overwrites
*/
// https://vuetifyjs.com/features/sass-variables/`
// @use 'vuetify/settings' with (
// $color-pack: false
// );

View File

@ -1,71 +0,0 @@
// Plugins
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import Fonts from 'unplugin-fonts/vite'
import Layouts from 'vite-plugin-vue-layouts'
import Vue from '@vitejs/plugin-vue'
import VueRouter from 'unplugin-vue-router/vite'
import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
// Utilities
import { defineConfig } from 'vite'
import { fileURLToPath, URL } from 'node:url'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
VueRouter(),
Layouts(),
Vue({
template: { transformAssetUrls }
}),
// https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme
Vuetify({
autoImport: true,
styles: {
configFile: 'src/styles/settings.scss',
},
}),
Components(),
Fonts({
google: {
families: [{
name: 'Roboto',
styles: 'wght@100;300;400;500;700;900',
}],
},
}),
AutoImport({
imports: [
'vue',
'vue-router',
],
eslintrc: {
enabled: true,
},
vueTemplate: true,
}),
],
define: { 'process.env': {} },
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
extensions: [
'.js',
'.json',
'.jsx',
'.mjs',
'.ts',
'.tsx',
'.vue',
],
},
server: {
port: 3000,
},
build: {
outDir: '../../static/frontend/',
},
base: '/frontend/',
})

View File

@ -1,9 +0,0 @@
from node
WORKDIR /app/
COPY tienda_ilusion/don_confiao/frontend/don-confiao/package.json /app/
RUN npm install
CMD ["npm", "run", "dev"]