Productos view

This commit is contained in:
2024-09-22 01:31:21 -03:00
parent 39379b67c3
commit 9dff07dc0a
13 changed files with 1602 additions and 103 deletions

View File

@@ -3,12 +3,16 @@ const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 1024,
height: 720,
width: 1100,
height: 790,
resizable: false,
frame: false,
transparent: true,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
contextIsolation: false
contextIsolation: false,
}
});
win.loadURL('http://localhost:8080');
@@ -21,7 +25,6 @@ app.on('window-all-closed', () => {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();

546
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,16 +21,21 @@
}
},
"dependencies": {
"bootstrap": "^5.3.3",
"boxicons": "^2.1.4",
"pinia": "^2.2.2",
"vue": "^3.2.13",
"vue-router": "^4.0.3"
"vue-router": "^4.0.3",
"vuedigitalpowerui": "^0.1.7"
},
"devDependencies": {
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"electron": "^32.1.2",
"electron-builder": "^25.0.5",
"sass": "^1.32.7",
"sass-loader": "^12.0.0"
"jquery": "^3.7.1",
"popper.js": "^1.16.1",
"sass": "^1.79.3",
"sass-loader": "^12.6.0"
}
}

View File

@@ -1,14 +1,18 @@
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<head>
<link rel="stylesheet" href="https://digitalpower.ar/cdn/digitalpower.css">
</head>
<div class="window-container">
<div id="title-bar" noselect></div>
<Nav/>
<router-view/>
</div>
</template>
<script setup>
import {onMounted, ref} from 'vue';
import {useRouter} from 'vue-router';
import Nav from "@/components/app/Nav.vue";
const router = useRouter();
@@ -18,6 +22,16 @@ onMounted(() => {
</script>
<style lang="scss">
:root {
--header-height: 150px !important;
--app-shadow: rgba(0, 0, 0, 0.45);
--navmenu-height: 200px;
}
body {
background: transparent !important;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
@@ -38,4 +52,63 @@ nav {
}
}
}
body {
margin: 0;
padding: 0;
padding: 2em;
width: 1024px;
height: 720px;
}
.window-container {
box-shadow: 0px 0px 20px 0px #0a0a0a;
border-radius: 15px;
width: 1024px;
height: 720px;
background: white;
overflow-y: auto;
}
#title-bar {
-webkit-app-region: drag;
height: 20px;
background: var(--primary);
color: white;
display: flex;
align-items: center;
padding: 0 10px;
}
.select-wrapper::after {
content: ">" !important;
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(-100%);
opacity: 0;
}
}
.slide-leave-active {
transition: opacity var(--duration), transform var(--duration);
}
.slide-leave-to {
animation: slideOut var(--duration) forwards;
}
[selectedNav="true"] {
background: white;
& > p {
color: var(--text-color) !important;
}
}
</style>

View File

@@ -0,0 +1,16 @@
<script setup>
</script>
<template>
<div id="app-layout">
<slot></slot>
</div>
</template>
<style scoped lang="scss">
#app-layout {
padding: 1em 2em;
height: calc(100vh - var(--header-height));
}
</style>

127
src/components/app/Nav.vue Normal file
View File

@@ -0,0 +1,127 @@
<script setup>
import NavMenu from "@/components/app/NavMenu.vue";
import {ref} from "vue";
import {useUi} from "@/pinia/ui";
import {useRouter} from "vue-router";
const router = useRouter();
const ui = useUi();
const options = ref([
{
name: "Aplicacion", items: [
{name: "Manual"},
{name: "Solicitar Ayuda"},
{name: "Digital Power"},
{
name: "Cerrar",
callback: () => {
window.close();
}
},
]
},
{
name: "Productos", items: [
{name: "Agregar Producto"},
{name: "Generar Venta"},
]
},
{
name: "Imprimir",
items: [
{name: "Listado de productos"},
{name: "Estadisticas"},
],
},
{
name: "Estadisticas",
items: [],
url: '/',
},
]);
function openMenu(items, name, url) {
ui.setCurrentMenu(null);
if (!items) return;
if (url) {
router.push(url);
return;
}
setTimeout(() => {
ui.setCurrentMenu(items);
ui.selectedMenu = name;
}, 5)
setTimeout(() => {
let $menu = document.querySelector("#menu-dropdown");
let $option = document.querySelector("#" + name);
const rect = ($option.getBoundingClientRect())
$menu.setAttribute("style", `
position: fixed;
left: ${rect.left}px;
bottom: ${rect.bottom}px;
top: ${rect.top + rect.height}px;
`)
}, 20)
}
function handleClick(item) {
item?.callback();
}
</script>
<template>
<nav>
<div class="list" noselect>
<div class="item" :selectedNav="opt.name === ui.selectedMenu" :id="opt?.name" v-for="(opt, key) in options" :key
@click="openMenu(opt?.items, opt?.name, opt?.url)"><p>
{{
opt?.name
}}</p></div>
</div>
<NavMenu @callback="handleClick" v-if="ui.currentMenu"></NavMenu>
</nav>
</template>
<style scoped lang="scss">
nav {
background: var(--primary);
width: 100%;
height: 60px;
padding: 0;
z-index: 20;
}
.list {
width: 100%;
height: 100%;
display: flex;
justify-content: start;
align-items: end;
}
.item {
transition: var(--duration);
border-top-right-radius: 15px;
&:hover {
cursor: pointer;
background: white;
& > p {
color: var(--text-color);
}
}
& > p {
transition: var(--duration);
margin: 0;
padding: 1em;
border-top-right-radius: 15px;
color: white;
}
}
</style>

View File

@@ -0,0 +1,54 @@
<script setup>
import {useUi} from "@/pinia/ui";
const emits = defineEmits(["callback"]);
const ui = useUi();
</script>
<template>
<div class="nav-menu" id="menu-dropdown">
<div v-for="(item, key) in ui.currentMenu" :key class="item" @click="emits('callback', item)">
{{ item?.name }}
</div>
</div>
</template>
<style scoped lang="scss">
.nav-menu {
width: 250px;
height: var(--navmenu-height);
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.4);
background: white;
border-bottom-left-radius: 15px;
border-bottom-right-radius: 15px;
animation: fade .2s ease-in-out forwards;
overflow-y: auto;
}
.item {
padding: .5em 1em;
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
text-align: left;
cursor: pointer;
transition: var(--duration);
&:hover {
color: white;
background: var(--primary);
}
}
@keyframes fade {
0% {
opacity: 0;
height: 0
}
100% {
opacity: 1;
height: var(--navmenu-height);
}
}
</style>

View File

@@ -0,0 +1,34 @@
<script setup>
import {Input} from 'vuedigitalpowerui'
import {ref} from "vue";
const emits = defineEmits(["search"]);
const props = defineProps(["filters"])
const filter = ref("");
const value = ref("");
function search() {
emits("search", {filter: filter.value, value: value.value})
}
</script>
<template>
<div id="filters" class="mt-3">
<form @submit.prevent="search" flex gapped flex-wrap>
<Input class="filter" type="select" v-model="filter" :options="filters"/>
<Input class="filter" placeholder="Busqueda" v-model="value"/>
<Input type="button" value="Buscar" background="var(--primary)" color="white" @click="search"/>
<Input type="button" value="Limpiar" background="var(--primary)" color="white"/>
</form>
</div>
</template>
<style scoped lang="scss">
#filters {
width: 100%;
}
.filter {
width: 250px !important;
}
</style>

View File

@@ -0,0 +1,86 @@
<script setup>
import {useUi} from "@/pinia/ui";
const props = defineProps(["items", "headers"])
</script>
<template>
<div id="table-container" radius-border>
<table>
<thead>
<tr>
<th v-for="(head, key) in headers" :key>{{ head?.name }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, key) in items">
<td v-for="(head, key) in headers" :class="head">{{ item?.[head?.name] }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<style scoped lang="scss">
#table-container {
box-shadow: 0px 0px 5px 0px var(--app-shadow);
border-radius: 15px;
height: 80%;
overflow-y: auto;
&::-webkit-scrollbar-track {
background: transparent;
}
}
table {
width: 100%;
border-collapse: collapse;
}
thead > tr > th {
border-top: none;
&:first-child {
border-left: none;
}
&:last-child {
border-right: none;
}
}
tbody > tr {
transition: var(--duration);
&:last-child {
& > td {
border-bottom: none !important;
}
}
&:hover {
background: var(--primary);
color: white;
}
}
th {
padding: 1em 2em;
//border: 1px solid var(--text-color);
border: 1px solid rgba(0, 0, 0, 0.27);
}
td {
padding: 1em 2em;
border-bottom: 1px solid rgba(0, 0, 0, 0.27);
}
.Nombre {
width: 400px;
}
</style>

View File

@@ -2,7 +2,13 @@ import {createApp} from 'vue'
import App from './App.vue'
import router from './router'
import {createPinia} from "pinia";
import "../src/styles/digitalpower.css"
import 'boxicons'
import * as bootstrap from 'bootstrap'
import 'bootstrap'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'jquery/src/jquery.js'
import 'bootstrap/dist/js/bootstrap.min.js'
const pinia = createPinia();
const app = createApp(App).use(pinia).use(router).mount('#app')

15
src/pinia/ui.js Normal file
View File

@@ -0,0 +1,15 @@
import {defineStore} from "pinia";
export const useUi = defineStore('ui', {
state: () => ({
currentMenu: null,
selectedMenu: null,
}),
getters: {},
actions: {
setCurrentMenu(state) {
if (!state) this.selectedMenu = null;
this.currentMenu = state;
},
}
})

662
src/styles/digitalpower.css Normal file
View File

@@ -0,0 +1,662 @@
@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
* {
box-sizing: border-box;
font-family: "Poppins", sans-serif;
/* Esto afectará a cualquier scrollbar en tu página web */
::-webkit-scrollbar {
width: 3px;
height: 5px;
}
/* Track */
::-webkit-scrollbar-track {
background: var(--gray-light);
}
/* Handle */
::-webkit-scrollbar-thumb {
background: var(--primary);
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
background: var(--secondary);
}
}
:root {
--gray: rgba(0, 0, 0, 0.3);
--gray-light: rgba(0, 0, 0, 0.1);
--gray-very-light: rgba(0, 0, 0, 0.08);
--primary-blue: #3a7ca5;
--primary: dodgerblue;
--primary-active-item: #efefef;
--secondary: #0887a1;
--red: #d14c4c;
--back-color: white;
--text-color: rgba(62, 62, 62, 0.7);
--sidebar-color: dodgerblue;
--sidebar-color-hover: white;
--input-bg: white;
--input-button: #efefef;
--input-bg-hover: #dbdbdb;
--shadow: 0px 0px 5px 0px var(--gray);
--admin-title-height: 67px;
--duration: 0.3s;
--header-height: 50px;
}
body {
margin: 0;
overflow-x: hidden;
background: var(--back-color);
}
#app-layout {
max-width: 100%;
}
#notification-container {
position: fixed;
top: calc(var(--header-height) + 1.5em);
right: 0;
margin-right: 1em;
z-index: 1000;
}
h1 {
font-weight: bold;
font-size: 2em;
}
h1, h2, h3, h4, h5, p, b {
width: fit-content;
}
pre {
background: lightgray;
}
.page-enter-active,
.page-leave-active {
transition: all 0.2s;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
}
.custom-modal-html {
width: 80vw;
height: fit-content;
max-height: 100vh;
max-width: 100vh;
position: fixed;
top: 50%;
left: 50%;
z-index: 200000;
color: var(--text-color);
background: var(--back-color);
overflow-y: auto;
transform: translate(-50%, -50%);
.title {
padding: 0.5em;
border-bottom: 1px solid var(--gray);
h5,
h1 {
text-align: center;
margin: 0;
}
}
.content {
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
padding: 2em;
p {
margin-bottom: 2em;
}
.actions {
gap: 1em;
}
}
}
a {
color: dodgerblue;
text-decoration: none;
cursor: pointer;
}
p {
font-size: 1em;
}
.active-item {
background: var(--secondary);
color: var(--sidebar-color-hover) !important;
.icon {
fill: var(--sidebar-color-hover) !important;
}
}
.notification {
padding: 1em;
background: var(--primary);
color: white;
margin: 1em 0;
min-width: 200px;
pointer-events: none;
user-select: none;
z-index: 1000;
}
.notification-enter {
animation-name: notificationAnimationEnter;
animation-duration: 0.25s;
}
.notification-exit {
animation-name: notificationAnimationExit;
animation-duration: 0.25s;
}
.ck-editor {
display: block;
width: 100% !important;
}
.ck {
color: var(--text-color) !important;
background: var(--input-bg) !important;
border: none !important;
}
.red {
background: var(--red) !important;
}
[limited-text] {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
text-wrap: nowrap;
}
[flex] {
display: flex !important;
}
[flex-wrap] {
flex-wrap: wrap;
}
[nopointer] {
pointer-events: none;
}
[subitem] {
padding-left: 1em;
}
[groupitem] {
border-bottom: 1px solid var(--gray);
}
[grouplabel] {
background: var(--gray-light);
color: white;
}
[menuitem] {
border-bottom: 1px solid var(--gray);
}
[menuitem],
[subitem] {
transition: 0.2s;
&:hover {
background: var(--secondary);
color: white;
}
}
[shadow] {
box-shadow: var(--shadow);
}
[borderbottom] {
border-bottom: 1px solid var(--gray);
}
[bordered] {
border: 1px solid var(--gray);
}
[radius-border] {
border-radius: 5px;
}
[bound] {
background: var(--gray-light);
background-image: linear-gradient(
to right,
transparent 20%,
var(--gray-very-light) 60%
);
background-size: 200% 100%;
transition: background-position 0.2s ease-out;
animation: boundAnimation 2s ease infinite alternate;
}
[custominput] {
border: 1px solid var(--gray-light);
border-radius: 5px;
&:focus {
outline: none;
border: 1px solid var(--gray);
box-shadow: 0px 0px 5px 0px var(--gray);
}
}
[customselect] {
width: 100%;
select {
padding: 0.6em 1em;
width: 100%;
appearance: none;
background: white;
}
}
.select-wrapper {
position: relative;
&::after {
content: "â–¼";
font-size: 1rem;
top: 50%;
right: 1em;
position: absolute;
color: var(--gray);
transform: translateY(-50%);
z-index: 100000;
}
}
.custom-button-html {
padding: 0.6em 1.5em;
border-radius: 5px;
border: none;
cursor: pointer;
font-size: 1em;
display: block;
width: fit-content;
transition: 0.2s;
color: white;
background: var(--primary);
margin-bottom: 0.5em;
&:hover {
filter: brightness(0.8);
}
&:focus {
outline: none;
}
}
[flex-center] {
display: flex !important;
align-items: center;
justify-content: center;
}
[flex-start] {
display: flex !important;
align-items: start;
justify-content: start;
}
[flex-column] {
flex-direction: column;
}
[block-center] {
display: block;
margin: auto;
width: fit-content;
}
[overlay] {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 15;
background: var(--gray);
}
[inside-verlay] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: none;
transition: var(--duration);
z-index: 15;
background: rgba(0, 0, 0, 0.4);
}
[fade] {
animation-name: fadeAnim;
animation-duration: 0.2s;
}
[delayedfade] {
animation: fadeAnim 0.2s forwards;
}
[absolute-centered] {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
[list] {
list-style: none;
margin: 0;
padding: 0;
}
[gapped] {
gap: 1em;
}
[noselect] {
user-select: none;
}
[underline] {
/*
border-bottom: 1px solid var(--primary);
*/
padding-bottom: 10px;
margin-bottom: 1em;
position: relative;
&::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background: var(--primary);
animation-name: underlineAnim;
animation-duration: var(--duration);
}
}
[nowrap] {
text-wrap: nowrap;
}
[pointer] {
pointer-events: all;
}
[select] {
user-select: all;
}
[slide] {
animation: slide;
animation-duration: var(--duration);
}
[slideRight] {
animation: slideRight;
animation-duration: var(--duration);
}
[slideUp] {
animation: slideUp;
animation-duration: var(--duration);
}
[slideDown] {
animation: slideDown;
animation-duration: var(--duration);
}
[fixed] {
position: fixed;
top: 0;
left: 0;
z-index: 100;
height: 100%;
}
.loader {
width: 48px;
height: 48px;
border-radius: 50%;
display: inline-block;
position: relative;
border: 3px solid;
border-color: #fff #fff transparent transparent;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
.loader::after,
.loader::before {
content: "";
box-sizing: border-box;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
border: 3px solid;
border-color: transparent transparent var(--primary-blue) var(--primary-blue);
width: 40px;
height: 40px;
border-radius: 50%;
box-sizing: border-box;
animation: rotationBack 0.5s linear infinite;
transform-origin: center center;
}
.loader::before {
width: 32px;
height: 32px;
border-color: #fff #fff transparent transparent;
animation: rotation 1.5s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes rotationBack {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(-360deg);
}
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes prixClipFix {
0% {
clip-path: polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0);
}
50% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 0, 100% 0, 100% 0);
}
75%,
100% {
clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 100% 100%, 100% 100%);
}
}
@keyframes boundAnimation {
0% {
background-position: 0 0;
}
100% {
background-position: 100% 0;
}
}
@keyframes notificationAnimationEnter {
0% {
transform: translateX(150%);
}
100% {
transform: translateX(0%);
}
}
@keyframes notificationAnimationExit {
0% {
transform: translateX(0%);
}
100% {
transform: translateX(150%);
}
}
@keyframes fadeAnim {
0% {
opacity: 0%;
}
100% {
opacity: 100%;
}
}
@keyframes underlineAnim {
0% {
width: 0%;
}
100% {
width: 100%;
}
}
@keyframes slide {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(0%);
}
}
@keyframes slideRight {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0%);
}
}
@keyframes slideUp {
0% {
transform: translateY(100%);
}
100% {
transform: translateY(0%);
}
}
@keyframes slideDown {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(0%);
}
}
@media screen and (max-width: 1000px) {
.dp-container {
h1 {
text-align: center;
font-size: 1.5em;
}
}
.custom-modal-html {
height: 60vh;
.actions {
flex-direction: column;
gap: 0.5em !important;
}
}
}

View File

@@ -1,11 +1,49 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
<Layout @click="handleClick">
<Table :headers :items="productos"></Table>
<Filters :filters="headers"></Filters>
</Layout>
</template>
<script setup>
import HelloWorld from '@/components/HelloWorld.vue'
import Layout from "@/components/app/Layout.vue";
import Table from "@/components/productos/Table.vue";
import {computed, onMounted, ref} from "vue";
import Filters from "@/components/productos/Filters.vue";
import {useUi} from "@/pinia/ui";
const ui = useUi();
const headers = computed(() => {
const producto = productos.value?.[0];
if (!producto) return [];
return Object.keys(producto).map((x) => {
return {value: x, name: x};
})
})
const productos = ref([
{Nombre: "Producto de prueba de longitud", Precio: "Precio", Stock: "Stock", Vendidos: 0, Acciones: 0},
{Nombre: "Producto de prueba de longitud", Precio: "Precio", Stock: "Stock", Vendidos: 0, Acciones: 0},
{Nombre: "Producto de prueba de longitud", Precio: "Precio", Stock: "Stock", Vendidos: 0, Acciones: 0},
{Nombre: "Producto de prueba de longitud", Precio: "Precio", Stock: "Stock", Vendidos: 0, Acciones: 0},
{Nombre: "Producto de prueba de longitud", Precio: "Precio", Stock: "Stock", Vendidos: 0, Acciones: 0},
{Nombre: "Producto de prueba de longitud", Precio: "Precio", Stock: "Stock", Vendidos: 0, Acciones: 0},
{Nombre: "Producto de prueba de longitud", Precio: "Precio", Stock: "Stock", Vendidos: 0, Acciones: 0},
{Nombre: "Producto de prueba de longitud", Precio: "Precio", Stock: "Stock", Vendidos: 0, Acciones: 0},
{Nombre: "Producto de prueba de longitud", Precio: "Precio", Stock: "Stock", Vendidos: 0, Acciones: 0},
{Nombre: "Producto de prueba de longitud", Precio: "Precio", Stock: "Stock", Vendidos: 0, Acciones: 0},
{Nombre: "Producto de prueba de longitud", Precio: "Precio", Stock: "Stock", Vendidos: 0, Acciones: 0},
{Nombre: "Producto de prueba de longitud", Precio: "Precio", Stock: "Stock", Vendidos: 0, Acciones: 0},
{Nombre: "Producto de prueba de longitud", Precio: "Precio", Stock: "Stock", Vendidos: 0, Acciones: 0},
{Nombre: "Producto de prueba de longitud", Precio: "Precio", Stock: "Stock", Vendidos: 0, Acciones: 0},
]);
onMounted(() => {
})
function handleClick() {
ui.setCurrentMenu(null)
}
</script>