Compare commits

..

14 Commits

Author SHA1 Message Date
f1f09029ba base de datos pocketBase 2023-04-02 18:27:34 +02:00
221f89c1e4 eliminado console.log 2023-03-23 15:04:51 +01:00
1753c6ee33 elimina items 2023-03-14 14:34:16 +01:00
14f7d0e67e carga producto 2023-03-14 14:01:38 +01:00
109ad712bf crear item y guardar en la lista 2023-03-14 13:07:09 +01:00
ee21c44afb rutas de navbar 2023-03-14 12:36:08 +01:00
b17f4e7f1e al darle a la papelera entra
- enlaces azules
- botón de crear item y back
- nombre de la lista arriba
- nombre de la lista en el navbar
2023-03-14 11:53:54 +01:00
0da30c0563 pagina de listas individuales 2023-03-13 19:24:02 +01:00
6029364240 q-file restringido por formato o tamaño 2023-03-13 13:11:10 +01:00
6e60580f9d thumbs 2023-03-13 12:51:38 +01:00
4f329ba46a eliminado essentialLinks y rutas en navbar 2023-03-13 12:38:28 +01:00
62a225633c badge items 2023-03-13 12:13:08 +01:00
9fdc68296b estilos y bordes 2023-03-13 12:04:00 +01:00
8ccd713672 espacio con avatar 2023-03-13 12:03:39 +01:00
7 changed files with 360 additions and 134 deletions

159
pb_schema.json Normal file
View File

@@ -0,0 +1,159 @@
[
{
"id": "_pb_users_auth_",
"name": "users",
"type": "auth",
"system": false,
"schema": [
{
"id": "users_name",
"name": "name",
"type": "text",
"system": false,
"required": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"id": "users_avatar",
"name": "avatar",
"type": "file",
"system": false,
"required": false,
"options": {
"maxSelect": 1,
"maxSize": 5242880,
"mimeTypes": [
"image/jpeg",
"image/png",
"image/svg+xml",
"image/gif",
"image/webp"
],
"thumbs": null
}
}
],
"indexes": [
"CREATE INDEX `__pb_users_auth__created_idx` ON `users` (`created`)"
],
"listRule": "",
"viewRule": "id = @request.auth.id",
"createRule": "",
"updateRule": "id = @request.auth.id",
"deleteRule": null,
"options": {
"allowEmailAuth": true,
"allowOAuth2Auth": true,
"allowUsernameAuth": true,
"exceptEmailDomains": null,
"manageRule": null,
"minPasswordLength": 8,
"onlyEmailDomains": null,
"requireEmail": false
}
},
{
"id": "wuqkbeb48hlovkc",
"name": "lista",
"type": "base",
"system": false,
"schema": [
{
"id": "pmy5olxm",
"name": "nombre",
"type": "text",
"system": false,
"required": true,
"options": {
"min": 2,
"max": 50,
"pattern": ""
}
},
{
"id": "6is0rpp0",
"name": "usuarios",
"type": "relation",
"system": false,
"required": true,
"options": {
"collectionId": "_pb_users_auth_",
"cascadeDelete": true,
"minSelect": null,
"maxSelect": null,
"displayFields": []
}
},
{
"id": "a6udx3z4",
"name": "items",
"type": "relation",
"system": false,
"required": false,
"options": {
"collectionId": "g4g4sojia3p5qr2",
"cascadeDelete": true,
"minSelect": null,
"maxSelect": null,
"displayFields": [
"nombre"
]
}
}
],
"indexes": [
"CREATE INDEX `_wuqkbeb48hlovkc_created_idx` ON `lista` (`created`)"
],
"listRule": "usuarios.id ?= @request.auth.id",
"viewRule": null,
"createRule": "@request.auth.id != \"\"",
"updateRule": null,
"deleteRule": "usuarios.id ?= @request.auth.id",
"options": {}
},
{
"id": "g4g4sojia3p5qr2",
"name": "items",
"type": "base",
"system": false,
"schema": [
{
"id": "hjfkfx3u",
"name": "nombre",
"type": "text",
"system": false,
"required": true,
"options": {
"min": 2,
"max": 50,
"pattern": ""
}
},
{
"id": "4jogigzv",
"name": "cantidad",
"type": "number",
"system": false,
"required": true,
"options": {
"min": 1,
"max": 50
}
}
],
"indexes": [
"CREATE INDEX `_g4g4sojia3p5qr2_created_idx` ON `items` (`created`)",
"CREATE UNIQUE INDEX \"idx_unique_hjfkfx3u\" on \"items\" (\"nombre\")"
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
}
]

View File

@@ -1,49 +0,0 @@
<template>
<q-item
clickable
tag="a"
target="_blank"
:href="link"
>
<q-item-section
v-if="icon"
avatar
>
<q-icon :name="icon" />
</q-item-section>
<q-item-section>
<q-item-label>{{ title }}</q-item-label>
<q-item-label caption>{{ caption }}</q-item-label>
</q-item-section>
</q-item>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'EssentialLink',
props: {
title: {
type: String,
required: true
},
caption: {
type: String,
default: ''
},
link: {
type: String,
default: '#'
},
icon: {
type: String,
default: ''
}
}
})
</script>

View File

@@ -2,16 +2,10 @@
<q-layout view="lHh Lpr lFf">
<q-header elevated>
<q-toolbar>
<q-btn
flat
dense
round
icon="menu"
aria-label="Menu"
@click="toggleLeftDrawer"
/>
<q-toolbar-title> Quasar App </q-toolbar-title>
<q-toolbar-title>
{{ listaStore.pb.authStore.model.name }} -
{{ lista }}
</q-toolbar-title>
<div>
<q-btn @click="listaStore.logout()">Cerrar</q-btn>
@@ -19,18 +13,6 @@
</q-toolbar>
</q-header>
<q-drawer v-model="leftDrawerOpen" show-if-above bordered>
<q-list>
<q-item-label header> Essential Links </q-item-label>
<EssentialLink
v-for="link in essentialLink"
:key="link.title"
v-bind="link"
/>
</q-list>
</q-drawer>
<q-page-container>
<router-view />
</q-page-container>
@@ -38,58 +20,29 @@
</template>
<script setup>
import { ref } from "vue";
import EssentialLink from "components/EssentialLink.vue";
import { ref, computed } from "vue";
import { useListaStore } from "../stores/lista.js";
import { useRouter } from "vue-router";
const $router = useRouter();
const listaStore = useListaStore();
const essentialLink = [
{
title: "Docs",
caption: "quasar.dev",
icon: "school",
link: "https://quasar.dev",
},
{
title: "Github",
caption: "github.com/quasarframework",
icon: "code",
link: "https://github.com/quasarframework",
},
{
title: "Discord Chat Channel",
caption: "chat.quasar.dev",
icon: "chat",
link: "https://chat.quasar.dev",
},
{
title: "Forum",
caption: "forum.quasar.dev",
icon: "record_voice_over",
link: "https://forum.quasar.dev",
},
{
title: "Twitter",
caption: "@quasarframework",
icon: "rss_feed",
link: "https://twitter.quasar.dev",
},
{
title: "Facebook",
caption: "@QuasarFramework",
icon: "public",
link: "https://facebook.quasar.dev",
},
{
title: "Quasar Awesome",
caption: "Community Quasar projects",
icon: "favorite",
link: "https://awesome.quasar.dev",
},
];
const leftDrawerOpen = ref(false);
const toggleLeftDrawer = () => {
leftDrawerOpen.value = !leftDrawerOpen.value;
const nombreLista = ref("");
const cargarLista = async () => {
const result = await listaStore.pb
.collection("lista")
.getOne($router.currentRoute.value.params.id, { $autoCancel: false });
nombreLista.value = result.nombre;
};
const lista = computed(() => {
if ($router.currentRoute.value.path == "/") {
return "Listas";
} else if ($router.currentRoute.value.path == "/perfil") {
return "Perfil";
} else if ($router.currentRoute.value.path.includes("/lista/")) {
cargarLista();
return nombreLista.value;
} else return "";
});
</script>

View File

@@ -1,5 +1,5 @@
<template>
<div class="row q-ma-md">
<div class="row q-mt-md q-mx-md">
<div class="col">
<q-btn
round
@@ -22,7 +22,15 @@
<div class="col-2"></div>
<div class="col">
<q-form class="q-gutter-md" @click.once="recibeDatos()">
<q-file outlined v-model="imagen" label="Avatar" class="q-py-md" />
<q-file
outlined
v-model="imagen"
label="Avatar"
accept=".jpg, image/*"
max-total-size="5120000"
@rejected="noValidos"
class="q-py-md"
/>
<q-input
outlined
v-model="name"
@@ -100,7 +108,9 @@
<script setup>
import { ref, computed, onMounted } from "vue";
import { useListaStore } from "../stores/lista.js";
import { useQuasar } from "quasar";
const $q = useQuasar();
const listaStore = useListaStore();
const imagen = ref(null);
@@ -173,14 +183,23 @@ const btnSaveDisable = () => {
const refrescarImagen = () => {
if (listaStore.pb.authStore.model.avatar.length > 0) {
fuenteImagen.value = listaStore.pb.getFileUrl(
listaStore.pb.authStore.model,
listaStore.pb.authStore.model.avatar
);
fuenteImagen.value =
listaStore.pb.getFileUrl(
listaStore.pb.authStore.model,
listaStore.pb.authStore.model.avatar
) + "?thumb=100x100";
} else {
fuenteImagen.value = `https://cdn.quasar.dev/img/avatar.png`;
}
};
const noValidos = () => {
$q.dialog({
type: "negative",
message: `Sólo se permiten archivos de imágenes de 5 MB como máximo`,
});
};
onMounted(() => {
refrescarImagen();
});

View File

@@ -1,11 +1,11 @@
<template>
<div
class="flex flex-center bg-teal-7 q-ma-md cursor-pointer"
class="flex flex-center bg-teal-7 q-ma-md cursor-pointer rounded-borders"
@click="irPerfil()"
>
<h1 style="font-weight: 415">
<h3 style="font-weight: 615">
{{ listaStore.pb.authStore.model.username }}
</h1>
</h3>
</div>
<div class="flex flex-center">
<q-btn
@@ -16,19 +16,25 @@
@click="crearLista()"
/>
</div>
<div class="flex flex-center q-pt-xl q-mt-xl">
<div class="flex flex-center q-mt-xl">
<div
class="full-width text-center q-px-md"
v-for="lista in listas"
:key="lista.id"
>
<p class="bg-cyan-7 q-pa-md text-weight-bold text-h6">
{{ lista.nombre
}}<q-icon
<p class="bg-cyan-7 q-pa-md text-weight-bold text-h6 rounded-borders">
<router-link :to="`/lista/${lista.id}`">{{ lista.nombre }}</router-link
><q-icon
class="float-right cursor-pointer q-mt-xs"
name="delete"
@click="eliminarLista(lista)"
/>
<q-badge
color="cyan-5"
text-color="black"
:label="lista.items.length"
class="float-right q-ma-sm"
/>
</p>
</div>
</div>
@@ -115,3 +121,11 @@ onMounted(() => {
listListas();
});
</script>
<style scoped>
a:link,
a:visited,
a:active {
text-decoration: none;
color: black;
}
</style>

View File

@@ -0,0 +1,125 @@
<template>
<div class="row">
<div class="col q-ma-md">
<q-btn
round
color="deep-orange"
icon="keyboard_return"
@click="$router.push('/')"
/>
</div>
<div class="col-10">
<div class="q-ma-md rounded-borders">
<q-input
outlined
v-model="producto"
label="Item"
@keyup.enter="cargarProducto()"
:rules="[
(val) =>
(val != null && val.length >= 3) ||
(val != null && val.length == 0) ||
'Mínimo 3 caracteres',
]"
/>
</div>
</div>
</div>
<div class="flex flex-center q-mt-xl">
<div
v-for="item in items"
:key="item.id"
class="full-width text-center q-px-md"
>
<p
class="bg-cyan-7 q-pa-md q-mx-md text-weight-bold text-h6 rounded-borders"
>
{{ item.nombre
}}<q-icon
class="float-right cursor-pointer q-mt-xs"
name="delete"
@click="eliminarItem(item)"
/>
</p>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useListaStore } from "../../stores/lista.js";
import { useRouter } from "vue-router";
const listaStore = useListaStore();
const $router = useRouter();
const lista = ref(null);
const items = ref(null);
const producto = ref("");
const cargarProducto = async () => {
if (producto.value.length >= 3) {
// Busca en todos, si no está crea y añade
const res = await listaStore.pb.collection("items").getFullList();
const busqueda = res.filter(
(element) => element.nombre == producto.value.trim()
);
if (busqueda.length == 0) {
const data = {
nombre: producto.value.trim(),
cantidad: 1,
};
const item = await listaStore.pb.collection("items").create(data);
lista.value.items.push(item.id);
await listaStore.pb
.collection("lista")
.update(lista.value.id, lista.value);
} else {
// Si está, comprueba que no esté en la lista y lo añade
if (lista.value.items.indexOf(busqueda[0].id) == -1) {
lista.value.items.push(busqueda[0].id);
await listaStore.pb
.collection("lista")
.update(lista.value.id, lista.value);
}
}
const arrayIdItem = lista.value.items;
const result = await listaStore.pb.collection("items").getFullList();
items.value = result.filter(
(element) => arrayIdItem.indexOf(element.id) != -1
);
producto.value = "";
}
};
const eliminarItem = async (i) => {
//Lo saca de la lista
const orden = lista.value.items.indexOf(i.id);
lista.value.items.splice(orden, 1);
await listaStore.pb.collection("lista").update(lista.value.id, lista.value);
const arrayIdItem = lista.value.items;
const result = await listaStore.pb.collection("items").getFullList();
items.value = result.filter(
(element) => arrayIdItem.indexOf(element.id) != -1
);
};
onMounted(async () => {
lista.value = await listaStore.pb
.collection("lista")
.getOne($router.currentRoute.value.params.id);
const arrayIdItem = lista.value.items;
const result = await listaStore.pb.collection("items").getFullList();
items.value = result.filter(
(element) => arrayIdItem.indexOf(element.id) != -1
);
});
</script>
<style scoped>
a:link,
a:visited,
a:active {
text-decoration: none;
color: gray;
}
</style>

View File

@@ -13,6 +13,11 @@ const routes = [
component: () => import("pages/IndexPage.vue"),
meta: { auth: true },
},
{
path: 'lista/:id',
component: () => import("pages/listas/indexPage.vue"),
meta: { auth: true },
},
],
},
{