Compare commits

..

205 commits

Author SHA1 Message Date
ale
c58824ff52 Cambio de 'pagado' a 'aprobado' 2025-06-18 18:57:20 -03:00
ale
fcb2d2c5bc No se muestra control de devoluciones en pedido aprobado 2025-06-18 18:56:56 -03:00
ale
686fcf3bd5 Muestra producto-cantidad cuando aprobado, quitado mensaje de 'n en chismosa' 2025-06-18 18:43:56 -03:00
ale
c064e80116 Quitado argumento 'undefined' para crear pedidos 2025-06-16 18:21:14 -03:00
ale
f26234c3bf Tags de componentes simplificadas 2025-06-16 18:20:58 -03:00
ale
0173d7bd36 Info tags cerradas en pedido select si no fueron interactuadas 2025-06-16 18:04:28 -03:00
ale
a16487cc3f Logica de infoTags movida a modulo ui 2025-06-16 17:05:18 -03:00
Rodrigo
d9747c9280 Arreglo migas 2025-06-15 16:59:18 -03:00
ale
dbaff75734 Agregada infoTags y sacados datos de canasta de la nav bar 2025-06-15 15:27:54 -03:00
ale
a96adedb82 Formato 2025-06-15 15:27:32 -03:00
ale
5b8f9cb694 Arreglo mensaje password comisiones 2025-06-15 13:13:24 -03:00
ale
214292bc8f Arreglo: precio total en vez de cantidad en chismosa 2025-06-15 13:04:53 -03:00
ale
17148d73f8 Agregada fecha a nombre de archivos exportados (csv y pdf) 2025-06-14 15:55:56 -03:00
ale
a38bceab82 Usando storage_path en vez de resource_path 2025-06-14 15:35:41 -03:00
ale
ff96c104a2 Agregados datos de canasta actual a nav-bar 2025-06-14 13:57:52 -03:00
ale
854278bc99 Agregado método, controller y ruta para obtener datos de canasta actual 2025-06-14 13:36:24 -03:00
ale
197b087097 Todo lo que era 'compras' cambiado a 'comisiones' 2025-06-14 12:58:21 -03:00
ale
5c51653037 Nombres para componentes 2025-06-14 12:58:01 -03:00
ale
a5be67df2e Borrados middleware viejos 2025-06-14 12:57:22 -03:00
ale
d8c8865d13 Merge branch 'master' into funcion/refactor-general 2025-06-14 12:33:01 -03:00
ale
9a310484d6 Cambio de ancho de input de usuario 2025-06-14 12:32:11 -03:00
ale
80b5dc60ac Agregado LoginDropdown a pantalla de login 2025-06-14 12:11:05 -03:00
ale
517e5203c9 Agregado LoginDropdown.vue con opciones de login según urlRol 2025-06-14 12:10:41 -03:00
ale
dbdb97a78e Eliminados métodos no usados y agregado método para opciones de login segun urlRol 2025-06-14 12:10:20 -03:00
ale
79bdb04ce2 Espacios 2025-06-14 12:09:54 -03:00
ale
3503be8e56 Eliminados componentes no usados 2025-06-14 12:09:38 -03:00
ale
faa947e6a7 Eliminadas admin_login y compras_login, usando login genérico 2025-06-13 19:33:17 -03:00
ale
fdd3232345 Usando componente genérico de login en las tres views de auth 2025-06-13 19:28:15 -03:00
ale
b72fc57b8d Componente genérico para login 2025-06-13 19:28:02 -03:00
ale
3cb27c5c30 Cambios de estilo para compras 2025-06-13 19:27:47 -03:00
ale
b4458862f4 Renombrado 2025-06-13 19:27:15 -03:00
ale
7a4aa6d7a0 Agregado height:100% a form 2025-06-13 19:26:59 -03:00
ale
0637b7a208 Agregado getter para nombre 2025-06-13 19:12:36 -03:00
ale
af8879eadd Agregados métodos para obtener textos y estilos de las diferentes pantallas de login 2025-06-13 18:33:55 -03:00
ale
9e93b914ac Agregada altura 100% a html, body y #root 2025-06-13 18:33:34 -03:00
ale
1b920093c4 Arreglo alineacion 2025-06-10 19:19:15 -03:00
ale
210c91f3a8 Mejoras estéticas y cambios cuando el pedido está aprobado 2025-06-10 19:09:21 -03:00
ale
2ee7fca584 Cambio por null check 2025-06-10 18:06:43 -03:00
ale
258bccf59b Reseteando ui 2025-05-29 16:51:08 -03:00
ale
ca42526a62 Hover dedito 2025-05-29 16:44:25 -03:00
ale
f484bfff79 Mejoras de estilo de chismosa para tablet y celular 2025-05-29 15:26:55 -03:00
ale
9384d09ff6 Arreglado scroll horizontal en celular y tablet 2025-05-29 15:24:39 -03:00
ale
f460cdd6ce Mejoras de estilo 2025-05-27 23:54:01 -03:00
ale
4b4a284914 Cambio en layout de productos 2025-05-27 23:38:37 -03:00
ale
a641247748 Cambio dinámico de tamaño y alineación 2025-05-27 23:32:27 -03:00
ale
82a518fa1d Mejoras de estilo 2025-05-27 22:28:40 -03:00
ale
deaa65d857 Ya no usa lodash 2025-05-27 20:42:45 -03:00
ale
4af9e53a50 Quitadas dependencias inncesarias y eliminadio bootstrap.js que no se usaba para nada 2025-05-27 20:42:24 -03:00
ale
8d64d85b8b Impidiendo cantidades invalidas 2025-05-27 20:38:12 -03:00
ale
2c720faca7 Cambiado margen derecho 2025-05-27 19:27:19 -03:00
ale
85fa9f1e9b formato 2025-05-27 19:21:43 -03:00
ale
c8d1969352 Migas simplificadas por renderizarse solo despues de que el pedido se defina 2025-05-27 19:21:20 -03:00
ale
1eb77be1d0 Columna definida afuera 2025-05-27 19:19:53 -03:00
ale
3949cf3400 Quitada chismosa 2025-05-27 19:19:33 -03:00
ale
973d099bf1 Layout de columnas para que el cartel de pedido aprobado no desplace la chismosa 2025-05-27 19:19:23 -03:00
ale
802d4d0c0b No se stackean las busquedas 2025-05-27 18:29:44 -03:00
ale
134ed0cd22 Agregadas acciones con argumentos a migas + accion para miga de categoria 2025-05-27 18:21:19 -03:00
ale
2bfcf59f3e Eliminada store de barrio, logica de pedidos existentes pasada a pedido select y datos de grupo de compra pasados a store de pedido 2025-05-27 18:14:00 -03:00
ale
4e197204a9 Agregado nombre 2025-05-27 15:49:24 -03:00
ale
2075bcab0f Usando titulo genérico para login 2025-05-27 15:25:56 -03:00
ale
c0d8392f6e Agregado metodo para titulo genérico de login 2025-05-27 15:25:47 -03:00
ale
ef9a296f5c Simplificado v-if 2025-05-26 18:50:18 -03:00
ale
bc55b4c34f Mejores imports 2025-05-26 18:05:48 -03:00
ale
e779111856 Formato 2025-05-26 18:05:38 -03:00
ale
d6990f8c88 Eliminada tabla bonos 2025-05-26 18:05:17 -03:00
ale
8eb385c67e Formato 2025-05-26 18:00:01 -03:00
ale
2970982c77 SubpedidoSelect mergeado con PedidoSelectSection en PedidoSelect y Pedido renombrado a Canasta 2025-05-26 17:59:57 -03:00
ale
9e63c83126 Quitados comentarios 2025-05-26 17:58:25 -03:00
ale
439f69a30c Agregado total sin devoluciones a resource de grupo de compra 2025-05-26 17:25:35 -03:00
ale
d0ce8e8e23 Navmigas movida a pedidos-body 2025-05-26 17:03:49 -03:00
ale
3b858f5b2b Quitada navbar de app blade, logout form escondida 2025-05-26 17:02:34 -03:00
ale
0512ea9ab2 Agregada miga para volver al select de pedidos 2025-05-26 16:46:39 -03:00
ale
8488d9d6c5 Movida logica de obtener sesion a subpedido select + simplificado elegir pedido + mejores responses de session controller 2025-05-26 16:37:19 -03:00
ale
354045c5df Borrada linea no usada 2025-05-24 14:47:19 -03:00
ale
ae1f8673e7 Metodo para resetear pedido 2025-05-24 14:46:46 -03:00
ale
4f74bf38f9 Metodo y ruta para borrar sesion 2025-05-24 14:46:15 -03:00
ale
8a2539f207 Quitada import no usado 2025-05-24 14:18:05 -03:00
ale
04673b1754 Quitada ruta y metodo no usados 2025-05-24 13:52:19 -03:00
ale
88af33d998 Agregado metodo y ruta para obtener categorias 2025-05-24 13:47:56 -03:00
ale
23af6ccd61 Borrada ruta no usada 2025-05-23 18:30:54 -03:00
ale
777f442118 Metodo toggleDevoluciones en controller 2025-05-23 18:28:09 -03:00
ale
7cae00e613 Quitado import no usado 2025-05-23 18:27:51 -03:00
ale
5a61ca46c5 Quitado password de grupo de compra 2025-05-23 18:27:33 -03:00
ale
e379825fd9 Usando ruta nueva 2025-05-23 18:14:51 -03:00
ale
6b2da42160 v-if simplificado 2025-05-23 18:14:11 -03:00
ale
48cf57a6d8 Agregada ruta y metodo para obtener barrios de una region 2025-05-23 17:56:46 -03:00
ale
aa545ff82a Eliminadas views no usadas 2025-05-23 17:40:05 -03:00
ale
b29d63ed4d Quitado import no usado 2025-05-23 17:32:38 -03:00
ale
1e91f443a7 Rutas de sesion movidas a /pedido 2025-05-23 17:16:19 -03:00
ale
6782b7f675 Limpiadas rutas no usadas 2025-05-23 17:12:48 -03:00
ale
85b3f1dd0f Agregados fillable faltantes + productos no barriales en metodos para armar planillas 2025-05-23 17:05:13 -03:00
ale
a006fc15fa Excluyendo productos barriales de productosPedidos 2025-05-23 17:04:29 -03:00
ale
37fd0fb4d3 Fila nullable 2025-05-23 16:53:54 -03:00
ale
d794dbd2b0 Usando vuex 2025-05-23 16:53:37 -03:00
ale
7140796ccd Usando sesion para recordar el pedido 2025-05-23 02:33:21 -03:00
ale
5b9908e0b5 Agregados toast 2025-05-23 02:18:12 -03:00
ale
f837b7f066 Tirando error cuando se intenta modificar un pedido aprobado 2025-05-23 02:13:09 -03:00
ale
0dba210a6a Funciones para toast y mostrar errores 2025-05-23 02:12:55 -03:00
ale
571b02382e app limpiada 2025-05-23 01:44:39 -03:00
ale
2f071e631d Pseudo routing con migas y dispatch en vez de href 2025-05-23 01:41:16 -03:00
ale
1e830e3cfd Logica de migas movida a ui.js 2025-05-23 01:27:34 -03:00
ale
0381bb0567 Cartel aprobado movido a pedidos body 2025-05-23 01:12:12 -03:00
ale
adabd09ea7 Cambio id 2025-05-23 01:11:31 -03:00
ale
a9ca04811e Usando vuex 2025-05-23 01:02:11 -03:00
ale
5458fae6d9 Renombrado 2025-05-23 01:02:01 -03:00
ale
5023032ac2 Usando vuex para el modal de devoluciones 2025-05-23 00:58:02 -03:00
ale
fb0e13089f Agregado devoluciones-modal 2025-05-23 00:56:27 -03:00
ale
21aa36e3d1 Cambios de nombres, y movida logica de chismosa a store de vuex 2025-05-23 00:37:12 -03:00
ale
f81141d18b Quitado codigo no usado 2025-05-23 00:36:22 -03:00
ale
8f0d715f8c Usando vuex 2025-05-23 00:01:55 -03:00
ale
45dcf643bf Formato 2025-05-22 23:07:07 -03:00
ale
5468b79562 Agregados getters para datos de productos 2025-05-22 23:06:51 -03:00
ale
08a731673b Quitado codigo no usado 2025-05-22 22:12:42 -03:00
ale
94e384c83c Crear pedido setea state, quitado todo lo relativo a sesion, y quitado codigo comentado 2025-05-22 21:46:15 -03:00
ale
a594e8a049 Arreglado parametro 2025-05-22 21:45:34 -03:00
ale
fd055cd7c6 Devolviendo resource tras crear pedido 2025-05-22 21:45:22 -03:00
ale
d4df72afe2 Titulo simplificado 2025-05-22 21:26:08 -03:00
ale
442d35eac8 Agregado main.blade.php con respectiva ruta y web usa main 2025-05-22 21:26:01 -03:00
ale
154e21fae6 Agregados componentes para ordenar el flujo de la aplicación + usandolo en la view + cambios en la store pedido por evitar redireccion 2025-05-22 21:08:06 -03:00
ale
1a8f9eda18 Cambio en nombre de getter 2025-05-22 21:06:14 -03:00
ale
128dd05b9a Agregado metodo, ruta, logica de vuex para obtener rol del usuario logeado 2025-05-22 21:04:17 -03:00
ale
181fbf924f Agregado nombre 2025-05-22 20:25:00 -03:00
ale
8d1eb03ffc Cambio formato y mostrando solo para pedidos 2025-05-22 00:26:08 -03:00
ale
baeff66aaf usando vuex para el resto de los datos 2025-05-22 00:12:34 -03:00
ale
1709468f1f total, productos, y devoluciones_habilitadas tomadas de vuex 2025-05-22 00:05:07 -03:00
ale
b46b56159b formato 2025-05-22 00:01:36 -03:00
ale
b8390c4ac6 Usando pedido de vuex 2025-05-21 23:41:23 -03:00
ale
36af26a647 Agregada logica de sesion 2025-05-21 22:40:53 -03:00
ale
cdf5663b16 Redirección movida a vuex 2025-05-21 22:40:23 -03:00
ale
8fbfa75144 Esperando a que pedido esté definido 2025-05-21 22:33:02 -03:00
ale
5a0cf73218 Crear pedido movido a vuex 2025-05-21 21:49:04 -03:00
ale
44bd8045b8 Cambios en obtener sesion 2025-05-21 21:31:46 -03:00
ale
8a32841f9d Agregadas rutas nuevas con session controller 2025-05-21 21:30:59 -03:00
ale
e0bb8127f4 Quitada ruta no usada 2025-05-21 21:30:38 -03:00
ale
1610acfbe7 formato 2025-05-21 21:30:10 -03:00
ale
fff9b5b65f Borradas cosas de admin ya no usadas 2025-05-21 20:48:06 -03:00
ale
d64659d653 template simplificada 2025-05-21 20:46:08 -03:00
ale
f92084d399 usando pedido definido 2025-05-21 20:45:50 -03:00
ale
f4560c0be5 arreglos en comparacion 2025-05-21 20:45:35 -03:00
ale
fe529c2242 esperar 2025-05-21 20:44:58 -03:00
ale
3509b9348f Transición a store 2025-05-21 19:28:41 -03:00
ale
83027245e8 Parámetros simplificados 2025-05-21 19:28:06 -03:00
ale
248b346f78 Usando store para pedidos 2025-05-21 19:10:47 -03:00
ale
191f01a398 Agregado 'pedidos' al state con metodos para traer y settear + separada logica de state en grupo de compra y pedidos 2025-05-21 19:10:00 -03:00
ale
408531bec3 Nombres de campos y simplificación de deshabilitar 2025-05-21 18:57:03 -03:00
ale
be24daf998 Usando store para grupo de compra id en lugar de prop 2025-05-21 18:50:32 -03:00
ale
020d554019 Formato 2025-05-21 18:44:22 -03:00
ale
dd30726d10 Formato 2025-05-21 18:41:10 -03:00
ale
59b6c29508 Solo se muestra en pedido 2025-05-21 18:40:01 -03:00
ale
2f7ee48930 Formato 2025-05-21 18:27:08 -03:00
ale
caebf1b53b Simplificado seleccionarCategoria 2025-05-21 18:24:31 -03:00
ale
b76ecf57dc Busqueda funcionando con productos.js 2025-05-21 18:23:35 -03:00
ale
0dc8f39f07 Cambio logica para usar modulo pedidos + cambios en migas 2025-05-21 18:19:39 -03:00
ale
e82dd59c34 Body espera a cargar productos y categorias 2025-05-21 18:19:22 -03:00
ale
0836584192 Quitados logs e if innecesario 2025-05-21 18:18:55 -03:00
ale
b490cd8b25 Trae todos los productos por defecto 2025-05-21 18:17:58 -03:00
ale
a512f8e162 Agregado modulo productos + cambios de nombre 2025-05-21 18:17:36 -03:00
ale
7bfbbd9309 Cambio de nombre de modulos + info de devoluciones habilitadas movida de pedido a barrio 2025-05-21 16:23:47 -03:00
ale
6c7e62bcb0 Agregadas acciones para crear y obtener pedido 2025-05-20 00:16:22 -03:00
ale
f4138510cd Agregado un enter 2025-05-20 00:15:47 -03:00
ale
ac9b0a00e8 Agregado modulo para info de barrio loggeado 2025-05-20 00:15:32 -03:00
ale
db5262d655 Simplifcado resource 2025-05-20 00:15:05 -03:00
ale
ff07551762 Simplifcado index 2025-05-19 23:35:27 -03:00
ale
40e466aa82 Primeras cosas de chismosa 2025-05-19 15:01:54 -03:00
ale
5b65bc5d9f Cambio en referencias 2025-05-19 15:01:30 -03:00
ale
4b3ab56565 Cambio a LoginForm 2025-05-19 15:00:57 -03:00
ale
eee3f53672 Agregado modulo a la store 2025-05-19 03:05:23 -03:00
ale
fe57e8c6b3 BarrioSelect y RegionSelect usan la store 2025-05-19 03:05:06 -03:00
ale
fe292802cd Clase login genérica para barrio y admin 2025-05-19 03:04:29 -03:00
ale
da5f0051c8 Agregado modulo de login para regiones y barrios 2025-05-19 03:02:51 -03:00
ale
47ce7e3bd8 Cambio de lógica para usar el modulo admin de la store 2025-05-19 00:17:50 -03:00
ale
e6770172ac Grupo de compra explícito en estado + metodos, acciones, y getters necesarios 2025-05-19 00:17:25 -03:00
ale
fbbb400892 /{gdc}/devoluciones devuelve no content 2025-05-19 00:13:28 -03:00
ale
85746f2d4d toggleAprobacion devuelve no content 2025-05-19 00:13:04 -03:00
ale
09bc8e7670 Cambio logica 2025-05-18 22:44:25 -03:00
ale
8a1334bae5 Cambio nombre 2025-05-18 21:46:24 -03:00
ale
63b51fd92c Reemplazo modulo sesión por modulo admin 2025-05-18 21:46:06 -03:00
ale
e2c716f576 Ruta 'sesion' cambiada por ruta para obtener grupo de compra de usuario logeado 2025-05-18 21:44:33 -03:00
ale
61e4c341f3 Quitado auth middleware de la ruta, ahora devuelve booleano con dato sobre estado de auth + actualizado para guardar el grupo de compra entero y no solo la id 2025-05-18 17:48:38 -03:00
ale
a485994a72 Agregada store de vuex para guardar información de la sesión tras login + ruta y controller + resource reducido de grupo de compra para pedidos 2025-05-18 17:34:14 -03:00
ale
6d3173cd1f Mejor referencia 2025-05-18 16:36:34 -03:00
ale
e047b0a23c Agregado vuex 2025-05-16 17:24:08 -03:00
ale
60a6f3e781 Quitados metodos no usados 2025-05-16 16:36:29 -03:00
ale
6bcb22ea00 Eliminados middlewares no usados + reorden 2025-05-16 16:17:21 -03:00
ale
1e7afc034e Quitadas dependencias no usadas 2025-05-16 16:11:49 -03:00
ale
ea8611771b Agregadas timestamps 2025-05-16 16:00:03 -03:00
ale
6e0238eef7 Quitados is_compras, is_admin de seeders 2025-05-16 15:12:37 -03:00
ale
ac6fc6bc0e Cambio referencia 2025-05-16 15:04:00 -03:00
ale
025b9239c3 Quitado redondeo 2025-05-16 15:03:51 -03:00
ale
8799446e57 Calculando total por producto 2025-05-16 15:03:40 -03:00
ale
44465c2783 Quitado total de pivot y de sync subpedido 2025-05-16 14:53:35 -03:00
ale
0b445ee1c5 Cambio en referencia 2025-05-16 14:53:23 -03:00
ale
8ad179c61c Quitadas import no usado 2025-05-16 14:53:02 -03:00
ale
f0f046d3cb Quitadas referencias a proveedor 2025-05-16 14:46:14 -03:00
ale
a9e9966c93 Refactor de login y home para que dependan del UserRole 2025-05-15 20:19:07 -03:00
ale
2e78d39f12 Reemplazo de logica de proovedor por es_solidario 2025-05-15 19:45:30 -03:00
ale
de1bae8620 Simplificado ProductoResource 2025-05-15 19:39:17 -03:00
ale
b330d991c6 Agregado y registrado middleware para usar UserRole 2025-05-15 19:38:47 -03:00
ale
2245eb4939 Eliminado admin 2025-05-15 01:16:54 -03:00
ale
eb3e4730bf Agregado es solidaio y eliminado proveedor 2025-05-15 01:13:38 -03:00
ale
2faea3b007 Modelos simplificados 2025-05-15 01:13:09 -03:00
ale
50d77b0108 Asignacion simplificada 2025-05-15 01:12:49 -03:00
ale
f8b487cebd Tablas simplificadas 2025-05-15 00:52:12 -03:00
ale
fc367c05a3 Agregado role_id a User 2025-05-15 00:32:08 -03:00
ale
bed975e944 Agregada tabla y modelo para roles de usuario 2025-05-14 23:24:36 -03:00
110 changed files with 3370 additions and 2118 deletions

2
.gitignore vendored
View file

@ -13,6 +13,8 @@ yarn-error.log
.idea .idea
/resources/csv/exports/*.csv /resources/csv/exports/*.csv
/resources/csv/canastas/*.csv /resources/csv/canastas/*.csv
/storage/csv/exports/*.csv
/storage/csv/canastas/*.csv
/public/css/ /public/css/
/public/js/ /public/js/
/public/mix-manifest.json /public/mix-manifest.json

View file

@ -14,10 +14,8 @@ use Illuminate\Support\Facades\Log;
class GrupoDeCompra extends Model class GrupoDeCompra extends Model
{ {
public $timestamps = false; protected $fillable = ["nombre", "region", "devoluciones_habilitadas"];
protected $fillable = ["nombre", "region", "telefono", "correo", "referente_finanzas", "cantidad_de_nucleos", "fila", "devoluciones_habilitadas"];
protected $table = 'grupos_de_compra'; protected $table = 'grupos_de_compra';
protected $hidden = ['password'];
public function subpedidos(): HasMany public function subpedidos(): HasMany
{ {
@ -45,6 +43,14 @@ class GrupoDeCompra extends Model
return $total; return $total;
} }
public function totalSinDevoluciones() {
$total = 0;
foreach ($this->pedidosAprobados() as $subpedido) {
$total = $total + $subpedido->totalSinDevoluciones();
}
return $total;
}
public function totalBarrial() public function totalBarrial()
{ {
$total = 0; $total = 0;
@ -105,12 +111,13 @@ class GrupoDeCompra extends Model
public function exportarPedidosAPdf() public function exportarPedidosAPdf()
{ {
$subpedidos = $this->pedidosAprobados(); $subpedidos = $this->pedidosAprobados();
PdfHelper::exportarPedidos($this->nombre . '.pdf', $subpedidos); $fecha = now()->format('Y-m-d');
PdfHelper::exportarPedidos($this->nombre . '-' . $fecha . '.pdf', $subpedidos);
} }
function pedidoParaPdf(): array function pedidoParaPdf(): array
{ {
$productos = $this->productosPedidos(true, true, 'producto_id'); $productos = $this->productosPedidos(true, 'producto_id');
$pedido = []; $pedido = [];
$pedido['productos'] = []; $pedido['productos'] = [];
@ -138,7 +145,8 @@ class GrupoDeCompra extends Model
public static function exportarPedidosBarrialesAPdf() public static function exportarPedidosBarrialesAPdf()
{ {
$barrios = GrupoDeCompra::barriosMenosPrueba()->get(); $barrios = GrupoDeCompra::barriosMenosPrueba()->get();
PdfHelper::exportarPedidos('pedidos_por_barrio.pdf', $barrios); $fecha = now()->format('Y-m-d');
PdfHelper::exportarPedidos('pedidos_por_barrio-' . $fecha . '.pdf', $barrios);
} }
static function filaVacia(string $product, int $columns): array static function filaVacia(string $product, int $columns): array
@ -172,7 +180,8 @@ class GrupoDeCompra extends Model
{ {
$records = $this->generarColumnaCantidades(); $records = $this->generarColumnaCantidades();
CsvHelper::generarCsv('csv/exports/' . $this->nombre . '.csv', $records); $fecha = now()->format('Y-m-d');
CsvHelper::generarCsv('csv/exports/' . $this->nombre . '-' . $fecha . '.csv', $records);
} }
public function generarColumnaCantidades(): array public function generarColumnaCantidades(): array
@ -229,7 +238,8 @@ class GrupoDeCompra extends Model
} }
array_splice($records, 0, 0, array($nucleos)); array_splice($records, 0, 0, array($nucleos));
CsvHelper::generarCsv('csv/exports/' . $this->nombre . '-completo.csv', $records); $fecha = now()->format('Y-m-d');
CsvHelper::generarCsv('csv/exports/' . $this->nombre . '-completo-' . $fecha . '.csv', $records);
} }
public function agregarCantidad($pedido, $id, array $records, $fila, int $i): array public function agregarCantidad($pedido, $id, array $records, $fila, int $i): array
@ -260,13 +270,12 @@ class GrupoDeCompra extends Model
return $result; return $result;
} }
public function productosPedidos($excluirBarriales = false, $excluirBonos = false, $orderBy = 'producto_nombre'): Collection public function productosPedidos($excluirBonos = false, $orderBy = 'producto_nombre'): Collection
{ {
$query = DB::table('pedidos_aprobados') $query = DB::table('pedidos_aprobados')
->where('grupo_de_compra_id', $this->id); ->where('grupo_de_compra_id', $this->id)
->where('producto_nombre','NOT LIKE','%barrial%');
if ($excluirBarriales)
$query = $query->where('producto_nombre','NOT LIKE','%barrial%');
if ($excluirBonos) if ($excluirBonos)
$query = $query->where('producto_es_bono',false); $query = $query->where('producto_es_bono',false);

View file

@ -3,14 +3,13 @@
namespace App\Helpers; namespace App\Helpers;
use App\Producto; use App\Producto;
use App\Proveedor;
use App\CanastaLog; use App\CanastaLog;
use DatabaseSeeder; use DatabaseSeeder;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class CanastaHelper class CanastaHelper
{ {
const TIPO = "Tipo"; const TIPO = "Tipo";
@ -21,10 +20,25 @@ class CanastaHelper
const CANASTA_CARGADA = 'Canasta cargada'; const CANASTA_CARGADA = 'Canasta cargada';
const PRODUCTO_TALLE_COLOR = "PTC"; const PRODUCTO_TALLE_COLOR = "PTC";
public static function canastaActual() {
$result = [];
$log = CanastaLog::where('descripcion', self::CANASTA_CARGADA)
->orderBy('created_at', 'desc')
->first();
$nombre = str_replace("csv/canastas/", "", $log->path);
$result["nombre"] = str_replace(".csv", "", $nombre);
$result["fecha"] = $log->created_at;
return $result;
}
public static function guardarCanasta($data, $path): string { public static function guardarCanasta($data, $path): string {
if (!File::exists(storage_path('csv/canastas'))) {
File::makeDirectory(storage_path('csv/canastas'), 0755, true);
}
$nombre = $data->getClientOriginalName(); $nombre = $data->getClientOriginalName();
$data->move(resource_path($path), $nombre); $data->move(storage_path($path), $nombre);
self::log($path . $nombre, self::ARCHIVO_SUBIDO); self::log($path . $nombre, self::ARCHIVO_SUBIDO);
@ -39,58 +53,38 @@ class CanastaHelper
$categoria = ''; $categoria = '';
foreach($registros as $i => $registro) { foreach($registros as $i => $registro) {
// saltear filas que no tienen tipo // saltear bono de transporte y filas que no tienen tipo
if (self::noTieneTipo($registro)) { if (self::noTieneTipo($registro) || $registro[self::TIPO] == "T")
var_dump("no hay tipo en la fila " . $i);
continue; continue;
}
// saltear bono de transporte
if ($registro[self::TIPO] == "T"){
continue;
}
// obtener categoria si no hay producto // obtener categoria si no hay producto
if ($registro[self::PRODUCTO] == '') { if ($registro[self::PRODUCTO] == '') {
// no es la pregunta de la copa? // no es la pregunta de la copa?
if (!Str::contains($registro[self::TIPO],"¿")) if (!Str::contains($registro[self::TIPO],"¿"))
$categoria = $registro[self::TIPO]; $categoria = $registro[self::TIPO];
continue; continue; // saltear si es la pregunta de la copa
} }
// completar producto // completar producto
$toInsert[] = [ $toInsert[] = DatabaseSeeder::addTimestamps([
'fila' => $i, 'fila' => $i,
'categoria' => $categoria, 'categoria' => $categoria,
'nombre' => trim(str_replace('*', '',$registro[self::PRODUCTO])), 'nombre' => trim(str_replace('*', '',$registro[self::PRODUCTO])),
'precio' => $registro[self::PRECIO], 'precio' => $registro[self::PRECIO],
'proveedor_id' => self::obtenerProveedor($registro[self::PRODUCTO]), 'es_solidario' => Str::contains($registro[self::PRODUCTO],"*"),
'bono' => preg_match(self::REGEX_BONO, $registro[self::TIPO]), 'bono' => preg_match(self::REGEX_BONO, $registro[self::TIPO]),
'requiere_notas'=> $registro[self::TIPO] == self::PRODUCTO_TALLE_COLOR, 'requiere_notas'=> $registro[self::TIPO] == self::PRODUCTO_TALLE_COLOR,
]; ]);
} }
foreach (array_chunk($toInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk) { foreach (array_chunk($toInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk)
DB::table('productos')->insert($chunk); Producto::insert($chunk);
}
self::agregarBonoBarrial(); self::agregarBonoBarrial();
self::log($archivo, self::CANASTA_CARGADA); self::log($archivo, self::CANASTA_CARGADA);
} }
private static function obtenerProveedor($nombre) {
$result = null;
if (Str::contains($nombre,"*")){
$result = Proveedor::firstOrCreate([
'nombre' => 'Proveedor de economía solidaria',
'economia_solidaria' => 1,
'nacional' => 1
])->id;
}
return $result;
}
/** /**
* @param $path * @param $path
* @param $descripcion * @param $descripcion
@ -122,13 +116,12 @@ class CanastaHelper
return Str::contains($c, 'BONO'); return Str::contains($c, 'BONO');
}); });
DB::table('productos')->insert([ Producto::create([
'fila' => 420,
'nombre' => "Bono barrial", 'nombre' => "Bono barrial",
'precio' => 20, 'precio' => 20,
'categoria' => $categoria, 'categoria' => $categoria,
'bono' => 1, 'bono' => 1,
'proveedor_id' => null, 'es_solidario' => 0,
'requiere_notas'=> false, 'requiere_notas'=> false,
]); ]);
} }

View file

@ -2,6 +2,7 @@
namespace App\Helpers; namespace App\Helpers;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Iterator; use Iterator;
use League\Csv\CannotInsertRecord; use League\Csv\CannotInsertRecord;
@ -13,7 +14,7 @@ use League\Csv\Writer;
class CsvHelper class CsvHelper
{ {
public static function getRecords($filePath): Iterator { public static function getRecords($filePath): Iterator {
$csv = Reader::createFromPath(resource_path($filePath)); $csv = Reader::createFromPath(storage_path($filePath));
try { try {
$csv->setDelimiter("|"); $csv->setDelimiter("|");
$csv->setEnclosure("'"); $csv->setEnclosure("'");
@ -27,8 +28,12 @@ class CsvHelper
public static function generarCsv($filePath, $contenido, $headers = null): void public static function generarCsv($filePath, $contenido, $headers = null): void
{ {
if (!File::exists(storage_path('csv/exports'))) {
File::makeDirectory(storage_path('csv/exports'), 0755, true);
}
try { try {
$writer = Writer::createFromPath(resource_path($filePath), 'w'); $writer = Writer::createFromPath(storage_path($filePath), 'w');
if ($headers) { if ($headers) {
$writer->insertOne($headers); $writer->insertOne($headers);
} }

View file

@ -9,7 +9,7 @@ class AdminController extends Controller
{ {
public function show() public function show()
{ {
return view('auth/admin_login'); return view('auth/login');
} }
public function index() { public function index() {
@ -23,14 +23,26 @@ class AdminController extends Controller
public function exportarPedidoACSV(GrupoDeCompra $gdc): BinaryFileResponse public function exportarPedidoACSV(GrupoDeCompra $gdc): BinaryFileResponse
{ {
$gdc->exportarPedidoEnCSV(); $gdc->exportarPedidoEnCSV();
$file = resource_path('csv/exports/'.$gdc->nombre.'.csv'); $pattern = storage_path('csv/exports/'. $gdc->nombre . '-*.csv');
return response()->download($file); $files = glob($pattern);
usort($files, function ($a, $b) {
return filemtime($b) <=> filemtime($a);
});
return response()->download($files[0]);
} }
public function exportarPedidoConNucleosACSV(GrupoDeCompra $gdc): BinaryFileResponse public function exportarPedidoConNucleosACSV(GrupoDeCompra $gdc): BinaryFileResponse
{ {
$gdc->exportarPedidoConNucleosEnCSV(); $gdc->exportarPedidoConNucleosEnCSV();
$file = resource_path('csv/exports/'.$gdc->nombre.'-completo.csv'); $pattern = storage_path('csv/exports/'.$gdc->nombre.'-completo-*.csv');
return response()->download($file); $files = glob($pattern);
usort($files, function ($a, $b) {
return filemtime($b) <=> filemtime($a);
});
return response()->download($files[0]);
} }
} }

View file

@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers\Api;
use App\Helpers\CanastaHelper;
use App\Http\Controllers\Controller;
class CanastaController extends Controller
{
public function canastaActual() {
return response()->json(CanastaHelper::canastaActual());
}
}

View file

@ -4,7 +4,9 @@ namespace App\Http\Controllers\Api;
use App\GrupoDeCompra; use App\GrupoDeCompra;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Resources\GrupoDeCompraReducido;
use App\Http\Resources\GrupoDeCompraResource; use App\Http\Resources\GrupoDeCompraResource;
use http\Env\Request;
class GrupoDeCompraController extends Controller class GrupoDeCompraController extends Controller
{ {
@ -16,4 +18,18 @@ class GrupoDeCompraController extends Controller
{ {
return new GrupoDeCompraResource($grupoDeCompra); return new GrupoDeCompraResource($grupoDeCompra);
} }
public function regiones()
{
return GrupoDeCompra::all()->pluck('region')->unique()->flatten();
}
public function region(string $region)
{
return GrupoDeCompra::where('region', $region)->get();
}
public function toggleDevoluciones(int $gdc) {
GrupoDeCompra::find($gdc)->toggleDevoluciones();
return response()->noContent();
}
} }

View file

@ -2,8 +2,8 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Filtros\FiltroDeProducto; use App\Filtros\FiltroDeProducto;
use App\Http\Resources\ProductoResource; use App\Http\Resources\ProductoResource;
use App\Producto; use App\Producto;
@ -15,9 +15,8 @@ class ProductoController extends Controller
return ProductoResource::collection(Producto::filtrar($filtros)->paginate(Producto::getPaginar($request))); return ProductoResource::collection(Producto::filtrar($filtros)->paginate(Producto::getPaginar($request)));
} }
public function show(Producto $producto) public function categorias()
{ {
return new ProductoResource($producto); return Producto::all()->pluck('categoria')->unique()->flatten();
} }
} }

View file

@ -17,7 +17,7 @@ class SubpedidoController extends Controller
{ {
public function index(FiltroDeSubpedido $filtros, Request $request) public function index(FiltroDeSubpedido $filtros, Request $request)
{ {
return Subpedido::filtrar($filtros)->get(); return Subpedido::filtrar($filtros)->select('id','nombre')->get();
} }
public function indexResources(FiltroDeSubpedido $filtros, Request $request) public function indexResources(FiltroDeSubpedido $filtros, Request $request)
@ -35,7 +35,7 @@ class SubpedidoController extends Controller
$s->nombre = $validado["nombre"]; $s->nombre = $validado["nombre"];
$s->grupo_de_compra_id = $validado["grupo_de_compra_id"]; $s->grupo_de_compra_id = $validado["grupo_de_compra_id"];
$s->save(); $s->save();
return $s; return $this->show($s);
} }
protected function validateSubpedido(): array protected function validateSubpedido(): array
@ -57,7 +57,7 @@ class SubpedidoController extends Controller
// recibe request, saca producto y cantidad, valida, y pasa a syncProducto en Subpedido // recibe request, saca producto y cantidad, valida, y pasa a syncProducto en Subpedido
public function syncProductos(Subpedido $subpedido) { public function syncProductos(Subpedido $subpedido) {
if ($subpedido->aprobado) if ($subpedido->aprobado)
return new SubpedidoResource($subpedido); abort(400, "No se puede modificar un pedido aprobado.");
$valid = request()->validate([ $valid = request()->validate([
'cantidad' => ['integer','required','min:0'], 'cantidad' => ['integer','required','min:0'],
@ -80,11 +80,12 @@ class SubpedidoController extends Controller
'aprobacion' => 'required | boolean' 'aprobacion' => 'required | boolean'
]); ]);
$subpedido->toggleAprobacion($valid['aprobacion']); $subpedido->toggleAprobacion($valid['aprobacion']);
return new SubpedidoResource($subpedido); return response()->noContent();
} }
public function syncDevoluciones(Subpedido $subpedido) { public function syncDevoluciones(Subpedido $subpedido) {
if ($subpedido->aprobado) return new SubpedidoResource($subpedido); if ($subpedido->aprobado)
abort(400, "No se puede modificar un pedido aprobado.");
$valid = request()->validate([ $valid = request()->validate([
'total' => 'required|min:0', 'total' => 'required|min:0',

View file

@ -6,7 +6,6 @@ use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider; use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller class LoginController extends Controller
{ {
@ -31,15 +30,8 @@ class LoginController extends Controller
protected function authenticated(Request $request, $user) protected function authenticated(Request $request, $user)
{ {
if ($user->is_compras) {
return redirect('compras/pedidos');
} else if ($user->is_admin) {
session(['admin_gdc' => $user->grupo_de_compra_id]);
return redirect('admin/pedidos');
} else {
return redirect('/'); return redirect('/');
} }
}
/** /**
* Create a new controller instance. * Create a new controller instance.

View file

@ -9,37 +9,45 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\BinaryFileResponse;
class ComprasController class ComisionesController
{ {
const CANASTAS_PATH = 'csv/canastas/'; const CANASTAS_PATH = 'csv/canastas/';
public function indexPedidos() { public function show()
return view('compras_pedidos'); {
return view('auth/login');
} }
public function descargarPedidos(): BinaryFileResponse public function descargarPedidos(): BinaryFileResponse
{ {
Producto::planillaTotales(); Producto::planillaTotales();
$file = resource_path('csv/exports/pedidos-por-barrio.csv'); $pattern = storage_path('csv/exports/pedidos-por-barrio-*.csv');
return response()->download($file); $files = glob($pattern);
usort($files, function ($a, $b) {
return filemtime($b) <=> filemtime($a);
});
return response()->download($files[0]);
} }
public function descargarNotas(): BinaryFileResponse public function descargarNotas(): BinaryFileResponse
{ {
Producto::planillaNotas(); Producto::planillaNotas();
$file = resource_path('csv/exports/notas-por-barrio.csv'); $pattern = storage_path('csv/exports/notas-por-barrio-*.csv');
return response()->download($file); $files = glob($pattern);
usort($files, function ($a, $b) {
return filemtime($b) <=> filemtime($a);
});
return response()->download($files[0]);
} }
public function pdf() { public function pdf() {
GrupoDeCompra::exportarPedidosBarrialesAPdf(); GrupoDeCompra::exportarPedidosBarrialesAPdf();
} }
public function show()
{
return view('auth/compras_login');
}
public function cargarCanasta(Request $request): JsonResponse public function cargarCanasta(Request $request): JsonResponse
{ {
$request->validate([ $request->validate([
@ -56,7 +64,7 @@ class ComprasController
public function descargarCanastaEjemplo(): BinaryFileResponse public function descargarCanastaEjemplo(): BinaryFileResponse
{ {
$file = resource_path('csv/productos.csv'); $file = storage_path('csv/productos.csv');
return response()->download($file); return response()->download($file);
} }
} }

View file

@ -0,0 +1,34 @@
<?php
namespace App\Http\Controllers;
use App\UserRole;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class RouteController extends Controller
{
function home(Request $request) {
if (!Auth::check())
return redirect('/login');
$barrio = UserRole::where('nombre', 'barrio')->first();
$admin = UserRole::where('nombre', 'admin_barrio')->first();
$comision = UserRole::where('nombre', 'comision')->first();
switch ($request->user()->role_id) {
case $barrio->id:
return redirect('/pedido');
case $admin->id:
return redirect('/admin');
case $comision->id:
return redirect('/comisiones');
default:
abort(400, 'Rol de usuario invalido');
}
}
function main(Request $request) {
return view('main');
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers;
use App\Subpedido;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
class SessionController extends Controller
{
public function store(Request $request): Response
{
$grupo_de_compra_id = Auth::user()->grupo_de_compra_id;
$validated = $request->validate([
'id' => 'required',
Rule::in(Subpedido::where('grupo_de_compra_id', $grupo_de_compra_id)->pluck('id')),
]);
session()->put('pedido_id', $validated["id"]);
return response()->noContent();
}
public function fetch(): JsonResponse
{
return response()->json(['id' => session('pedido_id')]);
}
public function destroy(): Response
{
session()->forget('pedido_id');
return response()->noContent();
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers;
use App\GrupoDeCompra;
use App\Http\Resources\GrupoDeCompraReducido;
use App\Http\Resources\GrupoDeCompraResource;
use App\UserRole;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class UserController extends Controller
{
public function rol(Request $request) {
return ["rol" => UserRole::find($request->user()->role_id)->nombre];
}
public function grupoDeCompra(Request $request)
{
$user = Auth::user();
$result = [ 'grupo_de_compra' => null, ];
$grupo_de_compra = GrupoDeCompra::find($user->grupo_de_compra_id);
switch (UserRole::findOrFail($user->role_id)->nombre) {
case 'barrio':
$result['grupo_de_compra'] = new GrupoDeCompraReducido($grupo_de_compra);
break;
case 'admin_barrio':
$result['grupo_de_compra'] = new GrupoDeCompraResource($grupo_de_compra);
break;
case 'comision':
break;
default:
abort(400, 'Rol invalido.');
}
return $result;
}
}

View file

@ -2,6 +2,7 @@
namespace App\Http; namespace App\Http;
use App\Http\Middleware\CheckRole;
use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
@ -56,8 +57,7 @@ class Kernel extends HttpKernel
*/ */
protected $routeMiddleware = [ protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class, 'auth' => \App\Http\Middleware\Authenticate::class,
'admin' => \App\Http\Middleware\Admin::class, 'role' => \App\Http\Middleware\CheckRole::class,
'compras' => \App\Http\Middleware\Compras::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,

View file

@ -1,20 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
class Admin
{
public function handle(Request $request, Closure $next)
{
$user = Auth::user();
if ($user->is_admin) {
return $next($request);
} else {
return response('Necesitás ser admin para hacer esto', 403);
}
}
}

View file

@ -15,6 +15,11 @@ class Authenticate extends Middleware
protected function redirectTo($request) protected function redirectTo($request)
{ {
if (!$request->expectsJson()) { if (!$request->expectsJson()) {
$path = $request->path();
if (preg_match('~^admin.*~i', $path))
return route('admin.login');
if (preg_match('~^comisiones.*~i', $path))
return route('comisiones.login');
return route('login'); return route('login');
} }
} }

View file

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use App\UserRole;
use Closure;
use Illuminate\Http\Request;
class CheckRole
{
/**
* Handle the incoming request.
*
* @param Request $request
* @param Closure $next
* @param string $role
* @return mixed
*/
public function handle($request, Closure $next, $role)
{
$role_id = UserRole::where('nombre', $role)->first()->id;
return $request->user()->role_id == $role_id ? $next($request)
: response('No tenés permiso para esto.', 403);
}
}

View file

@ -1,29 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class Compras
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
if (!Auth::check())
return redirect()->route('compras_login.show');
if (Auth::user()->is_compras) {
return $next($request);
} else {
return response('Necesitás ser de comisión compras para hacer esto', 403);
}
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class GrupoDeCompraReducido extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request): array {
return [
'id' => $this->id,
'nombre' => $this->nombre,
'devoluciones_habilitadas' => $this->devoluciones_habilitadas,
];
}
}

View file

@ -21,6 +21,7 @@ class GrupoDeCompraResource extends JsonResource
'devoluciones_habilitadas' => $this->devoluciones_habilitadas, 'devoluciones_habilitadas' => $this->devoluciones_habilitadas,
'pedidos' => SubpedidoResource::collection($this->subpedidos), 'pedidos' => SubpedidoResource::collection($this->subpedidos),
'total_a_recaudar' => number_format($this->totalARecaudar(),2), 'total_a_recaudar' => number_format($this->totalARecaudar(),2),
'total_sin_devoluciones' => number_format($this->totalSinDevoluciones(),2),
'total_barrial' => number_format($this->totalBarrial(),2), 'total_barrial' => number_format($this->totalBarrial(),2),
'total_devoluciones' => number_format($this->totalDevoluciones(),2), 'total_devoluciones' => number_format($this->totalDevoluciones(),2),
'total_a_transferir' => number_format($this->totalATransferir(),2), 'total_a_transferir' => number_format($this->totalATransferir(),2),

View file

@ -20,13 +20,8 @@ class ProductoResource extends JsonResource
'nombre' => $this->nombre, 'nombre' => $this->nombre,
'precio' => $this->precio, 'precio' => $this->precio,
'categoria' => $this->categoria, 'categoria' => $this->categoria,
'proveedor' => optional($this->proveedor)->nombre, 'economia_solidaria' => $this->es_solidario,
'economia_solidaria' => optional($this->proveedor)->economia_solidaria, 'nacional' => $this->es_solidario,
'nacional' => optional($this->proveedor)->nacional,
'imagen' => optional($this->poster)->url(),
'descripcion' => $this->descripcion,
'apto_veganxs' => $this->apto_veganxs,
'apto_celiacxs' => $this->apto_celiacxs,
'requiere_notas' => $this->requiere_notas, 'requiere_notas' => $this->requiere_notas,
]; ];
} }

View file

@ -15,11 +15,14 @@ class SubpedidoResource extends JsonResource
*/ */
public function toArray($request): array public function toArray($request): array
{ {
$productos = $this->productos;
foreach ($productos as $producto) {
$producto['pivot']['total'] = number_format($producto->pivot->cantidad * $producto->precio, 2);
}
return [ return [
'id' => $this->id, 'id' => $this->id,
'nombre' => $this->nombre, 'nombre' => $this->nombre,
'grupo_de_compra' => $this->grupoDeCompra, 'productos' => $productos,
'productos' => $this->productos,
'aprobado' => (bool) $this->aprobado, 'aprobado' => (bool) $this->aprobado,
'total' => number_format($this->total(),2), 'total' => number_format($this->total(),2),
'total_transporte' => number_format($this->totalTransporte()), 'total_transporte' => number_format($this->totalTransporte()),

View file

@ -7,7 +7,6 @@ use App\Helpers\CsvHelper;
use App\Helpers\TransporteHelper; use App\Helpers\TransporteHelper;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -15,18 +14,16 @@ use Illuminate\Support\Facades\DB;
class Producto extends Model class Producto extends Model
{ {
public $timestamps = false; protected $fillable = ["nombre", "precio", "categoria", "bono", "es_solidario", "requiere_notas"];
protected $fillable = ["nombre", "precio", "presentacion", "stock", "categoria"];
static int $paginarPorDefecto = 10;
public function subpedidos(): BelongsToMany public function subpedidos(): BelongsToMany
{ {
return $this->belongsToMany('App\Subpedido', 'productos_subpedidos')->withPivot(["cantidad", "notas"]); return $this->belongsToMany(Subpedido::class, 'productos_subpedidos')->withPivot(["cantidad", "notas"]);
} }
public function proveedor(): BelongsTo public static function noBarriales()
{ {
return $this->belongsTo('App\Proveedor'); return self::where('nombre', 'not like', '%barrial%');
} }
// Este método permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda) // Este método permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda)
@ -37,22 +34,22 @@ class Producto extends Model
public static function getPaginar(Request $request): int public static function getPaginar(Request $request): int
{ {
return $request->has('paginar') && intval($request->input('paginar')) ? intval($request->input('paginar')) : self::$paginarPorDefecto; return $request->has('paginar') && intval($request->input('paginar')) ? intval($request->input('paginar')) : self::all()->count();
} }
public static function productosFilaID() public static function productosFilaID()
{ {
return Producto::pluck('id', 'fila')->all(); return self::noBarriales()->pluck('id', 'fila')->all();
} }
public static function productosIDFila() public static function productosIDFila()
{ {
return Producto::pluck('fila', 'id')->all(); return self::noBarriales()->pluck('fila', 'id')->all();
} }
public static function productosIDNombre() public static function productosIDNombre()
{ {
return Producto::pluck('nombre', 'id')->all(); return self::noBarriales()->pluck('nombre', 'id')->all();
} }
static public function cantidadesPorBarrio(): Collection static public function cantidadesPorBarrio(): Collection
@ -112,7 +109,8 @@ class Producto extends Model
$planilla[$filaTransporte][] = $cantidad; $planilla[$filaTransporte][] = $cantidad;
} }
CsvHelper::generarCsv('csv/exports/pedidos-por-barrio.csv', $planilla, $headers); $fecha = now()->format('Y-m-d');
CsvHelper::generarCsv('csv/exports/pedidos-por-barrio- ' . $fecha . '.csv', $planilla, $headers);
} }
public static function notasPorBarrio(): Collection public static function notasPorBarrio(): Collection
@ -149,6 +147,7 @@ class Producto extends Model
$planilla[] = $fila; $planilla[] = $fila;
} }
CsvHelper::generarCsv('csv/exports/notas-por-barrio.csv', $planilla, $headers); $fecha = now()->format('Y-m-d');
CsvHelper::generarCsv('csv/exports/notas-por-barrio-' . $fecha . '.csv', $planilla, $headers);
} }
} }

View file

@ -1,18 +0,0 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Proveedor extends Model
{
public $timestamps = false;
protected $fillable = [ "nombre","direccion","telefono","correo","comentario" ];
protected $table = 'proveedores';
public function productos(): HasMany
{
return $this->hasMany('App\Producto');
}
}

View file

@ -12,17 +12,16 @@ use App\Filtros\FiltroDeSubpedido;
class Subpedido extends Model class Subpedido extends Model
{ {
public $timestamps = false;
protected $fillable = ['grupo_de_compra_id', 'aprobado', 'nombre', 'devoluciones_total', 'devoluciones_notas']; protected $fillable = ['grupo_de_compra_id', 'aprobado', 'nombre', 'devoluciones_total', 'devoluciones_notas'];
public function productos(): BelongsToMany public function productos(): BelongsToMany
{ {
return $this->belongsToMany('App\Producto')->withPivot(["cantidad", "total", "notas"]); return $this->belongsToMany(Producto::class)->withPivot(["cantidad", "notas"]);
} }
public function grupoDeCompra(): BelongsTo public function grupoDeCompra(): BelongsTo
{ {
return $this->belongsTo('App\GrupoDeCompra'); return $this->belongsTo(GrupoDeCompra::class);
} }
// Permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda) // Permite que se apliquen los filtros al hacer una request (por ejemplo, de búsqueda)
@ -100,7 +99,6 @@ class Subpedido extends Model
$this->productos()->syncWithoutDetaching([ $this->productos()->syncWithoutDetaching([
$producto->id => [ $producto->id => [
'cantidad' => $cantidad, 'cantidad' => $cantidad,
'total' => $cantidad * $producto->precio,
'notas' => $notas, 'notas' => $notas,
] ]
]); ]);
@ -108,11 +106,15 @@ class Subpedido extends Model
//si la cantidad es 0, se elimina el producto del subpedido //si la cantidad es 0, se elimina el producto del subpedido
$this->productos()->detach($producto->id); $this->productos()->detach($producto->id);
} }
$this->updated_at = now();
$this->save();
} }
public function toggleAprobacion(bool $aprobacion) public function toggleAprobacion(bool $aprobacion)
{ {
$this->aprobado = $aprobacion; $this->aprobado = $aprobacion;
$this->update(['aprobado' => $aprobacion]);
$this->save(); $this->save();
} }

View file

@ -16,7 +16,7 @@ class User extends Authenticatable
* @var array * @var array
*/ */
protected $fillable = [ protected $fillable = [
'name', 'email', 'password', 'name', 'email', 'password', 'role_id',
]; ];
/** /**
@ -40,6 +40,6 @@ class User extends Authenticatable
public function grupoDeCompra(): BelongsTo public function grupoDeCompra(): BelongsTo
{ {
return $this->belongsTo('App\GrupoDeCompra'); return $this->belongsTo(GrupoDeCompra::class);
} }
} }

View file

@ -4,7 +4,7 @@ namespace App;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class Admin extends Model class UserRole extends Model
{ {
// protected $fillable = ["nombre"];
} }

View file

@ -9,6 +9,7 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^7.4", "php": "^7.4",
"doctrine/dbal": "^2.2.0",
"fideloper/proxy": "^4.4", "fideloper/proxy": "^4.4",
"fruitcake/laravel-cors": "^2.0", "fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^6.3.1|^7.0.1", "guzzlehttp/guzzle": "^6.3.1|^7.0.1",

809
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,41 @@
<?php
use App\UserRole;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class CreateUserRolesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_roles', function (Blueprint $table) {
$table->id();
$table->string('nombre');
$table->timestamps();
});
$tipos = ["barrio", "admin_barrio", "comision"];
foreach ($tipos as $tipo) {
UserRole::create([
"nombre" => $tipo,
]);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user_roles');
}
}

View file

@ -0,0 +1,43 @@
<?php
use App\User;
use App\UserRole;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AgregarRolAUser extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->foreignId('role_id');
});
$barrio = UserRole::where('nombre', 'barrio')->first();
$admin_barrio = UserRole::where('nombre', 'admin_barrio')->first();
$comision = UserRole::where('nombre', 'comision')->first();
User::all()->each(function($user) use ($barrio, $comision, $admin_barrio) {
$user->role_id = $user->is_admin ? $admin_barrio->id :
($user->is_compras ? $comision->id : $barrio->id);
$user->save();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('user', function (Blueprint $table) {
$table->dropForeign('role_id');
});
}
}

View file

@ -0,0 +1,43 @@
<?php
use App\User;
use App\UserRole;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class SimplificarUsers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['is_admin', 'is_compras']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_admin')->default(false);
$table->boolean('is_compras')->default(false);
});
$admin_barrio = UserRole::where('nombre', 'admin_barrio')->first();
$comision = UserRole::where('nombre', 'comision')->first();
foreach (User::all() as $user) {
$user->is_admin = $user->role_id == $admin_barrio->id;
$user->is_compras = $user->role_id == $comision->id;
$user->save();
}
}
}

View file

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class SimplificarBarrios extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('grupos_de_compra', function (Blueprint $table) {
$table->dropColumn([
'cantidad_de_nucleos',
'telefono',
'correo',
'referente_finanzas',
]);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('grupos_de_compra', function (Blueprint $table) {
$table->double('cantidad_de_nucleos');
$table->string('telefono');
$table->string('correo');
$table->string('referente_finanzas');
});
}
}

View file

@ -0,0 +1,44 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class SimplificarProductos extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('productos', function (Blueprint $table) {
$table->dropColumn([
'presentacion',
'stock',
'imagen_id',
'descripcion',
'apto_veganxs',
'apto_celiacxs',
]);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('productos', function (Blueprint $table) {
$table->integer('presentacion')->nullable();
$table->integer('stock')->nullable();
$table->foreignId('imagen_id')->nullable();
$table->string('descripcion')->nullable();
$table->boolean('apto_veganxs')->nullable();
$table->boolean('apto_celiacxs')->nullable();
});
}
}

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class SimplificarProductoSubpedido extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('producto_subpedido', function (Blueprint $table) {
$table->dropColumn('total');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('producto_subpedido', function (Blueprint $table) {
$table->double('total');
});
}
}

View file

@ -0,0 +1,38 @@
<?php
use App\Producto;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AgregarEsSolidario extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('productos', function (Blueprint $table) {
$table->boolean('es_solidario')->default(false);
});
foreach (Producto::all() as $producto) {
$producto->es_solidario = $producto->proveedor_id != null;
$producto->save();
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('productos', function (Blueprint $table) {
$table->dropColumn('es_solidario');
});
}
}

View file

@ -0,0 +1,59 @@
<?php
use App\Producto;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class EliminarProveedor extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('productos', function (Blueprint $table) {
$table->dropColumn('proveedor_id');
});
Schema::dropIfExists('proveedores');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::create('proveedores', function (Blueprint $table) {
$table->id();
$table->string('nombre');
$table->string('direccion')->nullable();
$table->string('telefono')->nullable();
$table->string('correo')->nullable();
$table->boolean('economia_solidaria')->nullable();
$table->boolean('nacional')->nullable();
$table->text('detalles_de_pago')->nullable();
$table->text('comentario')->nullable();
$table->timestamps();
});
Schema::table('productos', function (Blueprint $table) {
$table->foreignId('proveedor_id')->nullable();
});
$proveedor_id = DB::table('proveedores')->insertGetId([
['nombre' => 'Proveedor de economía solidaria',
'economia_solidaria' => 1,
'nacional' => 1]
]);
foreach (Producto::all() as $producto) {
$producto->proveedor_id = $producto->es_solidario ? $proveedor_id : null;
$producto->save();
}
}
}

View file

@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class EliminarAdmin extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::dropIfExists('admins');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::create('admins', function (Blueprint $table) {
$table->id();
$table->string('nombre');
$table->foreignId('grupo_de_compra_id');
$table->string('email');
$table->string('contrasena');
$table->timestamps();
});
}
}

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class HacerFilaNullable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('productos', function (Blueprint $table) {
$table->integer('fila')->nullable()->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('productos', function (Blueprint $table) {
$table->integer('fila')->nullable(false)->change();
});
}
}

View file

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class QuitarPasswordDeGrupoDeCompra extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('grupos_de_compra', function (Blueprint $table) {
$table->dropColumn('password');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('grupos_de_compra', function (Blueprint $table) {
$table->string('password')->nullable();
});
}
}

View file

@ -1,10 +1,19 @@
<?php <?php
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class DatabaseSeeder extends Seeder class DatabaseSeeder extends Seeder
{ {
const CHUNK_SIZE = 100; const CHUNK_SIZE = 100;
static function addTimestamps($object) {
$now = DB::raw('CURRENT_TIMESTAMP');
$object['created_at'] = $now;
$object['updated_at'] = $now;
return $object;
}
/** /**
* Seed the application's database. * Seed the application's database.
* *

View file

@ -1,8 +1,10 @@
<?php <?php
use App\Helpers\CsvHelper as CsvHelperAlias; use App\Helpers\CsvHelper;
use App\GrupoDeCompra;
use App\User;
use App\UserRole;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
class GrupoDeCompraSeeder extends Seeder class GrupoDeCompraSeeder extends Seeder
@ -14,42 +16,32 @@ class GrupoDeCompraSeeder extends Seeder
*/ */
public function run() public function run()
{ {
$registros = CsvHelperAlias::getRecords('csv/barrios.csv'); $registros = CsvHelper::getRecords('csv/barrios.csv');
$gdcToInsert = []; $gdcToInsert = [];
$usersToInsert = []; $usersToInsert = [];
$roles = UserRole::where('nombre', 'barrio')->orWhere('nombre', 'admin_barrio')->get();
foreach($registros as $key => $registro){ foreach($registros as $key => $registro){
$gdcToInsert[] = [ $gdcToInsert[] = DatabaseSeeder::addTimestamps([
'nombre' => $registro['barrio'], 'nombre' => $registro['barrio'],
'region' => $registro['region'], 'region' => $registro['region'],
'telefono' => $registro['telefono'], ]);
'correo' => $registro['correo'],
'referente_finanzas' => $registro['referente']
];
$usersToInsert[] = [ foreach($roles as $role) {
'name' => $registro['barrio'], $nombre = $registro['barrio'] . ($role->nombre == 'barrio' ? '' : '_admin');
$usersToInsert[] = DatabaseSeeder::addTimestamps([
'name' => $nombre,
'password' => Hash::make("123"), 'password' => Hash::make("123"),
"is_admin" => 0, 'role_id' => $role->id,
'grupo_de_compra_id' => $key 'grupo_de_compra_id' => $key,
]; ]);
}
$usersToInsert[] = [
'name' => $registro['barrio'] . "_admin",
'password' => Hash::make("123"),
"is_admin" => 1,
'grupo_de_compra_id' => $key
];
} }
foreach (array_chunk($gdcToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk) foreach (array_chunk($gdcToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk)
{ GrupoDeCompra::insert($chunk);
DB::table('grupos_de_compra')->insert($chunk);
}
foreach (array_chunk($usersToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk) foreach (array_chunk($usersToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk)
{ User::insert($chunk);
DB::table('users')->insert($chunk);
}
} }
} }

View file

@ -1,7 +1,8 @@
<?php <?php
use App\User;
use App\UserRole;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
class UserSeeder extends Seeder class UserSeeder extends Seeder
@ -15,17 +16,14 @@ class UserSeeder extends Seeder
{ {
$usersToInsert = []; $usersToInsert = [];
$usersToInsert[] = [ $usersToInsert[] = DatabaseSeeder::addTimestamps([
'name' => 'compras', 'name' => 'comi',
'password' => Hash::make("123"), 'password' => Hash::make("123"),
'is_admin' => 0, 'role_id' => UserRole::where('nombre', 'comision')->first()->id,
'is_compras' => 1 ]);
];
foreach (array_chunk($usersToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk) foreach (array_chunk($usersToInsert,DatabaseSeeder::CHUNK_SIZE) as $chunk)
{ User::insert($chunk);
DB::table('users')->insert($chunk);
}
} }
} }

121
package-lock.json generated
View file

@ -8,16 +8,13 @@
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"bulma": "^0.9.4", "bulma": "^0.9.4",
"bulma-switch": "^2.0.4", "bulma-switch": "^2.0.4",
"bulma-toast": "^2.4.1" "bulma-toast": "^2.4.1",
"vuex": "^3.6.2"
}, },
"devDependencies": { "devDependencies": {
"axios": "^0.19.2", "axios": "^0.19.2",
"bootstrap": "^4.0.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"jquery": "^3.2",
"laravel-mix": "^5.0.1", "laravel-mix": "^5.0.1",
"lodash": "^4.17.19",
"popper.js": "^1.12",
"resolve-url-loader": "^2.3.1", "resolve-url-loader": "^2.3.1",
"sass": "^1.20.1", "sass": "^1.20.1",
"sass-loader": "^8.0.0", "sass-loader": "^8.0.0",
@ -350,7 +347,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@ -359,7 +355,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"dev": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@ -404,7 +399,6 @@
"version": "7.27.2", "version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz",
"integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/types": "^7.27.1" "@babel/types": "^7.27.1"
}, },
@ -1568,7 +1562,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
"integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.27.1", "@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1" "@babel/helper-validator-identifier": "^7.27.1"
@ -2060,7 +2053,6 @@
"version": "2.7.16", "version": "2.7.16",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz",
"integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==", "integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==",
"dev": true,
"dependencies": { "dependencies": {
"@babel/parser": "^7.23.5", "@babel/parser": "^7.23.5",
"postcss": "^8.4.14", "postcss": "^8.4.14",
@ -2075,7 +2067,6 @@
"version": "8.5.3", "version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -3095,26 +3086,6 @@
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"dev": true "dev": true
}, },
"node_modules/bootstrap": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz",
"integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"peerDependencies": {
"jquery": "1.9.1 - 3",
"popper.js": "^1.16.1"
}
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -4609,8 +4580,7 @@
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
"dev": true
}, },
"node_modules/cyclist": { "node_modules/cyclist": {
"version": "1.0.2", "version": "1.0.2",
@ -7945,12 +7915,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/jquery": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
"dev": true
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -8717,7 +8681,6 @@
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -9555,8 +9518,7 @@
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
"dev": true
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.1",
@ -9613,17 +9575,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
"dev": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/portfinder": { "node_modules/portfinder": {
"version": "1.0.37", "version": "1.0.37",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.37.tgz", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.37.tgz",
@ -10557,7 +10508,6 @@
"version": "2.8.8", "version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"optional": true, "optional": true,
"bin": { "bin": {
"prettier": "bin-prettier.js" "prettier": "bin-prettier.js"
@ -12085,7 +12035,6 @@
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -12094,7 +12043,6 @@
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -13345,7 +13293,6 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz",
"integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==", "integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==",
"deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.", "deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.",
"dev": true,
"dependencies": { "dependencies": {
"@vue/compiler-sfc": "2.7.16", "@vue/compiler-sfc": "2.7.16",
"csstype": "^3.1.0" "csstype": "^3.1.0"
@ -13463,6 +13410,14 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true "dev": true
}, },
"node_modules/vuex": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz",
"integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==",
"peerDependencies": {
"vue": "^2.0.0"
}
},
"node_modules/watchpack": { "node_modules/watchpack": {
"version": "1.7.5", "version": "1.7.5",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",
@ -15238,14 +15193,12 @@
"@babel/helper-string-parser": { "@babel/helper-string-parser": {
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
"dev": true
}, },
"@babel/helper-validator-identifier": { "@babel/helper-validator-identifier": {
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="
"dev": true
}, },
"@babel/helper-validator-option": { "@babel/helper-validator-option": {
"version": "7.27.1", "version": "7.27.1",
@ -15278,7 +15231,6 @@
"version": "7.27.2", "version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz",
"integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
"dev": true,
"requires": { "requires": {
"@babel/types": "^7.27.1" "@babel/types": "^7.27.1"
} }
@ -16040,7 +15992,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
"integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
"dev": true,
"requires": { "requires": {
"@babel/helper-string-parser": "^7.27.1", "@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1" "@babel/helper-validator-identifier": "^7.27.1"
@ -16309,7 +16260,6 @@
"version": "2.7.16", "version": "2.7.16",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz",
"integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==", "integrity": "sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg==",
"dev": true,
"requires": { "requires": {
"@babel/parser": "^7.23.5", "@babel/parser": "^7.23.5",
"postcss": "^8.4.14", "postcss": "^8.4.14",
@ -16321,7 +16271,6 @@
"version": "8.5.3", "version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
"dev": true,
"requires": { "requires": {
"nanoid": "^3.3.8", "nanoid": "^3.3.8",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
@ -17166,13 +17115,6 @@
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"dev": true "dev": true
}, },
"bootstrap": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz",
"integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==",
"dev": true,
"requires": {}
},
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -18344,8 +18286,7 @@
"csstype": { "csstype": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
"dev": true
}, },
"cyclist": { "cyclist": {
"version": "1.0.2", "version": "1.0.2",
@ -20905,12 +20846,6 @@
} }
} }
}, },
"jquery": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
"dev": true
},
"js-tokens": { "js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -21555,8 +21490,7 @@
"nanoid": { "nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="
"dev": true
}, },
"nanomatch": { "nanomatch": {
"version": "1.2.13", "version": "1.2.13",
@ -22207,8 +22141,7 @@
"picocolors": { "picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
"dev": true
}, },
"picomatch": { "picomatch": {
"version": "2.3.1", "version": "2.3.1",
@ -22247,12 +22180,6 @@
"find-up": "^4.0.0" "find-up": "^4.0.0"
} }
}, },
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"dev": true
},
"portfinder": { "portfinder": {
"version": "1.0.37", "version": "1.0.37",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.37.tgz", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.37.tgz",
@ -23060,7 +22987,6 @@
"version": "2.8.8", "version": "2.8.8",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"optional": true "optional": true
}, },
"private": { "private": {
@ -24290,14 +24216,12 @@
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
"dev": true
}, },
"source-map-js": { "source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
"dev": true
}, },
"source-map-resolve": { "source-map-resolve": {
"version": "0.5.3", "version": "0.5.3",
@ -25287,7 +25211,6 @@
"version": "2.7.16", "version": "2.7.16",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.16.tgz",
"integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==", "integrity": "sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw==",
"dev": true,
"requires": { "requires": {
"@vue/compiler-sfc": "2.7.16", "@vue/compiler-sfc": "2.7.16",
"csstype": "^3.1.0" "csstype": "^3.1.0"
@ -25382,6 +25305,12 @@
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
"dev": true "dev": true
}, },
"vuex": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz",
"integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw==",
"requires": {}
},
"watchpack": { "watchpack": {
"version": "1.7.5", "version": "1.7.5",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz",

View file

@ -11,12 +11,8 @@
}, },
"devDependencies": { "devDependencies": {
"axios": "^0.19.2", "axios": "^0.19.2",
"bootstrap": "^4.0.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"jquery": "^3.2",
"laravel-mix": "^5.0.1", "laravel-mix": "^5.0.1",
"lodash": "^4.17.19",
"popper.js": "^1.12",
"resolve-url-loader": "^2.3.1", "resolve-url-loader": "^2.3.1",
"sass": "^1.20.1", "sass": "^1.20.1",
"sass-loader": "^8.0.0", "sass-loader": "^8.0.0",
@ -27,6 +23,7 @@
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"bulma": "^0.9.4", "bulma": "^0.9.4",
"bulma-switch": "^2.0.4", "bulma-switch": "^2.0.4",
"bulma-toast": "^2.4.1" "bulma-toast": "^2.4.1",
"vuex": "^3.6.2"
} }
} }

112
resources/js/app.js vendored
View file

@ -5,6 +5,7 @@
*/ */
import axios from 'axios'; import axios from 'axios';
import Vue from 'vue'; import Vue from 'vue';
window.Vue = require('vue'); window.Vue = require('vue');
window.Event = new Vue(); window.Event = new Vue();
window.axios = axios; window.axios = axios;
@ -18,20 +19,11 @@ window.bulmaToast = require('bulma-toast');
* Eg. ./components/ExampleComponent.vue -> <example-component></example-component> * Eg. ./components/ExampleComponent.vue -> <example-component></example-component>
*/ */
import './components'; import './components';
import store from "./store";
/**
* Constants
*/
Vue.prototype.$rootMiga = {
nombre: "Categorías",
href: "/productos"
}
/** /**
* Global methods * Global methods
*/ */
Vue.prototype.$settearProducto = function(cantidad, id) {
Event.$emit("sync-subpedido", this.cant, this.producto.id)
}
Vue.prototype.$toast = function (mensaje, duration = 2000) { Vue.prototype.$toast = function (mensaje, duration = 2000) {
return window.bulmaToast.toast({ return window.bulmaToast.toast({
message: mensaje, message: mensaje,
@ -40,110 +32,14 @@ Vue.prototype.$toast = function(mensaje, duration = 2000) {
position: 'bottom-center', position: 'bottom-center',
}); });
} }
Vue.prototype.$limpiarFloat = function(unFloat) {
return parseFloat(unFloat.replace(/,/g, ''))
}
Vue.prototype.$limpiarInt = function(unInt) {
return parseInt(unInt.replace(/,/g, ''))
}
/** /**
* Next, we will create a fresh Vue application instance and attach it to * Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application * the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs. * or customize the JavaScript scaffolding to fit your unique needs.
*/ */
const app = new Vue({ new Vue({
el: '#root', el: '#root',
data() { store,
return {
gdc: null,
pedido: null,
devoluciones: null,
}
},
computed: {
productos: function() {
return this.pedido.productos
}
},
methods: {
cantidad(producto) {
let pedido = this.productos.some(p => p.id == producto.id)
return pedido ? this.productos.find(p => p.id == producto.id).pivot.cantidad : 0
},
notas(producto) {
let pedido = this.productos.some(p => p.id == producto.id);
return pedido ? this.productos.find(p => p.id == producto.id).pivot.notas : "";
},
settearDevoluciones() {
axios.get(`/api/grupos-de-compra/${this.gdc}/devoluciones`)
.then(response => {
this.devoluciones = response.data.devoluciones;
});
}
},
mounted() {
Event.$on('obtener-sesion', () => {
axios.get('/subpedidos/obtener_sesion')
.then(response => {
if (response.data.subpedido.id) {
this.gdc = response.data.gdc;
this.settearDevoluciones();
this.pedido = response.data.subpedido.id;
axios.get('/api/subpedidos/' + this.pedido)
.then(response => {
this.pedido = response.data.data;
Event.$emit("pedido-actualizado");
});
} else {
axios.get('/admin/obtener_sesion')
.then(response => {
this.gdc = response.data.gdc
});
}
})
})
Event.$on('sync-subpedido', (cantidad, id, notas) => {
if (this.pedido.aprobado) {
this.$toast('No se puede modificar un pedido ya aprobado', 2000);
return;
}
axios.post("/api/subpedidos/" + this.pedido.id + "/sync", {
cantidad: cantidad,
producto_id: id,
notas: notas,
}).then((response) => {
this.pedido = response.data.data
this.$toast('Pedido actualizado exitosamente')
Event.$emit("pedido-actualizado");
});
});
// Actualizar monto y notas de devoluciones
Event.$on('sync-devoluciones', (total, notas) => {
if (this.pedido.aprobado) {
this.$toast('No se puede modificar un pedido ya aprobado', 2000);
return;
}
axios.post("api/subpedidos/" + this.pedido.id + "/sync_devoluciones", {
total: total,
notas: notas,
}).then((response) => {
this.pedido = response.data.data;
this.$toast('Pedido actualizado');
Event.$emit("pedido-actualizado");
});
});
Event.$on('aprobacion-subpedido', (subpedidoId, aprobado) => {
axios.post("/api/admin/subpedidos/" + subpedidoId + "/aprobacion", {
aprobacion: aprobado
}).then((response) => {
Event.$emit('sync-aprobacion', response.data.data);
this.$toast('Pedido ' + (aprobado ? 'aprobado' : 'desaprobado') + ' exitosamente')
})
})
Event.$emit('obtener-sesion')
},
}); });

View file

@ -1,41 +0,0 @@
window._ = require('lodash');
/**
* We'll load jQuery and the Bootstrap jQuery plugin which provides support
* for JavaScript based Bootstrap features such as modals and tabs. This
* code may be modified to fit the specific needs of your application.
*/
try {
window.Popper = require('popper.js').default;
window.$ = window.jQuery = require('jquery');
require('bootstrap');
} catch (e) {}
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
// import Echo from 'laravel-echo';
// window.Pusher = require('pusher-js');
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: process.env.MIX_PUSHER_APP_KEY,
// cluster: process.env.MIX_PUSHER_APP_CLUSTER,
// forceTLS: true
// });

View file

@ -0,0 +1,28 @@
<script>
import LoginInput from "./login/LoginInput.vue";
import LoginLoginTitulos from "./login/LoginTitulos.vue";
import { mapGetters } from "vuex";
import LoginDropdown from "./login/LoginDropdown.vue";
export default {
name: 'LoginForm',
components: { LoginDropdown, LoginTitulos: LoginLoginTitulos, LoginInput },
computed: {
...mapGetters("login", ["estilos"])
}
}
</script>
<template>
<div id="login-form" :class="estilos.fondo">
<section class="section">
<login-dropdown class="is-hidden-tablet"/>
<login-titulos/>
<login-input/>
</section>
</div>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,35 @@
<script>
import NavBar from "./comunes/NavBar.vue";
import { mapActions, mapState } from "vuex";
import ComisionesBody from "./comisiones/Body.vue";
import AdminBody from "./admin/Body.vue";
import PedidosBody from "./pedidos/Body.vue";
import InfoTags from "./comunes/InfoTags.vue";
export default {
name: 'Main',
components: { InfoTags, ComisionesBody, AdminBody, PedidosBody, NavBar },
computed: {
...mapState("login", ["rol"]),
},
methods: {
...mapActions("login", ["getRol"]),
},
async mounted() {
await this.getRol();
},
}
</script>
<template>
<div id="app-main">
<nav-bar/>
<pedidos-body v-if="rol === 'barrio'"/>
<admin-body v-else-if="rol === 'admin_barrio'"/>
<comisiones-body v-else-if="rol === 'comision'"/>
<info-tags/>
</div>
</template>
<style scoped>
</style>

View file

@ -1,15 +1,11 @@
<template> <template>
<div class="block ml-3 mr-3 is-max-widescreen is-max-desktop"> <div class="block ml-3 mr-3 is-max-widescreen is-max-desktop">
<comunes-tabs-secciones :tabs="tabs" :tabInicial="tabActiva"></comunes-tabs-secciones> <tabs-secciones :tabs="tabs" :tabInicial="tabActiva"/>
<div class="block" id="pedidos-seccion" <div class="block" id="pedidos-seccion"
:class="seccionActiva === 'pedidos-seccion' ? 'is-active' : 'is-hidden'"> :class="seccionActiva === 'pedidos-seccion' ? 'is-active' : 'is-hidden'">
<div class="block pb-6" id="pedidos-tabla-y-dropdown" v-if="hayPedidos"> <div class="block pb-6" id="pedidos-tabla-y-dropdown" v-if="hayPedidos">
<admin-dropdown-descargar <dropdown-descargar/>
:gdc_id="gdc.id"> <tabla-pedidos/>
</admin-dropdown-descargar>
<admin-tabla-pedidos
:gdc="this.gdc"
></admin-tabla-pedidos>
</div> </div>
<p class="has-text-centered" v-else> <p class="has-text-centered" v-else>
Todavía no hay ningún pedido para administrar. Todavía no hay ningún pedido para administrar.
@ -17,8 +13,7 @@
</div> </div>
<div class="block pb-6" id="caracteristicas-seccion" <div class="block pb-6" id="caracteristicas-seccion"
:class="seccionActiva === 'caracteristicas-seccion' ? 'is-active' : 'is-hidden'"> :class="seccionActiva === 'caracteristicas-seccion' ? 'is-active' : 'is-hidden'">
<admin-caracteristicas-opcionales> <caracteristicas-opcionales/>
</admin-caracteristicas-opcionales>
</div> </div>
</div> </div>
</template> </template>
@ -28,19 +23,17 @@ import CaracteristicasOpcionales from "./CaracteristicasOpcionales.vue";
import TabsSecciones from "../comunes/TabsSecciones.vue"; import TabsSecciones from "../comunes/TabsSecciones.vue";
import DropdownDescargar from "./DropdownDescargar.vue"; import DropdownDescargar from "./DropdownDescargar.vue";
import TablaPedidos from "./TablaPedidos.vue"; import TablaPedidos from "./TablaPedidos.vue";
import TablaBonos from "./TablaBonos.vue"; import { mapActions, mapGetters } from "vuex";
import axios from "axios";
export default { export default {
name: "AdminBody",
components: { components: {
CaracteristicasOpcionales, CaracteristicasOpcionales,
TabsSecciones, TabsSecciones,
DropdownDescargar, DropdownDescargar,
TablaPedidos, TablaPedidos,
TablaBonos,
}, },
data() { data() {
return { return {
gdc: undefined,
tabs: [{ id: "pedidos", nombre: "Pedidos" }, tabs: [{ id: "pedidos", nombre: "Pedidos" },
{ id: "caracteristicas", nombre: "Caracteristicas opcionales" }], { id: "caracteristicas", nombre: "Caracteristicas opcionales" }],
tabActiva: "pedidos", tabActiva: "pedidos",
@ -48,32 +41,17 @@ export default {
} }
}, },
computed: { computed: {
hayPedidos: function() { ...mapGetters('admin', ['hayPedidos']),
return this.gdc && this.gdc.pedidos.length !== 0
},
hayAprobados: function() {
return this.gdc && this.gdc.pedidos.filter(p => p.aprobado).length > 0
}
}, },
methods: { methods: {
...mapActions('admin', ['getGrupoDeCompra']),
setSeccionActiva(tabId) { setSeccionActiva(tabId) {
this.tabActiva = tabId; this.tabActiva = tabId;
this.seccionActiva = tabId + "-seccion"; this.seccionActiva = tabId + "-seccion";
}, },
actualizar() {
axios.get('/api/grupos-de-compra/' + this.$root.gdc)
.then(response => {
this.gdc = response.data.data;
console.log(this.gdc);
})
}
}, },
async mounted() { async mounted() {
Event.$on('sync-aprobacion', (_) => { await this.getGrupoDeCompra();
this.actualizar();
});
await new Promise(r => setTimeout(r, 1000));
this.actualizar();
}, },
} }
</script> </script>

View file

@ -1,12 +0,0 @@
<template>
<div class="buttons is-right">
<a class="button is-danger is-light is-small" href="/admin">
<span class="icon">
<i class="fa fa-solid fa-user-check"></i>
</span>
<span>
Admin
</span>
</a>
</div>
</template>

View file

@ -8,8 +8,7 @@ export default {
caracteristicas: [ caracteristicas: [
{ {
id: "devoluciones", id: "devoluciones",
nombre: "Devoluciones", nombre: "Devoluciones"
habilitada: false
}, },
] ]
} }
@ -27,16 +26,15 @@ export default {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<admin-fila-caracteristica <fila-caracteristica
v-for="(c,i) in caracteristicas" v-for="(c,i) in caracteristicas"
:key="i" :key="i"
:caracteristica="c"> :caracteristica="c"
</admin-fila-caracteristica> />
</tbody> </tbody>
</table> </table>
</div> </div>
</template> </template>
<style scoped> <style scoped>
</style> </style>

View file

@ -14,13 +14,13 @@
</div> </div>
<div class="dropdown-menu" id="dropdown-menu" role="menu"> <div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content"> <div class="dropdown-content">
<a :href="'/admin/exportar-pedido-a-csv/' + gdc_id" class="dropdown-item has-background-primary"> <a :href="'/admin/exportar-pedido-a-csv/' + grupo_de_compra_id" class="dropdown-item has-background-primary">
Planilla para central (CSV) Planilla para central (CSV)
</a> </a>
<a :href="'/admin/exportar-planillas-a-pdf/' + gdc_id" class="dropdown-item"> <a :href="'/admin/exportar-planillas-a-pdf/' + grupo_de_compra_id" class="dropdown-item">
Planillas para armado (PDF) Planillas para armado (PDF)
</a> </a>
<a :href="'/admin/exportar-pedido-con-nucleos-a-csv/' + gdc_id" class="dropdown-item"> <a :href="'/admin/exportar-pedido-con-nucleos-a-csv/' + grupo_de_compra_id" class="dropdown-item">
Planilla completa de la canasta (CSV) Planilla completa de la canasta (CSV)
</a> </a>
</div> </div>
@ -30,27 +30,18 @@
</template> </template>
<script> <script>
import { mapGetters, mapState } from "vuex";
export default { export default {
props: {
gdc_id: {
type: Number,
required: true
},
},
data() { data() {
return { return {
dropdownActivo: false dropdownActivo: false
} };
}, },
computed: { computed: {
hayAprobados: function() { ...mapState('admin', ["grupo_de_compra_id"]),
return this.$parent.hayAprobados; ...mapGetters('admin', ["hayAprobados"]),
}
}, },
} }
</script> </script>
<style> <style></style>
</style>

View file

@ -1,48 +1,19 @@
<script> <script>
import axios from "axios"; import {mapActions, mapGetters, mapState} from "vuex";
export default { export default {
props: { props: {
caracteristica: Object caracteristica: Object
}, },
data() { computed: {
return { ...mapState('admin',["grupo_de_compra_id"]),
gdc: undefined ...mapGetters('admin',["getCaracteristica"]),
habilitada() {
return this.getCaracteristica(this.caracteristica.id);
} }
}, },
watch: {
'$root.gdc' : {
handler(newValue) {
if (newValue) {
this.gdc = newValue;
this.obtenerValor();
}
}
},
},
methods: { methods: {
toggleActivacion() { ...mapActions('admin',["toggleCaracteristica"]),
const id = this.caracteristica.id;
axios.post(`/api/grupos-de-compra/${this.gdc}/${id}`)
.then(response => {
this.caracteristica.habilitada = response.data[id];
this.$root[id] = response.data[id];
});
},
obtenerValor() {
const id = this.caracteristica.id;
axios.get(`/api/grupos-de-compra/${this.gdc}/${id}`)
.then(response => {
this.caracteristica.habilitada = response.data[id];
this.$root[id] = response.data[id];
});
},
},
mounted() {
if (this.$root.gdc) {
this.gdc = this.$root.gdc;
this.obtenerValor();
}
} }
} }
</script> </script>
@ -54,16 +25,16 @@ export default {
<div class="field"> <div class="field">
<input type="checkbox" class="switch is-rounded is-success" <input type="checkbox" class="switch is-rounded is-success"
:id="'switch-' + caracteristica.id" :id="'switch-' + caracteristica.id"
:checked="caracteristica.habilitada" :checked="habilitada"
@change="toggleActivacion(caracteristica)"> @change="toggleCaracteristica({ caracteristica_id: caracteristica.id })">
<label :for="'switch-' + caracteristica.id"> <label :for="'switch-' + caracteristica.id">
<span class="is-hidden-mobile">{{ caracteristica.habilitada ? 'Habilitada' : 'Deshabilitada' }}</span> <span class="is-hidden-mobile">
{{ habilitada ? 'Habilitada' : 'Deshabilitada' }}
</span>
</label> </label>
</div> </div>
</td> </td>
</tr> </tr>
</template> </template>
<style scoped> <style scoped></style>
</style>

View file

@ -1,36 +1,41 @@
<template> <template>
<tr> <tr>
<td>{{ pedido.nombre }}</td> <td>{{ pedido.nombre }}</td>
<td v-if="$root.devoluciones" class="has-text-right" >{{ pedido.total_sin_devoluciones }}</td> <td v-if="devoluciones_habilitadas" class="has-text-right" >
<td v-if="$root.devoluciones" class="has-text-right" ><abbr :title="pedido.devoluciones_notas">-{{ pedido.devoluciones_total }}</abbr></td> {{ pedido.total_sin_devoluciones }}
<td class="has-text-right" >{{ $root.devoluciones ? pedido.total : pedido.total_sin_devoluciones }}</td> </td>
<td v-if="devoluciones_habilitadas" class="has-text-right" >
<abbr :title="pedido.devoluciones_notas">
-{{ pedido.devoluciones_total }}
</abbr>
</td>
<td class="has-text-right" >
{{ devoluciones_habilitadas ? pedido.total : pedido.total_sin_devoluciones }}
</td>
<td> <td>
<admin-switch-aprobacion <switch-aprobacion :pedido_id="pedido_id"/>
:pedido="pedido">
</admin-switch-aprobacion>
</td> </td>
</tr> </tr>
</template> </template>
<script> <script>
import SwitchAprobacion from "./SwitchAprobacion.vue"; import SwitchAprobacion from "./SwitchAprobacion.vue";
import { mapGetters, mapState } from "vuex";
export default { export default {
components: { components: {
SwitchAprobacion SwitchAprobacion
}, },
props: { props: {
pedido: Object pedido_id: Number
},
computed: {
...mapState('admin',["devoluciones_habilitadas"]),
...mapGetters('admin',["getPedido"]),
pedido() {
return this.getPedido(this.pedido_id);
},
}, },
mounted() {
Event.$on('sync-aprobacion', (unPedido) => {
if (this.pedido.id === unPedido.id) {
this.pedido = unPedido
}
})
}
} }
</script> </script>
<style scoped> <style scoped></style>
</style>

View file

@ -1,56 +0,0 @@
<template>
<div v-show="visible" class="block">
<div class="field">
<label class="label has-text-white">Contraseña de administración del barrio</label>
<div class="field has-addons">
<div class="control">
<input required class="input" :type="this.passwordType" name="password" placeholder="Contraseña de admin del barrio">
</div>
<div class="control">
<a class="button is-warning" @click="togglePassword">
{{ (passwordVisible ? 'Ocultar' : 'Mostrar') + ' contraseña'}}
</a>
</div>
</div>
<p class="help has-text-white">Si no la sabés, consultá a la comisión informática.</p>
</div>
<div class="field">
<div class="control">
<input type="submit" class="button is-warning" value="Ingresar"/>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
visible: false,
gdc: null,
passwordVisible: false,
passwordType: "password",
}
},
mounted() {
Event.$on('gdc-seleccionado', (gdc) => {
this.gdc = gdc;
this.visible = true;
});
},
methods: {
togglePassword() {
if (this.passwordVisible) this.passwordType = "password";
else this.passwordType = "text"
this.passwordVisible = !this.passwordVisible
}
}
}
</script>
<style>
.help {
font-size: 1rem;
}
</style>

View file

@ -1,46 +1,34 @@
<template> <template>
<div class="field"> <div class="field">
<input type="checkbox" name="switchRoundedSuccess" class="switch is-rounded is-success" <input type="checkbox" name="switchRoundedSuccess" class="switch is-rounded is-success"
:id="'switch'+this.pedido.id" :id="'switch' + pedido_id"
:checked="pedido.aprobado" :checked="aprobado"
@change="toggleAprobacion"> @change="setAprobacionPedido({ pedido_id: pedido_id, aprobacion: !aprobado })">
<label :for="'switch'+this.pedido.id"> <label :for="'switch' + pedido_id">
<span class="is-hidden-mobile">{{ mensaje }}</span> <span class="is-hidden-mobile">{{ mensaje }}</span>
</label> </label>
</div> </div>
</template> </template>
<script> <script>
import { mapActions, mapGetters } from "vuex";
export default { export default {
props: { props: {
pedido: Object pedido_id: Number
},
data() {
return {
aprobado: this.pedido.aprobado
}
}, },
computed: { computed: {
mensaje: function () { ...mapGetters('admin', ["getPedido"]),
return this.aprobado ? "Pagado" : "No pagado" aprobado() {
return this.getPedido(this.pedido_id).aprobado;
},
mensaje() {
return this.aprobado ? "Aprobado" : "No aprobado";
} }
}, },
methods: { methods: {
toggleAprobacion() { ...mapActions('admin',["setAprobacionPedido"]),
Event.$emit('aprobacion-subpedido', this.pedido.id, !this.aprobado);
this.aprobado = !this.aprobado
}
}, },
mounted() {
Event.$on('sync-aprobacion', (unPedido) => {
if (this.pedido.id === unPedido.id) {
this.pedido = unPedido
}
})
}
} }
</script> </script>
<style scoped> <style scoped></style>
</style>

View file

@ -1,97 +0,0 @@
<template>
<div class="block">
<div class="block" v-show="!hayBonos">
<p class="has-text-centered">
Todavía no hay bonos pedidos.
</p>
</div>
<div class="block" v-show="hayBonos">
<table class="table is-bordered is-striped is-hoverable is-fullwidth">
<thead>
<tr>
<th><abbr title="Núcleo">Núcleo</abbr></th>
<td v-for="(bono,i) in bonos" :key="i" class="is-1">
{{ bono.nombre }}
</td>
<th><abbr title="Total a Pagar">Total $</abbr></th>
</tr>
</thead>
<tbody>
<tr v-for="(bp, i) in bonosPorPedido" :key="i">
<td> {{ bp.nucleo }} </td>
<td v-for="(bono,j) in bp.bonos" :key="j" class="has-text-right">
{{ bono.cantidad }}
</td>
<td class="has-text-right"> {{ bp.total }} </td>
</tr>
</tbody>
<tfoot>
<tr>
<th></th>
<th :colspan="bonos.length">Total bonos</th>
<th class="has-text-right">$ {{ totalBonos }}</th>
</tr>
</tfoot>
</table>
</div>
</div>
</template>
<script>
export default {
props: {
pedidos: {
type: Array,
required: true
}
},
data() {
return {
bonos: []
}
},
computed: {
pedidosAprobados: function() {
return this.pedidos.filter(p => p.aprobado)
},
hayBonos: function() {
return this.pedidosAprobados.filter(p => p.subtotal_bonos != 0).length !== 0
},
bonosPorPedido: function() {
let bonosPorPedido = this.pedidosAprobados.map(p => p = {"nucleo":p.nombre, "bonos":p.productos.filter(x => x.bono), "total":p.subtotal_bonos});
bonosPorPedido.forEach(bp => {
bp.bonos = bp.bonos.map(b => b = {"bono":b.nombre, "cantidad":b.pivot.cantidad, "total":b.pivot.total, "id":b.id})
})
return bonosPorPedido.map(bp => this.completarBonos(bp));
},
totalBonos: function() {
let total = 0
this.bonosPorPedido.map(bp => total += parseInt(bp.total))
return total
},
},
methods: {
completarBonos(bonosPedido) {
this.bonos.map(b => {
if (!bonosPedido.bonos.map(x => x.id).includes(b.id))
bonosPedido.bonos.push({"bono":b.nombre, "cantidad":0, "total":0, "id":b.id})
})
bonosPedido.bonos = bonosPedido.bonos.sort((b1,b2) => b1.id - b2.id)
return bonosPedido
}
},
beforeMount() {
axios.get("../api/productos", {
params: {
categoria:'TRANSPORTE, BONOS Y FINANCIAMIENTO SORORO',
}
}).then(response => {
this.bonos = response.data.data;
});
},
}
</script>
<style>
</style>

View file

@ -4,17 +4,18 @@
<thead> <thead>
<tr> <tr>
<th>Núcleo</th> <th>Núcleo</th>
<th v-if="$root.devoluciones"><abbr title="Total sin tomar en cuenta las devoluciones">Total parcial $</abbr></th> <th v-if="devoluciones_habilitadas"><abbr title="Total sin tomar en cuenta las devoluciones">Total parcial $</abbr></th>
<th v-if="$root.devoluciones"><abbr title="Devoluciones correspondientes al núcleo">Devoluciones $</abbr></th> <th v-if="devoluciones_habilitadas"><abbr title="Devoluciones correspondientes al núcleo">Devoluciones $</abbr></th>
<th><abbr title="Total a Pagar por el núleo">{{ $root.devoluciones ? 'Total real' : 'Total' }} $</abbr></th> <th><abbr title="Total a Pagar por el núleo">{{ devoluciones_habilitadas ? 'Total real' : 'Total' }} $</abbr></th>
<th class="is-1"><abbr title="Pagado">Pagado</abbr></th> <th class="is-1"><abbr title="Aprobado">Aprobado</abbr></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<admin-fila-pedido <fila-pedido
v-for="pedido in gdc.pedidos" v-for="pedido in pedidos"
:pedido="pedido" :key="pedido.id"> :pedido_id="pedido.id"
</admin-fila-pedido> :key="pedido.id"
/>
</tbody> </tbody>
</table> </table>
<table class="table is-striped is-bordered"> <table class="table is-striped is-bordered">
@ -23,27 +24,27 @@
</tr> </tr>
<tr> <tr>
<th>Total a recaudar:</th> <th>Total a recaudar:</th>
<td class="has-text-right">$ {{ gdc.total_a_recaudar }}</td> <td class="has-text-right">$ {{ devoluciones_habilitadas ? total_a_recaudar : total_sin_devoluciones }}</td>
</tr> </tr>
<tr> <tr>
<th>Total bonos barriales:</th> <th>Total bonos barriales:</th>
<td class="has-text-right">$ {{ gdc.total_barrial }}</td> <td class="has-text-right">$ {{ total_barrial }}</td>
</tr> </tr>
<tr v-if="$root.devoluciones"> <tr v-if="devoluciones_habilitadas">
<th>Total devoluciones:</th> <th>Total devoluciones:</th>
<td class="has-text-right">- $ {{ gdc.total_devoluciones }}</td> <td class="has-text-right">- $ {{ total_devoluciones }}</td>
</tr> </tr>
<tr> <tr>
<th>Cantidad bonos de transporte:</th> <th>Cantidad bonos de transporte:</th>
<td class="has-text-right">{{ gdc.cantidad_transporte }}</td> <td class="has-text-right">{{ cantidad_transporte }}</td>
</tr> </tr>
<tr> <tr>
<th>Total bonos de transporte:</th> <th>Total bonos de transporte:</th>
<td class="has-text-right">$ {{ gdc.total_transporte }}</td> <td class="has-text-right">$ {{ total_transporte }}</td>
</tr> </tr>
<tr> <tr>
<th>Total a depositar:</th> <th>Total a depositar:</th>
<td class="has-text-right">$ {{ gdc.total_a_transferir }}</td> <td class="has-text-right">$ {{ total_a_transferir }}</td>
</tr> </tr>
</table> </table>
</div> </div>
@ -51,19 +52,26 @@
<script> <script>
import FilaPedido from "./FilaPedido.vue"; import FilaPedido from "./FilaPedido.vue";
import { mapGetters, mapState } from "vuex";
export default { export default {
components: { components: {
FilaPedido FilaPedido
}, },
props: { computed: {
gdc: { ...mapState('admin', [
type: Object, "devoluciones_habilitadas",
required: true "pedidos",
} "total_a_recaudar",
} "total_sin_devoluciones",
"total_barrial",
"total_devoluciones",
"cantidad_transporte",
"total_transporte",
"total_a_transferir",
]),
...mapGetters('admin', ['pedidosAprobados']),
},
} }
</script> </script>
<style> <style></style>
</style>

View file

@ -1,16 +1,15 @@
<template> <template>
<div class="block ml-3 mr-3 is-max-widescreen is-max-desktop"> <div class="block ml-3 mr-3 is-max-widescreen is-max-desktop">
<comunes-tabs-secciones :tabs="tabs" :tabInicial="tabActiva"></comunes-tabs-secciones> <comunes-tabs-secciones :tabs="tabs" :tabInicial="tabActiva"></comunes-tabs-secciones>
<div class="block pb-6" id="pedidos-compras-seccion" <div class="block pb-6" id="pedidos-comisiones-seccion"
:class="seccionActiva === 'pedidos-compras-seccion' ? 'is-active' : 'is-hidden'"> :class="seccionActiva === 'pedidos-comisiones-seccion' ? 'is-active' : 'is-hidden'">
<div class="block" id="pedidos-compras-tabla-y-dropdown"> <div class="block" id="pedidos-comisiones-tabla-y-dropdown">
<compras-dropdown-descargar> <dropdown-descargar/>
</compras-dropdown-descargar>
</div> </div>
</div> </div>
<div class="block pb-6" id="canasta-compras-seccion" <div class="block pb-6" id="canasta-comisiones-seccion"
:class="seccionActiva === 'canasta-compras-seccion' ? 'is-active' : 'is-hidden'"> :class="seccionActiva === 'canasta-comisiones-seccion' ? 'is-active' : 'is-hidden'">
<div class="block" id="canasta-compras-seccion"> <div class="block" id="canasta-comisiones-seccion">
<article class="message is-warning"> <article class="message is-warning">
<div class="message-header"> <div class="message-header">
<p>Formato de la canasta</p> <p>Formato de la canasta</p>
@ -25,7 +24,7 @@
<li> No puede haber "enters" en ninguna celda </li> <li> No puede haber "enters" en ninguna celda </li>
<li> El bono de transporte debe tener tipo 'T' </li> <li> El bono de transporte debe tener tipo 'T' </li>
</ul> </ul>
<a class="has-text-info" href="/compras/canasta/ejemplo">Planilla de ejemplo.</a> <a class="has-text-info" href="/comisiones/canasta/ejemplo">Planilla de ejemplo.</a>
<article class="message is-danger mt-2"> <article class="message is-danger mt-2">
<div class="message-body"> <div class="message-body">
<div class="content"> <div class="content">
@ -37,7 +36,7 @@
</div> </div>
</article> </article>
<div class="buttons is-right"> <div class="buttons is-right">
<compras-canasta-input></compras-canasta-input> <canasta-input/>
</div> </div>
</div> </div>
</div> </div>
@ -50,6 +49,7 @@ import DropdownDescargar from "./DropdownDescargar.vue";
import CanastaInput from "./CanastaInput.vue"; import CanastaInput from "./CanastaInput.vue";
export default { export default {
name: "ComisionesBody",
components: { components: {
TabsSecciones, TabsSecciones,
DropdownDescargar, DropdownDescargar,
@ -57,10 +57,10 @@ export default {
}, },
data() { data() {
return { return {
tabs: [{ id: "pedidos-compras", nombre: "Pedidos" }, tabs: [{ id: "pedidos-comisiones", nombre: "Pedidos" },
{ id: "canasta-compras", nombre: "Canasta" }], { id: "canasta-comisiones", nombre: "Canasta" }],
tabActiva: "pedidos-compras", tabActiva: "pedidos-comisiones",
seccionActiva: "pedidos-compras-seccion", seccionActiva: "pedidos-comisiones-seccion",
archivo: undefined, archivo: undefined,
} }
}, },

View file

@ -14,7 +14,7 @@
</span> </span>
<span class="file-label">Subir canasta</span> <span class="file-label">Subir canasta</span>
</span> </span>
<span class="file-name" v-if="archivo"> <span class="file-name" v-if="cargando">
{{ 'Cargando ' + archivo.nombre }} {{ 'Cargando ' + archivo.nombre }}
</span> </span>
</label> </label>
@ -24,6 +24,7 @@
<script> <script>
import axios from "axios"; import axios from "axios";
import { mapActions } from "vuex";
export default { export default {
name: "CanastaInput", name: "CanastaInput",
@ -34,6 +35,7 @@ export default {
}; };
}, },
methods: { methods: {
...mapActions('ui',["toast"]),
async archivoSubido(event) { async archivoSubido(event) {
const archivo = event.target.files[0]; const archivo = event.target.files[0];
if (archivo && archivo.type === "text/csv") { if (archivo && archivo.type === "text/csv") {
@ -43,20 +45,20 @@ export default {
try { try {
this.cargando = true; this.cargando = true;
const response = await axios.post("/compras/canasta", formData, { const response = await axios.post("/comisiones/canasta", formData, {
headers: { headers: {
"Content-Type": "multipart/form-data", "Content-Type": "multipart/form-data",
}, },
}); });
this.$root.$toast(response.data.message || "Canasta cargada exitosamente"); this.toast({ mensaje: (response.data.message || "Canasta cargada exitosamente") });
} catch (error) { } catch (error) {
this.$root.$toast(error.response?.data?.message || "Hubo errores."); this.toast({ mensaje: (error.response?.data?.message || "Hubo errores.") });
} finally { } finally {
this.cargando = false; this.cargando = false;
this.archivo = null; this.archivo = null;
} }
} else { } else {
this.$root.$toast("La canasta debe ser .CSV") this.toast("La canasta debe ser .CSV")
this.archivo = null; this.archivo = null;
} }
}, },

View file

@ -14,13 +14,13 @@
</div> </div>
<div class="dropdown-menu" id="dropdown-menu" role="menu"> <div class="dropdown-menu" id="dropdown-menu" role="menu">
<div class="dropdown-content"> <div class="dropdown-content">
<a href="/compras/pedidos/descargar" class="dropdown-item"> <a href="/comisiones/pedidos/descargar" class="dropdown-item">
Pedidos por barrio Pedidos por barrio
</a> </a>
<a href="/compras/pedidos/notas" class="dropdown-item"> <a href="/comisiones/pedidos/notas" class="dropdown-item">
Notas por barrio Notas por barrio
</a> </a>
<a href="/compras/pedidos/pdf" class="dropdown-item"> <a href="/comisiones/pedidos/pdf" class="dropdown-item">
Pedidos por barrio en pdf Pedidos por barrio en pdf
</a> </a>
</div> </div>

View file

@ -1,52 +0,0 @@
<template>
<div class="block">
<div class="field">
<label class="label">Usuario</label>
<div class="field">
<div class="control">
<input required class="input" type="text" name="name" placeholder="Usuario">
</div>
</div>
</div>
<div class="field">
<label class="label">Contraseña</label>
<div class="field has-addons">
<div class="control">
<input required class="input" :type="this.passwordType" name="password" placeholder="Contraseña">
</div>
<div class="control">
<a class="button is-dark" @click="togglePassword">
{{ (passwordVisible ? 'Ocultar' : 'Mostrar') + ' contraseña'}}
</a>
</div>
</div>
</div>
<div class="field">
<div class="control">
<input type="submit" class="button is-dark" value="Ingresar"/>
</div>
</div>
</div>
</template>
<script>
export default {
name: "LoginAdmin",
data() {
return {
passwordVisible: false,
passwordType: "password",
}
},
methods: {
togglePassword() {
if (this.passwordVisible) this.passwordType = "password";
else this.passwordType = "text"
this.passwordVisible = !this.passwordVisible
}
}
}
</script>
<style>
</style>

View file

@ -1,51 +0,0 @@
<template>
<div v-show="visible" class="block">
<div class="field">
<label class="label" :class="isAdmin ? 'has-text-white' : ''">Seleccioná tu barrio o grupo de compra</label>
<div class="control">
<div class="select">
<select @change="onGDCSelected" v-model="gdc" name="name">
<option :disabled="isDefaultDisabled==1" value=null>Seleccionar</option>
<option v-for="(gdc, index) in gdcs" :key="index" v-text="gdc.nombre + (isAdmin ? '_admin' : '')"
:name="gdc.nombre + (isAdmin ? '_admin' : '')">
</option>
</select>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
visible: false,
region: null,
gdcs: [],
isDefaultDisabled: 0,
gdc: null,
isAdmin: this.admin == null ? false : this.admin
}
},
mounted() {
Event.$on('region-seleccionada', (region)=> {
this.region = region;
this.fillGDC(region);
this.visible = true;
});
},
methods : {
fillGDC(region) {
axios.get("/api/grupos-de-compra").then(response => {
this.gdcs = response.data[this.region];
});
},
onGDCSelected() {
this.isDefaultDisabled = 1;
Event.$emit("gdc-seleccionado",this.gdc);
}
},
props: {'admin': Boolean}
}
</script>

View file

@ -0,0 +1,90 @@
<script>
import { mapActions, mapMutations, mapState } from "vuex";
export default {
name: "InfoTags",
computed: {
...mapState("ui", ["canasta_actual", "show_tags"])
},
methods: {
...mapActions("ui", ["getCanastaActual"]),
...mapMutations("ui", ["toggleTags"])
},
async mounted() {
await this.getCanastaActual();
this.fechaCanasta = new Date(this.canasta_actual.fecha)
.toLocaleDateString('es-UY');
this.nombreCanasta = this.canasta_actual.nombre;
},
data() {
return {
nombreCanasta: "",
fechaCanasta: "",
}
},
}
</script>
<template>
<div>
<div v-if="!show_tags" class="info-tab" @click="toggleTags(true)">
<button class="button is-borderless" type="button">
<span class="icon">
<i class="fas fa-info-circle"></i>
</span>
</button>
</div>
<div v-if="show_tags" class="box sticky-tags">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="tag">Canasta</span>
<span class="tag is-danger">{{ nombreCanasta }}</span>
</div>
</div>
<div class="control">
<div class="tags has-addons">
<span class="tag">Actualizada</span>
<span class="tag is-danger">{{ fechaCanasta }}</span>
</div>
</div>
<button class="delete" type="button" @click="toggleTags(true)"></button>
</div>
</div>
</div>
</template>
<style scoped>
.sticky-tags {
position: fixed;
bottom: 1rem;
right: 1rem;
z-index: 50;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
max-width: 90vw;
}
.is-borderless {
border: 0;
background: none;
box-shadow: none;
}
.info-tab {
position: fixed;
right: -0.1rem;
bottom: 1rem;
z-index: 51;
transform: translateX(10%);
transition: transform 0.3s ease;
}
.info-tab button {
border-top-left-radius: 9999px;
border-bottom-left-radius: 9999px;
background-color: white;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 0.5rem 0.75rem;
}
</style>

View file

@ -4,15 +4,16 @@
<a class="navbar-item" href="https://mps.org.uy"> <a class="navbar-item" href="https://mps.org.uy">
<img src="/assets/logoMPS.png" height="28"> <img src="/assets/logoMPS.png" height="28">
</a> </a>
<!-- Styles nombre del barrio--> <div class="navbar-item hide-below-1024" v-if="pedidoDefinido">
<p class="navbar-item hide-below-1024"> <p>{{ `Barrio: ${grupo_de_compra.nombre} - Núcleo: ${nombre}` }}</p>
<slot name="gdc"></slot> </div>
</p> <chismosa-dropdown
<p class="navbar-item"> v-if="pedidoDefinido"
<slot name="subpedido"></slot> class="hide-above-1023"
</p> ariaControls="mobile"
<pedidos-chismosa-dropdown v-if="this.$root.pedido != null" class="hide-above-1023" id="mobile"></pedidos-chismosa-dropdown> />
<a role="button" class="navbar-burger" :class="{'is-active':burgerActiva}" aria-label="menu" aria-expanded="false" data-target="nav-bar" @click="toggleBurger"> <a role="button" class="navbar-burger" :class="{'is-active':burgerActiva}" aria-label="menu"
aria-expanded="false" data-target="nav-bar" @click="toggleBurger">
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
@ -20,20 +21,25 @@
</div> </div>
<div class="navbar-menu" :class="{'is-active':burgerActiva}"> <div class="navbar-menu" :class="{'is-active':burgerActiva}">
<div class="navbar-end"> <div class="navbar-end">
<div v-if="this.$root.pedido != null" class="navbar-item field has-addons mt-2 mr-3"> <div v-if="pedidoDefinido" class="navbar-item field has-addons mt-1 mr-3 mb-1">
<a class="button is-small has-text-dark-grey" @click.capture="buscar"> <a class="button is-small has-text-dark-grey" @click.capture="buscar">
<span class="icon"> <span class="icon">
<i class="fas fa-search"></i> <i class="fas fa-search"></i>
</span> </span>
</a> </a>
<input class="input is-small" type="text" placeholder="Harina" v-model="searchString" @keyup.enter="buscar" > <input class="input is-small" type="text" placeholder="Harina" v-model="searchString"
@keyup.enter="buscar">
</div> </div>
<pedidos-chismosa-dropdown v-if="this.$root.pedido != null" class="hide-below-1024" id="wide"></pedidos-chismosa-dropdown> <chismosa-dropdown
v-if="pedidoDefinido"
class="hide-below-1024"
ariaControls="wide">
</chismosa-dropdown>
<div class="block navbar-item"> <div class="block navbar-item">
<a onclick="event.preventDefault(); document.getElementById('logout-form').submit();" class="text-a"> <a onclick="event.preventDefault(); document.getElementById('logout-form').submit();"
class="text-a">
Cerrar sesión Cerrar sesión
</a> </a>
<slot name="logout-form"></slot>
</div> </div>
</div> </div>
</div> </div>
@ -42,22 +48,35 @@
<script> <script>
import ChismosaDropdown from '../pedidos/ChismosaDropdown.vue'; import ChismosaDropdown from '../pedidos/ChismosaDropdown.vue';
import { mapActions, mapGetters, mapMutations, mapState } from "vuex";
export default { export default {
components: { ChismosaDropdown }, components: { ChismosaDropdown },
data() { data() {
return { return {
burgerActiva: false, burgerActiva: false,
searchString: "", searchString: "",
nombreCanasta: "",
fechaCanasta: "",
} }
}, },
computed: {
...mapGetters('pedido', ["pedidoDefinido"]),
...mapState('pedido', ["nombre"]),
...mapState('pedido', ["grupo_de_compra"]),
},
methods: { methods: {
...mapActions('productos', ["filtrarProductos"]),
...mapMutations('ui', ["addMiga", "popUltimaBusqueda"]),
toggleBurger() { toggleBurger() {
this.burgerActiva = !this.burgerActiva this.burgerActiva = !this.burgerActiva
}, },
buscar() { buscar() {
if (this.burgerActiva) this.toggleBurger() if (this.burgerActiva)
Event.$emit("migas-setear-como-inicio", this.$rootMiga) this.toggleBurger();
Event.$emit("filtrar-productos",'nombre',this.searchString) this.filtrarProductos({ filtro: "nombre", valor: this.searchString });
this.popUltimaBusqueda();
this.addMiga({ nombre: this.searchString });
} }
}, },
}; };

View file

@ -1,38 +0,0 @@
<template>
<div class="block">
<div class="field">
<label class="label" :class="whiteText ? 'has-text-white' : ''">Seleccioná tu región</label>
<div class="control">
<div class="select">
<select @change="onRegionSelected" v-model="region">
<option :disabled="isDefaultDisabled===1" value=null>Seleccionar</option>
<option v-for="(region, index) in regiones" :key="index" v-text="region" :name="region"></option>
</select>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
regiones: [],
isDefaultDisabled: 0,
region: null,
whiteText: this.admin == null ? false : this.admin
}
},
mounted() {
axios.get("/api/regiones").then(response => this.regiones = response.data);
},
methods: {
onRegionSelected() {
this.isDefaultDisabled = 1;
Event.$emit("region-seleccionada",this.region);
}
},
props: {'admin': Boolean}
}
</script>

View file

@ -0,0 +1,48 @@
<script>
import { mapGetters } from "vuex";
export default {
name: "LoginDropdown",
computed: {
...mapGetters("login", ["opcionesLogin", "estilos"])
},
data() {
return {
dropdownActivo: false
};
},
}
</script>
<template>
<div class="buttons is-right">
<div class="dropdown" :class="{'is-active': dropdownActivo}" @mouseleave="dropdownActivo = false">
<div class="dropdown-trigger">
<button class="button"
aria-haspopup="true"
aria-controls="dropdown-menu"
type="button"
@click="dropdownActivo = !dropdownActivo">
<span class="icon is-small">
<i class="fa fa-solid fa-user-check"></i>
</span>
<span>Cambiar login</span>
<span class="icon is-small">
<i class="fas fa-angle-down" aria-hidden="true"></i>
</span>
</button>
</div>
<div class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a class="dropdown-item" v-for="opcion in opcionesLogin" :href="opcion.href">
{{ opcion.nombre }}
</a>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,24 @@
<script>
import { mapGetters } from "vuex";
import BarrioLogin from "./input/BarrioLogin.vue";
import UserLogin from "./input/UserLogin.vue";
export default {
name: "LoginInput",
components: { UserLogin, BarrioLogin },
computed: {
...mapGetters("login", ["urlRol"]),
},
}
</script>
<template>
<div>
<user-login v-if="urlRol === 'comisiones'"/>
<barrio-login v-else/>
</div>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,33 @@
<script>
import { mapGetters } from "vuex";
import LoginDropdown from "./LoginDropdown.vue";
export default {
name: "LoginTitulos",
components: { LoginDropdown },
computed: {
...mapGetters("login", ["textos", "estilos"]),
}
};
</script>
<template>
<div class="columns">
<div class="column">
<div class="block">
<h1 class="title" :class="estilos.texto">{{ textos.titulo }}</h1>
<p class="subtitle" :class="estilos.texto">
{{ `Bienvenidx a la ${textos.subtitlo} del ` }}
<strong :class="estilos.texto">Mercado Popular de Subistencia</strong>
</p>
</div>
</div>
<div class="column is-2 is-hidden-mobile">
<login-dropdown/>
</div>
</div>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,26 @@
<script>
import { defineComponent } from "vue";
import RegionSelect from "./barrio/RegionSelect.vue";
import BarrioSelect from "./barrio/BarrioSelect.vue";
import { mapActions } from "vuex";
export default defineComponent({
components: { BarrioSelect, RegionSelect },
methods: {
...mapActions("login", ["getRegiones"]),
},
async mounted() {
await this.getRegiones();
},
})
</script>
<template>
<div class="block">
<region-select/>
<barrio-select/>
</div>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,65 @@
<template>
<div class="block">
<div class="field">
<label class="label" :class="estilos.texto">
{{ textos.password }}
</label>
<div class="field has-addons">
<div class="control">
<input required
class="input"
:type="passwordType"
name="password"
:placeholder="textos.password">
</div>
<div class="control">
<a class="button" :class="estilos.botones" @click="togglePassword">
{{ textoBotonPassword }}
</a>
</div>
</div>
<p class="help"
:class="estilos.texto">
{{ textos.ayuda }}
</p>
</div>
<div class="field">
<div class="control">
<input type="submit" class="button" :class="estilos.botones" value="Log in"/>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: 'PasswordInput',
data() {
return {
passwordVisible: false,
passwordType: "password",
}
},
computed: {
...mapGetters("login", ["textos", "estilos"]),
textoBotonPassword() {
return `${this.passwordVisible ? "Ocultar" : "Mostrar"} contraseña`;
}
},
methods: {
togglePassword() {
this.passwordType = this.passwordVisible ? "password" : "text";
this.passwordVisible = !this.passwordVisible;
}
}
}
</script>
<style>
.help {
font-size: 1rem;
}
</style>

View file

@ -0,0 +1,19 @@
<script>
import { defineComponent } from "vue";
import PasswordInput from "./PasswordInput.vue";
import UserInput from "./user/UserInput.vue";
export default defineComponent({
components: { UserInput, PasswordInput }
})
</script>
<template>
<div class="block">
<user-input/>
<password-input/>
</div>
</template>
<style scoped>
</style>

View file

@ -0,0 +1,47 @@
<template>
<div v-if="region_elegida" class="block">
<div class="field">
<label class="label" :class="estilos.texto">
Seleccioná tu barrio o grupo de compra
</label>
<div class="control">
<div class="select">
<select v-model="barrio"
@change="selectGrupoDeCompra({ grupo_de_compra: barrio })">
<option :disabled="grupo_de_compra_elegido" value=null>
Seleccionar
</option>
<option v-for="(gdc, index) in grupos_de_compra"
:key="index"
v-text="gdc.nombre"
:name="gdc.nombre">
</option>
</select>
</div>
</div>
</div>
<password-input v-if="grupo_de_compra_elegido"/>
<input readonly v-model="nombre" type="hidden" name="name">
</div>
</template>
<script>
import { mapGetters, mapMutations, mapState } from "vuex";
import PasswordInput from "../PasswordInput.vue";
export default {
name: 'BarrioSelect',
components: { PasswordInput },
methods: {
...mapMutations("login", ["selectGrupoDeCompra"]),
},
computed: {
...mapState("login", ["region_elegida", "grupos_de_compra", "grupo_de_compra_elegido"]),
...mapGetters("login", ["urlRol", "estilos", "nombre"]),
},
data() {
return {
barrio: null,
};
},
}
</script>

View file

@ -0,0 +1,43 @@
<template>
<div class="field">
<label class="label" :class="estilos.texto">
Seleccioná tu región
</label>
<div class="control">
<div class="select">
<select @change="selectRegion({ region })" v-model="region">
<option :disabled="region_elegida" value=null>
Seleccionar
</option>
<option v-for="(region, index) in regiones"
:key="index"
v-text="region"
:name="region">
</option>
</select>
</div>
</div>
</div>
</template>
<script>
import {mapActions, mapGetters, mapState} from "vuex";
export default {
name: 'RegionSelect',
async mounted() {
await this.getRegiones();
},
data() {
return {
region: null,
};
},
methods: {
...mapActions("login", ["getRegiones", "selectRegion"])
},
computed: {
...mapState("login", ["regiones", "region_elegida"]),
...mapGetters("login", ["estilos"]),
}
}
</script>

View file

@ -0,0 +1,20 @@
<script>
export default {
name: "UserInput",
}
</script>
<template>
<div class="field">
<label class="label">Usuario</label>
<div class="field has-addons">
<div class="control">
<input required class="input" type="text" name="name" placeholder="Usuario">
</div>
</div>
</div>
</template>
<style scoped>
</style>

View file

@ -1,22 +1,41 @@
<template> <template>
<div class="columns ml-3 mr-3"> <div id="pedidos-body">
<pedidos-categorias-container :class="chismosaActiva ? 'hide-below-1024' : ''"></pedidos-categorias-container> <pedido-select v-if="!pedidoDefinido"/>
<pedidos-productos-container :class="chismosaActiva ? 'hide-below-1024' : ''"></pedidos-productos-container> <div v-else>
<pedidos-chismosa v-show="chismosaActiva"></pedidos-chismosa> <nav-migas/>
<div class="columns">
<div class="column" :class="{ 'is-two-thirds-desktop is-hidden-touch': show_chismosa }">
<cartel-pedido-aprobado/>
<canasta/>
</div>
<div class="column is-full-touch" v-if="show_chismosa">
<chismosa/>
</div>
</div>
</div>
</div> </div>
</template> </template>
<script> <script>
import { mapActions, mapGetters, mapState } from "vuex";
import CartelPedidoAprobado from "./CartelPedidoAprobado.vue";
import PedidoSelect from "./PedidoSelect.vue";
import Canasta from "./Canasta.vue";
import NavMigas from "./NavMigas.vue";
import Chismosa from "./Chismosa.vue";
export default { export default {
data() { name: "PedidosBody",
return { components: { Chismosa, NavMigas, CartelPedidoAprobado, PedidoSelect, Canasta },
chismosaActiva: false, computed: {
...mapGetters('pedido', ["pedidoDefinido"]),
...mapState('ui', ["show_chismosa"]),
},
methods: {
...mapActions('productos', ["init"]),
},
async mounted() {
await this.init();
} }
},
mounted() {
Event.$on('toggle-chismosa', (activa) => {
this.chismosaActiva = activa;
});
},
} }
</script> </script>

View file

@ -0,0 +1,34 @@
<script >
import { defineComponent } from "vue";
import { mapMutations, mapState } from "vuex";
import CategoriasContainer from "./CategoriasContainer.vue";
import ProductosContainer from "./ProductosContainer.vue";
import Chismosa from "./Chismosa.vue";
import DevolucionesModal from "./DevolucionesModal.vue";
export default defineComponent({
components: { DevolucionesModal, CategoriasContainer, ProductosContainer, Chismosa },
computed: {
...mapState('ui', ["show_chismosa", "show_devoluciones", "tags_interactuada"])
},
methods: {
...mapMutations("ui", ["toggleTags"]),
},
mounted() {
if (!this.tags_interactuada)
this.toggleTags(false);
}
})
</script>
<template>
<div class="columns ml-3 mr-3" v-else>
<categorias-container :class="show_chismosa ? 'hide-below-1024' : ''"/>
<productos-container :class="show_chismosa ? 'hide-below-1024' : ''"/>
<devoluciones-modal v-show="show_devoluciones"/>
</div>
</template>
<style scoped>
</style>

View file

@ -1,30 +1,17 @@
<template> <template>
<div v-show="aprobado" class="notification is-warning has-text-centered"> <div v-if="aprobado" class="notification is-warning has-text-centered">
Tu pedido fue <strong>aprobado</strong>, por lo que no puede ser modificado Tu pedido fue <strong>aprobado</strong>, por lo que no puede ser modificado
</div> </div>
</template> </template>
<script> <script>
import { mapState } from "vuex";
export default { export default {
data() { name: 'CartelPedidoAprobado',
return { computed: {
aprobado: false, ...mapState('pedido',["aprobado"]),
} }
},
mounted() {
Event.$on('pedido-actualizado', this.actualizarEstado);
if (this.$root.pedido != null) {
this.actualizarEstado();
}
},
methods: {
pedidoAprobado: function() {
return this.$root.pedido.aprobado;
},
actualizarEstado: function() {
this.aprobado = this.pedidoAprobado();
},
},
} }
</script> </script>

View file

@ -1,41 +1,92 @@
<template> <template>
<div v-show="visible" class="column"> <div v-show="visible" class="column">
<div class="columns is-multiline is-mobile"> <div ref="categorias"
<div v-for="(categoria,i) in categorias" :key="i" class="block column is-one-quarter-desktop is-one-third-tablet is-half-mobile"> class="columns is-multiline is-mobile"
<div @click.capture="seleccionarCategoria(categoria)" class="card" style="height:100%" > :class="{ 'align-last-left': isLastRowIncomplete }">
<div v-for="(categoria,i) in categorias" :key="i"
:class="{ 'is-3-desktop is-2-fullhd': !show_chismosa }"
class="column is-4-tablet is-6-mobile hover-dedito">
<div @click.capture="seleccionar(categoria)" class="card" style="height: 100%">
<div class="card-content"> <div class="card-content">
<div class="media"> <div class="media">
<div class="media-content" style="overflow: hidden"> <div class="media-content" style="overflow: hidden">
<p class="title is-6" v-text="categoria"></p> <p class="title is-size-7-mobile is-size-6-tablet has-text-centered" v-text="categoria"></p>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div><!-- END CARD -->
</div><!-- END BLOCK COLUMN -->
</div><!-- END COLUMNS -->
</div><!-- END CONTAINER -->
</template> </template>
<script> <script>
import { mapActions, mapMutations, mapState } from "vuex";
export default { export default {
name: 'CategoriasContainer',
computed: {
...mapState('productos', ["categorias", "filtro"]),
...mapState('ui', ["show_chismosa"]),
visible() {
return this.filtro === null;
}
},
methods: {
...mapActions('productos', ["seleccionarCategoria"]),
...mapMutations('ui', ["addMiga"]),
seleccionar(categoria) {
this.seleccionarCategoria({ categoria: categoria });
this.addMiga({ nombre: categoria, action: "productos/seleccionarCategoria", arguments: { categoria: categoria }});
},
checkIfLastRowIncomplete() {
this.$nextTick(() => {
const wrapper = this.$refs.categorias;
if (!wrapper) return;
const columns = wrapper.querySelectorAll('.column');
if (columns.length === 0) {
this.isLastRowIncomplete = false;
return;
}
const firstRowTop = columns[0].offsetTop;
let firstRowCount = 0;
columns.forEach(col => {
if (col.offsetTop === firstRowTop) firstRowCount++;
});
this.isLastRowIncomplete = this.categorias.length % firstRowCount !== 0;
});
}
},
data() { data() {
return { return {
categorias: null, isLastRowIncomplete: false
visible: true
} }
}, },
mounted() { mounted() {
axios.get("/api/categorias").then(response => { this.checkIfLastRowIncomplete();
this.categorias = response.data; window.addEventListener('resize', this.checkIfLastRowIncomplete);
}); },
Event.$emit("migas-setear-como-inicio", this.$rootMiga); beforeDestroy() {
Event.$on("filtrar-productos", (_) => this.visible = false) window.removeEventListener('resize', this.checkIfLastRowIncomplete);
}, },
methods: {
seleccionarCategoria(categoria) {
this.visible = false;
Event.$emit("filtrar-productos",'categoria',categoria);
}
}
} }
</script> </script>
<style>
.hover-dedito {
cursor: pointer;
}
.columns.align-last-left {
justify-content: flex-start !important;
}
.columns.align-last-left > .column:last-child:nth-child(3n),
.columns.align-last-left > .column:last-child:nth-child(2n),
.columns.align-last-left > .column:last-child {
margin-right: auto;
}
.title {
word-break: keep-all;
}
</style>

View file

@ -1,6 +1,5 @@
<template> <template>
<div class="column is-one-third"> <div class="fixed-right mr-3 ml-3">
<div class="fixed-right">
<table v-show="mostrar_tabla" class="table is-striped is-bordered tabla-chismosa is-narrow"> <table v-show="mostrar_tabla" class="table is-striped is-bordered tabla-chismosa is-narrow">
<thead> <thead>
<tr> <tr>
@ -12,20 +11,20 @@
<tfoot> <tfoot>
<tr> <tr>
<th><abbr title="Bonos de Transporte">B. Transporte</abbr></th> <th><abbr title="Bonos de Transporte">B. Transporte</abbr></th>
<th class="has-text-right">{{ cantidad_bonos_transporte }}</th> <th class="has-text-right">{{ cantidad_transporte }}</th>
<th class="has-text-right">{{ total_bonos_transporte }}</th> <th class="has-text-right">{{ total_transporte }}</th>
</tr> </tr>
<tr v-if="this.$root.devoluciones"> <tr v-if="grupo_de_compra.devoluciones_habilitadas && !aprobado">
<th><p>Devoluciones</p></th> <th><p>Devoluciones</p></th>
<td> <td>
<abbr :title="notas_devoluciones">{{ notas_devoluciones_abbr }}</abbr> <abbr :title="devoluciones_notas">{{ notas_abreviadas }}</abbr>
<button @click.capture="modificarDevoluciones()" class="button is-warning is-small"> <button @click.capture="toggleDevoluciones()" class="button is-warning is-small">
<span class="icon"> <span class="icon">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</span> </span>
</button> </button>
</td> </td>
<th class="has-text-right">-{{ devoluciones }}</th> <th class="has-text-right">-{{ devoluciones_total }}</th>
</tr> </tr>
<tr> <tr>
<th>Total total</th> <th>Total total</th>
@ -34,57 +33,41 @@
</tr> </tr>
</tfoot> </tfoot>
<tbody> <tbody>
<pedidos-producto-row v-for="producto in productos" :producto="producto" :key="producto.id"></pedidos-producto-row> <producto-row v-for="producto in productos" :producto="producto" :key="producto.id"/>
</tbody> </tbody>
</table> </table>
<p class="has-text-centered" v-show="!mostrar_tabla"> <p class="has-text-centered" v-show="!mostrar_tabla">
Compa, todavía no agregaste nada a la chismosa. Compa, todavía no agregaste nada a la chismosa.
</p> </p>
</div> </div>
</div>
</template> </template>
<script> <script>
import ProductoRow from "./ProductoRow.vue";
import { mapMutations, mapState } from "vuex";
export default { export default {
data() { components: { ProductoRow },
return { computed: {
mostrar_tabla: false, ...mapState('pedido',[
cantidad_bonos_transporte: 0, "grupo_de_compra",
total_bonos_transporte: 0, "productos",
devoluciones: 0, "total",
notas_devoluciones: "", "total_transporte",
notas_devoluciones_abbr: "", "cantidad_transporte",
total: 0, "devoluciones_total",
productos: [], "devoluciones_notas",
} "aprobado"
]),
notas_abreviadas() {
return this.devoluciones_notas.substring(0, 15) + (this.devoluciones_notas.length > 15 ? "..." : "");
},
mostrar_tabla() {
return this.productos?.length !== 0;
}, },
mounted() {
Event.$on('pedido-actualizado', this.pedidoActualizado);
Event.$on('toggle-chismosa', this.pedidoActualizado);
}, },
methods: { methods: {
pedidoActualizado: function() { ...mapMutations('ui',["toggleDevoluciones"]),
this.mostrar_tabla = this.$root.productos.length > 0;
this.cantidad_bonos_transporte = this.cantidadBonosDeTransporte();
this.total_bonos_transporte = this.totalBonosDeTransporte();
this.devoluciones = this.$root.pedido.devoluciones_total;
this.notas_devoluciones = this.$root.pedido.devoluciones_notas;
this.notas_devoluciones_abbr = this.notas_devoluciones.substring(0, 15);
if (this.notas_devoluciones.length > 15) {
this.notas_devoluciones_abbr += "...";
}
this.total = this.$root.pedido.total;
this.productos = this.$root.productos;
},
modificarDevoluciones: function() {
Event.$emit("modificar-devoluciones");
},
cantidadBonosDeTransporte: function() {
return this.$root.pedido.cantidad_transporte;
},
totalBonosDeTransporte: function() {
return this.$root.pedido.total_transporte
},
}, },
} }
</script> </script>
@ -93,10 +76,10 @@
.tabla-chismosa { .tabla-chismosa {
width: 100%; width: 100%;
} }
.fixed-right { .fixed-right {
position: fixed; position: fixed;
overflow-y: auto; overflow-y: auto;
max-height: 81vh; max-height: 81vh;
margin-right: 20px;
} }
</style> </style>

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="dropdown is-right navbar-item" :class="{'is-active':activa}"> <div class="dropdown is-right navbar-item" :class="{'is-active': show_chismosa}">
<div class="dropdown-trigger"> <div class="dropdown-trigger">
<a class="text-a" aria-haspopup="true" :aria-controls="id" @click.capture="toggle"> <a class="text-a" aria-haspopup="true" :aria-controls="ariaControls" @click.capture="toggleChismosa">
<span class="icon is-small mr-1"> <span class="icon is-small mr-1">
<img src="/assets/chismosa.png"> <img src="/assets/chismosa.png">
</span> </span>
@ -13,33 +13,23 @@
<script> <script>
import Chismosa from './Chismosa.vue' import Chismosa from './Chismosa.vue'
import { mapMutations, mapState } from "vuex";
export default { export default {
components: { components: {
Chismosa Chismosa
}, },
props: { props: {
id: { ariaControls: {
type: String, type: String,
required: true required: true
} }
}, },
data() { computed: {
return { ...mapState('pedido',["total"]),
activa: false, ...mapState('ui',["show_chismosa"]),
total: 0,
}
},
mounted() {
Event.$on('pedido-actualizado', this.actualizar);
}, },
methods: { methods: {
toggle() { ...mapMutations('ui',["toggleChismosa"]),
this.activa = !this.activa;
Event.$emit("toggle-chismosa", this.activa);
},
actualizar() {
this.total = this.$root.pedido.total;
},
}, },
} }
</script> </script>

View file

@ -1,67 +1,71 @@
<template> <template>
<div v-bind:class="visible ? 'is-active modal' : 'modal'"> <div :class="show_devoluciones ? 'is-active modal' : 'modal'">
<div class="modal-background"></div> <div class="modal-background"></div>
<div class="modal-card"> <div class="modal-card">
<header class="modal-card-head"> <header class="modal-card-head">
<p class="modal-card-title">Devoluciones</p> <p class="modal-card-title">Devoluciones</p>
<button class="delete" aria-label="close" @click.capture="cerrar"></button> <button class="delete" aria-label="close" @click.capture="toggleDevoluciones()"></button>
</header> </header>
<section class="modal-card-body"> <section class="modal-card-body">
<div class="field has-addons is-centered is-thin-centered"> <div class="field has-addons is-centered is-thin-centered">
<p class="control"> <p class="control">
Total: Total:
<input id="total" class="input" type="number" v-model="total" style="text-align: center"> <input id="totalControl" class="input" type="number" v-model="totalControl"
style="text-align: center">
</p> </p>
</div> </div>
<div class="field has-addons is-centered is-thin-centered"> <div class="field has-addons is-centered is-thin-centered">
<p class="control"> <p class="control">
Notas: Notas:
<input id="notas" class="input" type="text" v-model.text="notas"> <input id="notasControl" class="input" type="text" v-model.text="notasControl">
</p> </p>
</div> </div>
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<button class="button is-success" @click="modificar">Aceptar</button> <button class="button is-success" @click="modificar">Aceptar</button>
<button class="button" @click.capture="cerrar">Cancelar</button> <button class="button" @click.capture="toggleDevoluciones()">Cancelar</button>
</footer> </footer>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapActions, mapMutations, mapState } from "vuex";
export default { export default {
name: 'DevolucionesModal',
data() { data() {
return { return {
visible: false, totalControl: 0,
total: 0, notasControl: "",
notas: "",
}
},
computed: {
miga: function() {
return {
nombre: "Devoluciones",
href: "#devoluciones",
}
},
},
methods: {
cerrar() {
this.visible = false;
Event.$emit("migas-pop");
},
modificar() {
Event.$emit('sync-devoluciones', this.total, this.notas);
this.cerrar();
} }
}, },
mounted() { mounted() {
Event.$on('modificar-devoluciones', () => { this.actualizar();
this.visible = true; },
this.total = this.$root.pedido.devoluciones_total; watch: {
this.notas = this.$root.pedido.devoluciones_notas; cantidadEnChismosa() {
Event.$emit("migas-agregar", this.miga); this.actualizar();
}); },
notasEnChismosa() {
this.actualizar();
}
},
computed: {
...mapState('ui', ["show_devoluciones"]),
...mapState('pedido', ["devoluciones_total", "devoluciones_notas"])
},
methods: {
...mapMutations('ui', ["toggleDevoluciones"]),
...mapActions('pedido', ["modificarDevoluciones"]),
modificar() {
this.modificarDevoluciones({ monto: this.totalControl, notas: this.notasControl });
this.toggleDevoluciones();
},
actualizar() {
this.totalControl = this.devoluciones_total;
this.notasControl = this.devoluciones_notas;
},
}, },
} }
</script> </script>

View file

@ -1,55 +0,0 @@
<template>
<div v-show="visible" class="block">
<div class="field">
<label class="label">Contraseña del barrio</label>
<div class="field has-addons">
<div class="control">
<input required class="input" :type="this.passwordType" name="password" placeholder="Contraseña del barrio">
</div>
<div class="control">
<a class="button is-info" @click="togglePassword">
{{ (passwordVisible ? 'Ocultar' : 'Mostrar') + ' contraseña'}}
</a>
</div>
</div>
<p class="help">Si no la sabés, consultá a tus compañerxs.</p>
</div>
<div class="field">
<div class="control">
<input type="submit" class="button is-success" value="Ingresar"/>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
visible: false,
gdc: this.$root.gdc,
passwordVisible: false,
passwordType: "password",
}
},
mounted() {
Event.$on('gdc-seleccionado', (gdc) => {
this.$root.gdc = gdc;
this.visible = true;
});
},
methods: {
togglePassword() {
if (this.passwordVisible) this.passwordType = "password";
else this.passwordType = "text"
this.passwordVisible = !this.passwordVisible
}
}
}
</script>
<style>
.help {
font-size: 1rem;
}
</style>

View file

@ -1,44 +1,38 @@
<template> <template>
<nav class="breadcrumb is-centered has-background-danger-light is-fixed-top" aria-label="breadcrumbs" v-show="visible"> <nav class="breadcrumb is-centered has-background-danger-light is-fixed-top"
aria-label="breadcrumbs" v-show="visible">
<ul class="mt-4"> <ul class="mt-4">
<li v-for="(miga, i) in migas" :key="i" :class="{'is-active': i == migaActiva}"> <li v-for="(miga, i) in migas" :key="i" :class="{'is-active': i === migaActiva}">
<a :href="miga.href" v-text="miga.nombre" <a @click="clickMiga({ miga: miga })"
:class="{'has-text-danger': i != migaActiva}"></a> v-text="miga.nombre"
:class="{'has-text-danger': i !== migaActiva}">
</a>
</li> </li>
</ul> </ul>
</nav> </nav>
</template> </template>
<script> <script>
import { mapActions, mapMutations, mapState } from "vuex";
export default { export default {
data() { methods: {
return { ...mapActions('productos', ["getProductos"]),
migas: [] ...mapActions('ui', ["clickMiga"]),
} ...mapMutations('ui', ["addMiga"]),
}, },
computed: { computed: {
visible: function() { ...mapState('ui', ["migas"]),
return this.migas.length > 0 visible() {
return this.migas.length > 0;
},
migaActiva() {
return this.migas.length - 1;
}, },
migaActiva: function() {
return this.migas.length-1
}
}, },
mounted() { mounted() {
Event.$on('migas-setear-como-inicio', (miga) => { this.addMiga({ nombre: 'Categorias', action: 'productos/getProductos' });
this.migas = []; },
this.migas.push(miga);
});
Event.$on('migas-agregar', (miga) => {
this.migas.push(miga);
});
Event.$on('migas-reset', () => {
this.migas = [];
});
Event.$on('migas-pop', () => {
this.migas.pop();
});
}
} }
</script> </script>
@ -47,8 +41,8 @@ nav.breadcrumb.is-fixed-top {
position: fixed; position: fixed;
left: 0; left: 0;
right: 0; right: 0;
top: 3.25rem; top: 2.25rem;
height: 2.75rem;
z-index: 5; z-index: 5;
padding: 0.5rem;
} }
</style> </style>

View file

@ -0,0 +1,121 @@
<template>
<section class="section">
<div id="root" class="container">
<h1 class="title">
Pedidos MPS
</h1>
<p class="subtitle">
Bienvenidx a la aplicación de pedidos del
<strong>Mercado Popular de Subsistencia</strong>
</p>
<div>
<label class="label">Escribí el nombre de tu familia o grupo de convivencia</label>
<div class="columns">
<div class="column is-two-thirds">
<div class="field">
<div class="control">
<input class="input" @input="onType" v-model="searchString"/>
</div>
<p class="help">
Debe ser clarx para que tus compas del barrio te identifiquen.
</p>
</div>
</div>
<div class="column is-one-third buttons">
<button class="button is-danger" v-if="!deshabilitado" @click="submit()">
Crear nuevo pedido
</button>
</div>
</div>
<div v-if="pedidos.length" class="block">
<label class="label">
Si ya comenzaste a hacer tu pedido este mes, elegilo en esta lista:
</label>
<p class="help">
Podés seguir escribiendo en el campo de arriba para refinar la búsqueda.
</p>
<div class="columns is-mobile"
v-for="(subpedidoExistente, index) in pedidos"
:class="{'has-background-grey-lighter': index % 2}"
:key="index">
<div class="column is-half-mobile is-two-thirds-desktop is-two-thirds-tablet">
<p class="nombre">
{{ subpedidoExistente.nombre }}
</p>
</div>
<div class="buttons column is-half-mobile is-one-third-desktop is-one-third-tablet">
<button class="button is-danger" @click="submit(subpedidoExistente)">
Continuar pedido
</button>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script>
import { mapActions, mapMutations, mapState } from "vuex";
import axios from "axios";
export default {
name: 'PedidoSelect',
async mounted() {
this.toggleTags(false);
await this.getGrupoDeCompra();
const sesion = await axios.get("/pedido/sesion");
if (sesion.data.id)
await this.elegirPedido({ pedido_id: sesion.data.id });
},
data() {
return {
pedidos: [],
searchString: null,
}
},
computed: {
...mapState('pedido', ["grupo_de_compra"]),
deshabilitado() {
return !this.searchString?.trim()
|| this.pedidos.some(p => p.nombre.toLowerCase() === this.searchString.toLowerCase())
}
},
methods: {
...mapActions('pedido', ["getGrupoDeCompra", "crearPedido", "elegirPedido"]),
...mapMutations("ui", ["toggleTags"]),
async getPedidos(nombre) {
const response = await axios.get('/api/subpedidos/',{
params: {
nombre: nombre,
grupo_de_compra: this.grupo_de_compra.id
}
});
this.pedidos = response.data;
},
onType() {
if (!this.searchString)
this.pedidos = [];
else
this.getPedidos(this.searchString);
},
async submit(pedido) {
if (pedido)
await this.elegirPedido({ pedido_id: pedido.id });
else
await this.crearPedido({
nombre: this.searchString,
grupo_de_compra_id: this.grupo_de_compra.id
});
},
}
}
</script>
<style>
.nombre {
padding-top: calc(.5em - 1px);
margin-bottom: .5rem
}
</style>

View file

@ -1,35 +1,42 @@
<template> <template>
<div> <div class="is-justify-content-center">
<div class="field has-addons contador"> <div class="field has-addons is-justify-content-center contador">
<div class="control"> <div class="control">
<button class="button is-small" @click.capture="decrementar();"> <button class="button is-small" :disabled="cantidadControl < 1" v-if="!aprobado" @click.capture="decrementar();">
<i class="fa fa-solid fa-minus"></i> <i class="fa fa-solid fa-minus"></i>
</button> </button>
</div> </div>
<div class="control"> <div class="control">
<input id="cantidad" v-model="cantidad" class="input is-small" type="number" style="text-align: center"> <input id="cantidad"
v-model="cantidadControl"
class="input is-small"
type="number"
style="text-align: center"
:readonly="aprobado">
</div> </div>
<div class="control" @click="incrementar();"> <div class="control">
<button class="button is-small"> <button class="button is-small" v-if="!aprobado" @click="incrementar();">
<i class="fa fa-solid fa-plus"></i> <i class="fa fa-solid fa-plus"></i>
</button> </button>
</div> </div>
<button :disabled="disableConfirm()" class="button is-small is-success ml-1" @click="confirmar()"> <button :disabled="!hayCambios || cantidadControl < 0" v-if="!aprobado" class="button is-small is-success ml-1" @click="confirmar()">
<span class="icon"> <span class="icon">
<i class="fas fa-check"></i> <i class="fas fa-check"></i>
</span> </span>
</button> </button>
<button :disabled="!puedeBorrar()" class="button is-small is-danger ml-1" @click="borrar()"> <button :disabled="!puedeBorrar" v-if="!aprobado" class="button is-small is-danger ml-1" @click="borrar()">
<span class="icon"> <span class="icon">
<i class="fas fa-trash-alt"></i> <i class="fas fa-trash-alt"></i>
</span> </span>
</button> </button>
</div> </div>
<div v-if="producto.requiere_notas" v-bind:class="{'has-icons-right': notas_warning_visible}" class="control is-full-width has-icons-left"> <div v-if="!aprobado && requiere_notas"
:class="{'has-icons-right': notas_warning_visible}"
class="control is-full-width has-icons-left">
<span class="icon is-small is-left"> <span class="icon is-small is-left">
<i class="fas fa-sticky-note"></i> <i class="fas fa-sticky-note"></i>
</span> </span>
<input v-model="notas" v-bind:class="{'is-danger': notas_warning_visible}" id="notas" class="input" type="text" placeholder="Talle o color" /> <input v-model="notasControl" v-bind:class="{'is-danger': notas_warning_visible}" id="notas" class="input" type="text" placeholder="Talle o color"/>
<span v-if="notas_warning_visible" class="icon is-small is-right"> <span v-if="notas_warning_visible" class="icon is-small is-right">
<i class="fas fa-exclamation-triangle"></i> <i class="fas fa-exclamation-triangle"></i>
</span> </span>
@ -43,70 +50,82 @@
</template> </template>
<script> <script>
import { mapActions, mapGetters, mapState } from "vuex";
export default { export default {
props: { props: {
producto: Object producto_id: {
type: Number,
required: true,
},
requiere_notas: {
type: Number,
required: true,
}
}, },
data() { data() {
return { return {
cantidad: this.cantidadEnChismosa(), cantidadControl: 0,
notas: this.notasEnChismosa(), notasControl: '',
notas_warning_visible: false, notas_warning_visible: false,
} }
}, },
watch: {
cantidadEnChismosa() {
this.actualizar();
},
notasEnChismosa() {
this.actualizar();
}
},
mounted() { mounted() {
Event.$on('sync-subpedido', (cantidad, productoId, notas) => { this.actualizar();
if (this.producto.id === productoId) },
this.sincronizar(cantidad, notas); computed: {
}); ...mapState('pedido', ["aprobado"]),
...mapGetters('pedido', ["enChismosa", "cantidad", "notas"]),
cantidadEnChismosa() {
return this.cantidad(this.producto_id);
},
notasEnChismosa() {
return this.notas(this.producto_id);
},
hayCambios() {
return this.cantidadControl !== this.cantidadEnChismosa || this.notasControl !== this.notasEnChismosa;
},
puedeBorrar() {
return this.enChismosa(this.producto_id) && !this.aprobado;
},
faltaNotas() {
return this.requiere_notas && this.cantidadControl > 0 && !this.notasControl;
},
}, },
methods: { methods: {
notasEnChismosa() { ...mapActions('pedido', ["modificarChismosa"]),
return this.producto.pivot !== undefined ? this.producto.pivot.notas : "";
},
cantidadEnChismosa() {
return this.producto.pivot !== undefined ? this.producto.pivot.cantidad : 0;
},
decrementar() { decrementar() {
this.cantidad -= 1; this.cantidadControl -= 1;
}, },
incrementar() { incrementar() {
this.cantidad += 1; this.cantidadControl += 1;
}, },
confirmar() { borrar() {
if (this.warningNotas()) { this.cantidadControl = 0;
this.confirmar();
},
async confirmar() {
if (this.faltaNotas) {
this.notas_warning_visible = true; this.notas_warning_visible = true;
return; return;
} }
console.log("Emit sync " + this.cantidad + " " + this.notas); await this.modificarChismosa({
Event.$emit('sync-subpedido', this.cantidad, this.producto.id, this.notas); producto_id: this.producto_id,
cantidad: this.cantidadControl,
notas: this.notasControl
});
}, },
borrar() { actualizar() {
this.cantidad = 0; this.cantidadControl = this.cantidadEnChismosa;
this.confirmar(); this.notasControl = this.notasEnChismosa;
},
sincronizar(cantidad, notas) {
this.notas_warning_visible = false;
this.notas = notas;
this.cantidad = cantidad;
if (this.producto.pivot !== undefined) {
this.producto.pivot.cantidad = cantidad;
this.producto.pivot.notas = notas;
}
},
hayCambios() {
if (this.cantidad != this.cantidadEnChismosa()) return true;
return this.cantidad > 0 && this.notas != this.notasEnChismosa();
},
puedeBorrar() {
return this.cantidadEnChismosa() > 0;
},
warningNotas() {
return this.producto.requiere_notas && this.cantidad > 0 && !this.notas;
},
disableConfirm() {
return !this.hayCambios();
}, },
} }
} }
@ -127,12 +146,13 @@
} }
.contador { .contador {
min-width: 178px; min-width: 1.5rem;
} }
.is-danger { .is-danger {
background-color: #fca697; background-color: #fca697;
} }
.is-danger::placeholder { .is-danger::placeholder {
color: #fff; color: #fff;
opacity: 1; /* Firefox */ opacity: 1; /* Firefox */

View file

@ -1,83 +1,62 @@
<script> <script>
import ProductoCantidad from "./ProductoCantidad.vue";
import { mapGetters, mapState } from "vuex";
export default { export default {
name: "ProductoCard", name: "ProductoCard",
components: { ProductoCantidad },
props: { props: {
producto: Object producto: {
}, type: Object,
data() { required: true
return {
cantidad: this.producto.cantidad,
enChismosa: this.producto.cantidad,
notas: this.producto.notas,
} }
}, },
mounted() { computed: {
Event.$on('sync-subpedido', (cantidad, productoId, notas) => { ...mapState('ui', ["show_chismosa"]),
if (this.producto.id === productoId) ...mapState('pedido', ["aprobado"]),
this.sincronizar(cantidad, notas); ...mapGetters('pedido', ["enChismosa", "cantidad"]),
}); fuePedido() {
return this.enChismosa(this.producto.id);
}, },
methods: { cantidadEnChismosa() {
decrementar() { return this.cantidad(this.producto.id);
this.cantidad -= 1;
},
incrementar() {
this.cantidad += 1;
},
confirmar() {
Event.$emit('sync-subpedido', this.cantidad, this.producto.id, this.notas);
},
borrar() {
this.cantidad = 0;
this.confirmar();
},
sincronizar(cantidad, notas) {
this.cantidad = cantidad;
this.producto.cantidad = cantidad;
this.enChismosa = cantidad;
this.notas = notas;
this.producto.notas = notas;
},
hayCambios() {
return this.cantidad !== this.enChismosa || this.notas !== this.producto.notas;
},
puedeBorrar() {
return this.enChismosa > 0;
}, },
conIconos() {
return this.producto.economia_solidaria || this.producto.nacional;
} }
},
} }
</script> </script>
<template> <template>
<div class="block column is-one-quarter-desktop is-full-mobile is-half-tablet min-width-from-desktop"> <div class="box is-flex is-flex-direction-column" style="height:100%">
<div class="box" style="height:100%"> <div class="columns is-mobile">
<div class="columns">
<div class="column"> <div class="column">
<p class="title is-6"> <p class="title is-6">
{{ producto.nombre }} {{ producto.nombre }}
</p> </p>
<p class="subtitle is-7" v-text="producto.proveedor"></p>
<span class="subtitle is-7 hidden-from-tablet" v-if="enChismosa !== 0">{{ enChismosa }} en chismosa</span>
</div> </div>
<div class="column is-one-quarter has-text-right"> <div class="column is-one-quarter has-text-right">
<p class="has-text-weight-bold has-text-primary"> <p class="has-text-weight-bold has-text-primary block">
<span class="is-left-mobile"> ${{ producto.precio }}
<img v-show="producto.economia_solidaria" height="30px" width="30px" src="/assets/solidaria.png" alt="proveedor de economía solidaria">
<img v-show="producto.nacional" height="30px" width="30px" src="/assets/uruguay.png" alt="proveedor nacional"/>
</span>
$<span v-text="producto.precio"></span>
</p> </p>
</div> </div>
</div> </div>
<footer class="columns"> <div class="columns mt-auto is-justify-content-left is-mobile">
<div class="column is-three-quarters"> <div v-if="conIconos" class="column has-text-left">
<pedidos-producto-cantidad :producto="producto"></pedidos-producto-cantidad> <span>
<img v-show="producto.economia_solidaria" height="30px" width="30px" src="/assets/solidaria.png" alt="proveedor de economía solidaria">
<img v-show="producto.nacional" height="30px" width="30px" src="/assets/uruguay.png" alt="proveedor nacional"/>
</span>
</div>
<div class="column mt-auto"
:class="conIconos ? 'is-three-quarters-mobile is-two-thirds-tablet' : 'is-full'">
<producto-cantidad
:producto_id="producto.id"
:requiere_notas="producto.requiere_notas"
/>
</div> </div>
<div class="column">
<p class="subtitle is-7 is-hidden-mobile" v-if="enChismosa > 0">{{ enChismosa }} en chismosa</p>
</div> </div>
</footer>
</div><!-- END BOX -->
</div> </div>
</template> </template>

View file

@ -2,15 +2,25 @@
<tr> <tr>
<td>{{ this.producto.nombre }}</td> <td>{{ this.producto.nombre }}</td>
<td class="has-text-right"> <td class="has-text-right">
<pedidos-producto-cantidad :producto="producto"></pedidos-producto-cantidad> <producto-cantidad
:producto_id="producto.id"
:requiere_notas="producto.requiere_notas"
/>
</td> </td>
<td class="has-text-right">{{ Math.ceil(this.producto.pivot.total) }}</td> <td class="has-text-right">{{ `${cantidad(producto.id) * producto.precio}` }}</td>
</tr> </tr>
</template> </template>
<script> <script>
import ProductoCantidad from "./ProductoCantidad.vue";
import { mapGetters } from "vuex";
export default { export default {
components: { ProductoCantidad },
props: { props: {
producto: Object producto: Object
}, },
computed: {
...mapGetters('pedido',["cantidad"]),
},
} }
</script> </script>

View file

@ -1,55 +1,79 @@
<template> <template>
<div v-show="visible" class="column"> <div v-show="visible" class="column">
<div class="columns is-multiline is-mobile"> <div ref="productos"
<pedidos-producto-card v-for="(producto,i) in productos" :key="i" :producto="producto"> class="columns is-multiline is-mobile"
</pedidos-producto-card><!-- END BLOCK COLUMN --> :class="{ 'align-last-left': isLastRowIncomplete }">
</div><!-- END COLUMNS --> <div v-for="(producto,i) in this.productos"
</div><!-- END CONTAINER --> class="block column is-full-mobile is-half-tablet is-one-quarter-fullhd"
:class="show_chismosa ? 'is-half-desktop' : 'is-one-third-desktop'">
<producto-card :key="i" :producto="producto"/>
</div>
</div>
</div>
</template> </template>
<script> <script>
import ProductoCard from "./ProductoCard.vue";
import { mapState } from "vuex";
export default { export default {
data() { name: 'ProductosContainer',
return { components: { ProductoCard },
productos: [],
visible: false,
paginar: 150,
valor: null,
filtro: null
}
},
computed: { computed: {
...mapState('productos', ["productos", "filtro"]),
...mapState('ui', ["show_chismosa"]),
visible() {
return this.filtro !== null;
},
miga: function () { miga: function () {
return { return {
nombre: this.valor, nombre: this.filtro.valor,
href: "#" + this.valor href: "#" + this.filtro.valor
} }
} }
}, },
methods: {
checkIfLastRowIncomplete() {
this.$nextTick(() => {
const wrapper = this.$refs.productos;
if (!wrapper) return;
const columns = wrapper.querySelectorAll('.column');
if (columns.length === 0) {
this.isLastRowIncomplete = false;
return;
}
const firstRowTop = columns[0].offsetTop;
let firstRowCount = 0;
columns.forEach(col => {
if (col.offsetTop === firstRowTop) firstRowCount++;
});
this.isLastRowIncomplete = this.productos.length % firstRowCount !== 0;
});
}
},
data() {
return {
isLastRowIncomplete: false
}
},
mounted() { mounted() {
Event.$on('filtrar-productos', (filtro,valor) => { this.checkIfLastRowIncomplete();
this.filtro = filtro window.addEventListener('resize', this.checkIfLastRowIncomplete);
this.valor = valor
axios.get("/api/productos", {
params: this.params(filtro,valor)
}).then(response => {
this.productos = response.data.data;
this.productos.forEach(p => {
p.pivot = {};
p.pivot.cantidad = this.$root.cantidad(p);
p.pivot.notas = this.$root.notas(p);
});
});
this.visible = true;
Event.$emit("migas-agregar",this.miga);
});
}, },
methods: { beforeDestroy() {
params(filtro,valor) { window.removeEventListener('resize', this.checkIfLastRowIncomplete);
let params = { paginar: this.paginar }
params[filtro] = valor
return params
}, },
} }
}
</script> </script>
<style>
.columns.align-last-left {
justify-content: flex-start !important;
}
.columns.align-last-left > .column:last-child:nth-child(3n),
.columns.align-last-left > .column:last-child:nth-child(2n),
.columns.align-last-left > .column:last-child {
margin-right: auto;
}
</style>

View file

@ -1,91 +0,0 @@
<template>
<div>
<label class="label">Escribí el nombre de tu familia o grupo de convivencia</label>
<div class="columns">
<div class="column is-two-thirds">
<div class="field">
<div class="control">
<input class="input" @input="onType" v-model="subpedido"/>
</div>
<p class="help">Debe ser claro para que tus compas del barrio te identifiquen.</p>
</div>
</div>
<div class="column is-one-third buttons">
<button class="button is-danger" v-show="!botonCrearDesabilitado" @click="submit">Crear nuevo pedido</button>
</div>
</div>
<div v-if="subpedidosExistentes.length" class="block">
<label class="label">Si ya comenzaste a hacer tu pedido este mes, elegilo en esta lista:</label>
<p class="help">Podés seguir escribiendo en el campo de arriba para refinar la búsqueda.</p>
<div class="columns is-mobile" v-for="(subpedidoExistente, index) in subpedidosExistentes" :class="{'has-background-grey-lighter': index % 2}" :key="index">
<div class="column is-half-mobile is-two-thirds-desktop is-two-thirds-tablet">
<p style="padding-top: calc(.5em - 1px); margin-bottom: .5rem" v-text="subpedidoExistente.nombre"></p>
</div>
<div class="buttons column is-half-mobile is-one-third-desktop is-one-third-tablet">
<button class="button is-danger" @click="elegirSubpedido(subpedidoExistente)">Continuar pedido</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
subpedido: null,
subpedidosExistentes: []
}
},
computed: {
nombresDeSubpedidos: function() {
return this.subpedidosExistentes.map(a => a.nombre.toLowerCase())
},
botonCrearDesabilitado : function() {
return !this.subpedido || this.nombresDeSubpedidos.includes(this.subpedido.toLowerCase())
}
},
props: ["gdcid"],
mounted() {
console.log("ready");
},
methods: {
onType() {
if (!this.subpedido){
this.subpedidosExistentes = [];
return;
}
axios.get("/api/subpedidos", {
params: {
nombre: this.subpedido,
grupo_de_compra: this.gdcid
}
}).then(response => {
this.subpedidosExistentes = response.data
});
},
submit() {
axios.post("/api/subpedidos", {
nombre: this.subpedido,
grupo_de_compra_id: this.gdcid
}).then(response => {
//se creo el subpedido
this.elegirSubpedido(response.data);
});
},
elegirSubpedido(subpedido){
//lo guardamos en sesion
this.guardarSubpedidoEnSesion(subpedido);
},
guardarSubpedidoEnSesion(subpedido) {
axios.post("/subpedidos/guardar_sesion", {
subpedido: subpedido,
grupo_de_compra_id: this.gdcid
}).then(_ => {
Event.$emit('obtener-sesion')
window.location.href = 'productos';
});
}
}
}
</script>

19
resources/js/store/index.js vendored Normal file
View file

@ -0,0 +1,19 @@
import Vue from 'vue';
import Vuex from 'vuex';
import admin from "./modules/admin";
import login from "./modules/login";
import pedido from "./modules/pedido";
import productos from "./modules/productos";
import ui from "./modules/ui";
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
admin,
login,
pedido,
productos,
ui,
},
});

83
resources/js/store/modules/admin.js vendored Normal file
View file

@ -0,0 +1,83 @@
import axios from "axios";
const state = {
lastFetch: null,
grupo_de_compra_id: null,
nombre: null,
devoluciones_habilitadas: null,
pedidos: null,
total_a_recaudar: null,
total_sin_devoluciones: null,
total_barrial: null,
total_devoluciones: null,
total_a_transferir: null,
total_transporte: null,
cantidad_transporte: null,
};
const mutations = {
setState(state, { grupo_de_compra }) {
state.lastFetch = new Date();
state.grupo_de_compra_id = grupo_de_compra.id;
state.nombre = grupo_de_compra.nombre;
state.devoluciones_habilitadas = grupo_de_compra.devoluciones_habilitadas;
state.pedidos = grupo_de_compra.pedidos;
state.total_a_recaudar = grupo_de_compra.total_a_recaudar;
state.total_sin_devoluciones = grupo_de_compra.total_sin_devoluciones;
state.total_barrial = grupo_de_compra.total_barrial;
state.total_devoluciones = grupo_de_compra.total_devoluciones;
state.total_a_transferir = grupo_de_compra.total_a_transferir;
state.total_transporte = grupo_de_compra.total_transporte;
state.cantidad_transporte = grupo_de_compra.cantidad_transporte;
},
toggleCaracteristica(state, { caracteristica_id }) {
state[`${caracteristica_id}_habilitadas`] = !state[`${caracteristica_id}_habilitadas`];
}
};
const actions = {
async getGrupoDeCompra({ commit }) {
const response = await axios.get('/user/grupo_de_compra');
commit('setState', response.data);
},
async setAprobacionPedido({ commit, dispatch }, { pedido_id, aprobacion }){
await axios.post("/api/admin/subpedidos/" + pedido_id + "/aprobacion", { aprobacion: aprobacion });
await actions.getGrupoDeCompra({ commit });
dispatch("ui/toast",
{ mensaje: `Pedido ${aprobacion ? '' : 'des' }aprobado con éxito.` },
{ root: true });
},
async toggleCaracteristica({ commit }, { caracteristica_id }) {
await axios.post(`/api/grupos-de-compra/${state.grupo_de_compra_id}/${caracteristica_id}`);
commit('toggleCaracteristica', { caracteristica_id: caracteristica_id })
},
};
const getters = {
grupoDeCompraDefinido() {
return state.lastFetch !== null;
},
hayPedidos() {
return state.pedidos?.length > 0;
},
pedidosAprobados() {
return state.pedidos?.filter(p => p.aprobado) ?? [];
},
hayAprobados() {
return getters.pedidosAprobados().length !== 0;
},
getPedido() {
return (pedido_id) => state.pedidos?.find(p => p.id === pedido_id);
},
getCaracteristica() {
return (caracteristica) => state[`${caracteristica}_habilitadas`];
}
};
export default {
namespaced: true,
state,
mutations,
actions,
getters,
};

139
resources/js/store/modules/login.js vendored Normal file
View file

@ -0,0 +1,139 @@
import axios from "axios";
const state = {
regiones: null,
grupos_de_compra: null,
region_elegida: null,
grupo_de_compra_elegido: null,
rol: null,
};
const mutations = {
setRegiones(state, { regiones }) {
state.regiones = regiones;
},
setRegionYBarrios(state, { region, grupos_de_compra }) {
state.region_elegida = region;
state.grupos_de_compra = grupos_de_compra;
},
selectGrupoDeCompra(state, { grupo_de_compra }) {
state.grupo_de_compra_elegido = grupo_de_compra;
},
setRol(state, { rol }) {
state.rol = rol;
},
};
const actions = {
async getRegiones({ commit }) {
const response = await axios.get("/api/regiones");
commit('setRegiones', { regiones: response.data });
},
async selectRegion({ commit }, { region }) {
const response = await axios.get(`/api/regiones/${region}`);
commit('setRegionYBarrios', { region: region, grupos_de_compra: response.data });
},
async getRol({ commit }) {
const response = await axios.get("/user/rol");
commit('setRol', { rol: response.data.rol });
}
};
const getters = {
urlRol() {
let split = window.location.pathname
.replace('login', '')
.split('/')
.filter(x => x.length);
return split[0] ?? 'pedido';
},
textos() {
let rol = getters.urlRol();
switch (rol) {
case 'admin':
return {
titulo: "Administración de Pedidos MPS",
subtitlo: "administración de pedidos",
password: "Contraseña de administración del barrio",
ayuda: "Si no la sabés, consultá a la comisión informática",
label: "Seleccioná tu región"
};
case 'comisiones':
return {
titulo: "Comisiones MPS",
subtitlo: "página de comisiones",
password: "Contraseña",
ayuda: "Si no la sabés, consultá a la comisión informática",
label: "Usuario"
};
case 'pedido':
return {
titulo: "Pedidos MPS",
subtitlo: "aplicación de pedidos",
password: "Contraseña",
ayuda: "Si no la sabés, consultá a la comisión informática",
label: "Seleccioná tu región"
};
default:
throw new Error("Url inválida");
}
},
estilos() {
let rol = getters.urlRol();
switch (rol) {
case 'admin':
return {
fondo: "has-background-danger",
texto: "has-text-white",
botones: "is-warning",
};
case 'comisiones':
return {
fondo: "has-background-warning",
texto: "",
botones: "is-dark"
};
case 'pedido':
return {
fondo: "",
texto: "",
botones: "is-danger"
};
default:
throw new Error("Url inválida");
}
},
opcionesLogin() {
let rol = getters.urlRol();
switch (rol) {
case 'admin':
return [
{ nombre: "Pedidos", href: "/" },
{ nombre: "Comisiones", href: "/comisiones" }
];
case 'comisiones':
return [
{ nombre: "Pedidos", href: "/" },
{ nombre: "Administración", href: "/admin" }
];
case 'pedido':
return [
{ nombre: "Administración", href: "/admin" },
{ nombre: "Comisiones", href: "/comisiones" }
];
default:
throw new Error("Url inválida");
}
},
nombre() {
return `${state.grupo_de_compra_elegido}${ getters.urlRol() === 'admin' ? '_admin' : ''}`;
}
};
export default {
namespaced: true,
state,
mutations,
actions,
getters,
};

125
resources/js/store/modules/pedido.js vendored Normal file
View file

@ -0,0 +1,125 @@
import axios from "axios";
const state = {
lastFetch: null,
grupo_de_compra: null,
pedido_id: null,
nombre: null,
productos: null,
aprobado: null,
total: null,
total_transporte: null,
cantidad_transporte: null,
total_sin_devoluciones: null,
devoluciones_total: null,
devoluciones_notas: null,
};
const mutations = {
setGrupoDeCompra(state, grupo_de_compra) {
state.grupo_de_compra = grupo_de_compra;
},
setPedido(state, pedido) {
state.lastFetch = new Date();
state.pedido_id = pedido.id;
state.nombre = pedido.nombre;
state.productos = pedido.productos;
state.aprobado = pedido.aprobado;
state.total = pedido.total;
state.total_transporte = pedido.total_transporte;
state.cantidad_transporte = pedido.cantidad_transporte;
state.total_sin_devoluciones = pedido.total_sin_devoluciones;
state.devoluciones_total = pedido.devoluciones_total;
state.devoluciones_notas = pedido.devoluciones_notas;
},
reset(state) {
state.lastFetch = null;
state.pedido_id = null;
state.nombre = null;
state.productos = null;
state.aprobado = null;
state.total = null;
state.total_transporte = null;
state.cantidad_transporte = null;
state.total_sin_devoluciones = null;
state.devoluciones_total = null;
state.devoluciones_notas = null;
}
};
const actions = {
async getGrupoDeCompra({ commit }) {
const response = await axios.get('/user/grupo_de_compra');
commit('setGrupoDeCompra', response.data.grupo_de_compra);
},
async guardarSesion(_, { pedido_id }) {
await axios.post("/pedido/sesion", { id: pedido_id });
},
async crearPedido({ commit, dispatch }, { nombre, grupo_de_compra_id }) {
const response = await axios.post("/api/subpedidos", {
nombre: nombre,
grupo_de_compra_id: grupo_de_compra_id
});
dispatch("guardarSesion", { pedido_id: response.data.data.id});
commit('setPedido', response.data.data);
},
async elegirPedido({ commit, dispatch }, { pedido_id }) {
const response = await axios.get(`/api/subpedidos/${pedido_id}`);
dispatch("guardarSesion", { pedido_id: pedido_id})
commit('setPedido', response.data.data);
},
async modificarChismosa({ commit, dispatch }, { producto_id, cantidad, notas }) {
try {
const response = await axios.post("/api/subpedidos/" + state.pedido_id + "/sync", {
cantidad: cantidad,
producto_id: producto_id,
notas: notas,
});
commit('setPedido', response.data.data);
dispatch("ui/toast", { mensaje: 'Pedido modificado con éxito' }, { root: true });
} catch (error) {
dispatch("ui/error", { error: error }, { root: true });
}
},
async modificarDevoluciones({ commit, dispatch }, { monto, notas }) {
try {
const response = await axios.post("api/subpedidos/" + state.pedido_id + "/sync_devoluciones", {
total: monto,
notas: notas,
});
commit('setPedido', response.data.data);
dispatch("ui/toast", { mensaje: 'Devoluciones modificadas con éxito' }, { root: true });
} catch (error) {
dispatch("ui/error", { error: error }, { root: true });
}
},
async resetear({ commit, dispatch }) {
await axios.delete("/pedido/sesion");
dispatch("productos/getProductos", null, { root: true });
dispatch("ui/resetear", null, { root: true });
commit('reset');
},
};
const getters = {
pedidoDefinido() {
return state.lastFetch !== null;
},
enChismosa() {
return ((producto_id) => state.productos.some(p => p.id === producto_id));
},
cantidad() {
return ((producto_id) => state.productos.find(p => p.id === producto_id)?.pivot.cantidad ?? 0);
},
notas() {
return ((producto_id) => state.productos.find(p => p.id === producto_id)?.pivot.notas ?? "");
}
}
export default {
namespaced: true,
state,
mutations,
actions,
getters,
};

56
resources/js/store/modules/productos.js vendored Normal file
View file

@ -0,0 +1,56 @@
import axios from "axios";
const state = {
lastFetch: null,
categorias: [],
productos: [],
filtro: null,
};
const mutations = {
setCategorias(state, categorias) {
state.lastFetch = new Date();
state.categorias = categorias;
},
setProductos(state, productos) {
state.lastFetch = new Date();
state.productos = productos;
},
setFiltro(state, filtro) {
state.lastFetch = new Date();
state.filtro = filtro;
},
};
const actions = {
async init({ dispatch }) {
dispatch('getCategorias');
dispatch('getProductos');
},
async getCategorias({ commit }) {
const response = await axios.get('api/categorias');
commit('setCategorias', response.data);
},
async getProductos({ commit }) {
const response = await axios.get("/api/productos");
commit('setFiltro', null);
commit('setProductos', response.data.data);
},
async seleccionarCategoria({ dispatch }, { categoria }) {
dispatch('filtrarProductos', { filtro: "categoria", valor: categoria });
},
async filtrarProductos({ commit }, { filtro, valor }) {
const response = await axios.get("/api/productos", {
params: { [filtro]: valor }
});
commit('setFiltro', { clave: filtro, valor: valor });
commit('setProductos', response.data.data);
}
};
export default {
namespaced: true,
state,
mutations,
actions,
};

79
resources/js/store/modules/ui.js vendored Normal file
View file

@ -0,0 +1,79 @@
const state = {
show_chismosa: false,
show_devoluciones: false,
show_tags: true,
tags_interactuada: false,
migas: [{ nombre: 'Pedidos', action: 'pedido/resetear' }],
canasta_actual: null,
};
const mutations = {
setCanastaActual(state, { canasta }) {
state.canasta_actual = canasta;
},
toggleChismosa(state) {
state.show_chismosa = !state.show_chismosa;
},
toggleDevoluciones(state) {
state.show_devoluciones = !state.show_devoluciones;
},
toggleTags(state, manual) {
if (manual)
state.tags_interactuada = true;
state.show_tags = !state.show_tags;
},
addMiga(state, miga) {
state.migas.push(miga);
},
popUltimaBusqueda(state) {
if (!state.migas.at(-1).action)
state.migas.pop();
},
reset(state) {
state.show_chismosa = false;
state.show_devoluciones = false;
}
};
const actions = {
async getCanastaActual({ commit }) {
const response = await axios.get('/api/canasta-actual');
commit("setCanastaActual", { canasta: response.data });
},
clickMiga({ dispatch }, { miga }) {
let dropWhile = (array, pred) => {
let result = array.slice(0);
while (result.length && pred(result[0])) {
result = result.slice(1)
}
return result;
}
dispatch(miga.action, miga.arguments ?? null, { root: true });
state.migas = dropWhile(state.migas.reverse(),(m => m.nombre !== miga.nombre)).reverse();
},
toast(_, { mensaje }) {
return window.bulmaToast.toast({
message: mensaje,
duration: 2000,
type: 'is-danger',
position: 'bottom-center',
});
},
error({ dispatch }, { error }) {
const errorMsg = error.response && error.response.data && error.response.data.message
? error.response.data.message
: error.message;
dispatch("toast", { mensaje: errorMsg });
},
resetear({ commit }) {
commit("reset");
},
};
export default {
namespaced: true,
state,
mutations,
actions,
};

View file

@ -7,6 +7,16 @@
@import 'bulma'; @import 'bulma';
@import '~bulma-switch'; @import '~bulma-switch';
html, body {
overflow-x: hidden;
max-width: 100%;
height: 100%;
}
form, #root, #login-form {
height: 100%;
}
main.has-top-padding { main.has-top-padding {
padding-top: 4.5rem !important; padding-top: 4.5rem !important;
} }

View file

@ -1,34 +0,0 @@
<!DOCTYPE html>
<html class="has-background-danger" lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ config('app.name', 'Pedidos del MPS') }}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
<link rel="stylesheet" href="{{ mix('css/app.css') }}">
</head>
<body>
<section class="section">
<div id="root" class="container">
<h1 class="title has-text-white">
Administración de Pedidos MPS
</h1>
<p class="subtitle has-text-white">
Bienvenidx a la administración de pedidos del <strong class="has-text-white">Mercado Popular de Subsistencia</strong>
</p>
@error('name')
<div class="notification is-warning">
Contraseña incorrecta, intentalo nuevamente.
</div>
@enderror
<comunes-region-select v-bind:admin="true"></comunes-region-select>
<form method="post" action="login">
@csrf
<comunes-barrio-select v-bind:admin="true"></comunes-barrio-select>
<admin-login></admin-login>
</form>
</div>
</section>
<script src="{{ mix('js/app.js') }}" defer></script>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show more