Modulo Administración #78
|
@ -130,24 +130,28 @@ onMounted(async () => {
|
||||||
v-model="formData.newPassword"
|
v-model="formData.newPassword"
|
||||||
:type="!showNewPwd ? 'password' : 'text'"
|
:type="!showNewPwd ? 'password' : 'text'"
|
||||||
:label="t('newPassword')"
|
:label="t('newPassword')"
|
||||||
><template #append>
|
>
|
||||||
|
<template #append>
|
||||||
<QIcon
|
<QIcon
|
||||||
:name="showNewPwd ? 'visibility_off' : 'visibility'"
|
:name="showNewPwd ? 'visibility_off' : 'visibility'"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="showNewPwd = !showNewPwd"
|
@click="showNewPwd = !showNewPwd"
|
||||||
/> </template
|
/>
|
||||||
></VnInput>
|
</template>
|
||||||
|
</VnInput>
|
||||||
<VnInput
|
<VnInput
|
||||||
v-model="repeatPassword"
|
v-model="repeatPassword"
|
||||||
:type="!showCopyPwd ? 'password' : 'text'"
|
:type="!showCopyPwd ? 'password' : 'text'"
|
||||||
:label="t('repeatPassword')"
|
:label="t('repeatPassword')"
|
||||||
><template #append>
|
>
|
||||||
|
<template #append>
|
||||||
<QIcon
|
<QIcon
|
||||||
:name="showCopyPwd ? 'visibility_off' : 'visibility'"
|
:name="showCopyPwd ? 'visibility_off' : 'visibility'"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
@click="showCopyPwd = !showCopyPwd"
|
@click="showCopyPwd = !showCopyPwd"
|
||||||
/> </template
|
/>
|
||||||
></VnInput>
|
</template>
|
||||||
|
</VnInput>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="isHeaderMounted" #actions>
|
<template v-if="isHeaderMounted" #actions>
|
||||||
<QBtn
|
<QBtn
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, inject } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
schema: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
imageName: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
|
const api = inject('api');
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { notify } = useNotify();
|
||||||
|
|
||||||
|
const inputFileRef = ref(null);
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const name = ref(props.imageName ?? '');
|
||||||
|
const file = ref(null);
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('name', name.value);
|
||||||
|
formData.append('image', file.value);
|
||||||
|
formData.append('schema', props.schema);
|
||||||
|
formData.append('srv', 'json:image/upload');
|
||||||
|
|
||||||
|
await api({
|
||||||
|
method: 'post',
|
||||||
|
url: location.origin,
|
||||||
|
data: formData,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
notify(t('imageAdded'), 'positive');
|
||||||
|
emit('close');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error uploading image:', error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<QForm @submit="onSubmit">
|
||||||
|
<QCard class="q-pa-lg">
|
||||||
|
<VnInput v-model="name" :label="t('name')" />
|
||||||
|
<QFile
|
||||||
|
ref="inputFileRef"
|
||||||
|
:label="t('file')"
|
||||||
|
v-model="file"
|
||||||
|
:multiple="false"
|
||||||
|
class="q-mb-xs"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QIcon
|
||||||
|
name="attach_file"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="inputFileRef.pickFiles()"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</QFile>
|
||||||
|
<div class="flex row justify-end q-gutter-x-sm">
|
||||||
|
<QSpinner
|
||||||
|
v-if="loading"
|
||||||
|
color="primary"
|
||||||
|
size="3em"
|
||||||
|
:thickness="2"
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
v-else
|
||||||
|
type="submit"
|
||||||
|
:label="t('send')"
|
||||||
|
flat
|
||||||
|
class="q-mt-md"
|
||||||
|
rounded
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</QCard>
|
||||||
|
</QForm>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
||||||
|
<i18n lang="yaml">
|
||||||
|
en-US:
|
||||||
|
name: Name
|
||||||
|
file: File
|
||||||
|
send: Send
|
||||||
|
es-ES:
|
||||||
|
name: Nombre
|
||||||
|
file: Archivo
|
||||||
|
send: Enviar
|
||||||
|
ca-ES:
|
||||||
|
name: Nom
|
||||||
|
file: Arxiu
|
||||||
|
send: Enviar
|
||||||
|
fr-FR:
|
||||||
|
name: Nom
|
||||||
|
file: Fichier
|
||||||
|
send: Envoyer
|
||||||
|
pt-PT:
|
||||||
|
name: Nome
|
||||||
|
file: Arquivo
|
||||||
|
send: Enviar
|
||||||
|
</i18n>
|
|
@ -2,6 +2,8 @@
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import { useAppStore } from 'stores/app';
|
import { useAppStore } from 'stores/app';
|
||||||
|
|
||||||
|
import ImageEditor from 'src/components/ui/ImageEditor.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
baseURL: {
|
baseURL: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -27,23 +29,67 @@ const props = defineProps({
|
||||||
rounded: {
|
rounded: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
fullRounded: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
editable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
editSchema: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
editImageName: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
const show = ref(false);
|
const showZoom = ref(false);
|
||||||
|
const showEditForm = ref(false);
|
||||||
const url = computed(() => {
|
const url = computed(() => {
|
||||||
return `${props.baseURL ?? app.imageUrl}/${props.storage}/${props.size}/${props.id}`;
|
return `${props.baseURL ?? app.imageUrl}/${props.storage}/${props.size}/${props.id}`;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<QImg
|
<div class="relative-position main-image-container">
|
||||||
:class="{ zoomIn: props.zoomSize, rounded: props.rounded }"
|
<QBtn
|
||||||
:src="url"
|
v-if="props.editable"
|
||||||
v-bind="$attrs"
|
icon="add_a_photo"
|
||||||
@click="show = !show"
|
class="show-edit-button absolute-top-left"
|
||||||
spinner-color="primary"
|
round
|
||||||
/>
|
text-color="black"
|
||||||
<QDialog v-model="show" v-if="props.zoomSize">
|
@click.stop="showEditForm = !showEditForm"
|
||||||
|
/>
|
||||||
|
<QImg
|
||||||
|
:class="{
|
||||||
|
zoomIn: props.zoomSize,
|
||||||
|
rounded: props.rounded,
|
||||||
|
'full-rounded': props.fullRounded
|
||||||
|
}"
|
||||||
|
class="main-image"
|
||||||
|
:src="url"
|
||||||
|
v-bind="$attrs"
|
||||||
|
@click="showZoom = !showZoom"
|
||||||
|
spinner-color="primary"
|
||||||
|
:width="props.width"
|
||||||
|
:height="props.height"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<QDialog v-if="props.zoomSize" v-model="showZoom">
|
||||||
<QImg
|
<QImg
|
||||||
:src="url"
|
:src="url"
|
||||||
size="full"
|
size="full"
|
||||||
|
@ -52,17 +98,47 @@ const url = computed(() => {
|
||||||
spinner-color="primary"
|
spinner-color="primary"
|
||||||
/>
|
/>
|
||||||
</QDialog>
|
</QDialog>
|
||||||
|
<QDialog v-if="props.editable" v-model="showEditForm">
|
||||||
|
<ImageEditor
|
||||||
|
class="all-pointer-events"
|
||||||
|
:schema="props.editSchema"
|
||||||
|
:imageName="props.editImageName"
|
||||||
|
@close="showEditForm = false"
|
||||||
|
/>
|
||||||
|
</QDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.q-img {
|
.main-image-container {
|
||||||
|
&:hover {
|
||||||
|
.show-edit-button {
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
.main-image {
|
||||||
|
filter: brightness(80%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-image {
|
||||||
&.zoomIn {
|
&.zoomIn {
|
||||||
cursor: zoom-in;
|
cursor: zoom-in;
|
||||||
}
|
}
|
||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.show-edit-button {
|
||||||
|
visibility: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: $gray-light;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.rounded {
|
.rounded {
|
||||||
border-radius: 50%;
|
border-radius: 0.6em;
|
||||||
|
}
|
||||||
|
.full-rounded {
|
||||||
|
border-radius: 50px;
|
||||||
}
|
}
|
||||||
.img_zoom {
|
.img_zoom {
|
||||||
border-radius: 0%;
|
border-radius: 0%;
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref, inject } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import VnInput from 'src/components/common/VnInput.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
searchFn: {
|
||||||
|
type: Function,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: 'Search'
|
||||||
|
},
|
||||||
|
sqlQuery: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
searchField: {
|
||||||
|
type: String,
|
||||||
|
default: 'search'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['onSearch', 'onSearchError']);
|
||||||
|
|
||||||
|
const jApi = inject('jApi');
|
||||||
|
const { t } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const searchTerm = ref('');
|
||||||
|
|
||||||
|
const search = async () => {
|
||||||
|
try {
|
||||||
|
let data = null;
|
||||||
|
router.replace({
|
||||||
|
query: searchTerm.value ? { search: searchTerm.value } : {}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (props.sqlQuery) {
|
||||||
|
data = await jApi.query(props.sqlQuery, {
|
||||||
|
[props.searchField]: searchTerm.value
|
||||||
|
});
|
||||||
|
} else if (props.searchFn) {
|
||||||
|
data = props.searchFn(searchTerm.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('onSearch', data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error searching:', error);
|
||||||
|
emit('onSearchError');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (route.query.search) {
|
||||||
|
searchTerm.value = route.query.search;
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<VnInput
|
||||||
|
v-model="searchTerm"
|
||||||
|
@keyup.enter="search()"
|
||||||
|
:placeholder="props.placeholder || t('search')"
|
||||||
|
bg-color="white"
|
||||||
|
isOutlined
|
||||||
|
:clearable="false"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<QIcon name="search" class="cursor-pointer" @click="search()" />
|
||||||
|
</template>
|
||||||
|
</VnInput>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n lang="yaml">
|
||||||
|
en-US:
|
||||||
|
search: Search
|
||||||
|
es-ES:
|
||||||
|
search: Buscar
|
||||||
|
ca-ES:
|
||||||
|
search: Cercar
|
||||||
|
fr-FR:
|
||||||
|
search: Rechercher
|
||||||
|
pt-PT:
|
||||||
|
search: Pesquisar
|
||||||
|
</i18n>
|
|
@ -53,7 +53,12 @@ export default {
|
||||||
checkout: 'Configurar encàrrec',
|
checkout: 'Configurar encàrrec',
|
||||||
controlPanel: 'Panell de control',
|
controlPanel: 'Panell de control',
|
||||||
adminConnections: 'Connexions',
|
adminConnections: 'Connexions',
|
||||||
|
adminItems: 'Articles',
|
||||||
|
adminVisits: 'Visites',
|
||||||
|
adminUsers: "Gestió d'usuaris",
|
||||||
|
adminPhotos: 'Imatges',
|
||||||
//
|
//
|
||||||
orderLoadedIntoBasket: 'Comanda carregada a la cistella!',
|
orderLoadedIntoBasket: 'Comanda carregada a la cistella!',
|
||||||
at: 'a les'
|
at: 'a les',
|
||||||
|
imageAdded: 'Imatge afegida correctament'
|
||||||
};
|
};
|
||||||
|
|
|
@ -66,9 +66,14 @@ export default {
|
||||||
checkout: 'Configure order',
|
checkout: 'Configure order',
|
||||||
controlPanel: 'Control panel',
|
controlPanel: 'Control panel',
|
||||||
adminConnections: 'Connections',
|
adminConnections: 'Connections',
|
||||||
|
adminItems: 'Items',
|
||||||
|
adminVisits: 'Visits',
|
||||||
|
adminUsers: 'User management',
|
||||||
|
adminPhotos: 'Images',
|
||||||
//
|
//
|
||||||
orderLoadedIntoBasket: 'Order loaded into basket!',
|
orderLoadedIntoBasket: 'Order loaded into basket!',
|
||||||
at: 'at',
|
at: 'at',
|
||||||
|
imageAdded: 'Image added successfully',
|
||||||
|
|
||||||
orders: 'Orders',
|
orders: 'Orders',
|
||||||
order: 'Pending order',
|
order: 'Pending order',
|
||||||
|
|
|
@ -72,6 +72,10 @@ export default {
|
||||||
checkout: 'Configurar pedido',
|
checkout: 'Configurar pedido',
|
||||||
controlPanel: 'Panel de control',
|
controlPanel: 'Panel de control',
|
||||||
adminConnections: 'Conexiones',
|
adminConnections: 'Conexiones',
|
||||||
|
adminItems: 'Artículos',
|
||||||
|
adminVisits: 'Visitas',
|
||||||
|
adminUsers: 'Gestión de usuarios',
|
||||||
|
adminPhotos: 'Imágenes',
|
||||||
//
|
//
|
||||||
orderLoadedIntoBasket: '¡Pedido cargado en la cesta!',
|
orderLoadedIntoBasket: '¡Pedido cargado en la cesta!',
|
||||||
at: 'a las',
|
at: 'a las',
|
||||||
|
|
|
@ -53,7 +53,12 @@ export default {
|
||||||
checkout: "Définissez l'ordre",
|
checkout: "Définissez l'ordre",
|
||||||
controlPanel: 'Panneau de configuration',
|
controlPanel: 'Panneau de configuration',
|
||||||
adminConnections: 'Connexions',
|
adminConnections: 'Connexions',
|
||||||
|
adminItems: 'Articles',
|
||||||
|
adminVisits: 'Visites',
|
||||||
|
adminUsers: 'Gestion des utilisateurs',
|
||||||
|
adminPhotos: 'Images',
|
||||||
//
|
//
|
||||||
orderLoadedIntoBasket: 'Commande chargée dans le panier!',
|
orderLoadedIntoBasket: 'Commande chargée dans le panier!',
|
||||||
at: 'à'
|
at: 'à',
|
||||||
|
imageAdded: 'Image ajoutée correctement'
|
||||||
};
|
};
|
||||||
|
|
|
@ -54,7 +54,12 @@ export default {
|
||||||
checkout: 'Configurar encomenda',
|
checkout: 'Configurar encomenda',
|
||||||
controlPanel: 'Painel de controle',
|
controlPanel: 'Painel de controle',
|
||||||
adminConnections: 'Conexões',
|
adminConnections: 'Conexões',
|
||||||
|
adminItems: 'Artigos',
|
||||||
|
adminVisits: 'Visitas',
|
||||||
|
adminUsers: 'Gestão de usuários',
|
||||||
|
adminPhotos: 'Imagens',
|
||||||
//
|
//
|
||||||
orderLoadedIntoBasket: 'Pedido carregado na cesta!',
|
orderLoadedIntoBasket: 'Pedido carregado na cesta!',
|
||||||
at: 'às'
|
at: 'às',
|
||||||
|
imageAdded: 'Imagen adicionada corretamente'
|
||||||
};
|
};
|
||||||
|
|
|
@ -50,6 +50,7 @@ const supplantUser = async user => {
|
||||||
console.error('Error supplanting user:', error);
|
console.error('Error supplanting user:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
getConnections();
|
getConnections();
|
||||||
intervalId.value = setInterval(getConnections, 60000);
|
intervalId.value = setInterval(getConnections, 60000);
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import CardList from 'src/components/ui/CardList.vue';
|
||||||
|
import VnImg from 'src/components/ui/VnImg.vue';
|
||||||
|
import VnSearchBar from 'src/components/ui/VnSearchBar.vue';
|
||||||
|
|
||||||
|
import { useAppStore } from 'stores/app';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const { isHeaderMounted } = storeToRefs(appStore);
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const items = ref([]);
|
||||||
|
|
||||||
|
const query = `SELECT i.id, i.longName, i.size, i.category,
|
||||||
|
i.value5, i.value6, i.value7,
|
||||||
|
i.image, im.updated
|
||||||
|
FROM vn.item i
|
||||||
|
LEFT JOIN image im
|
||||||
|
ON im.collectionFk = 'catalog'
|
||||||
|
AND im.name = i.image
|
||||||
|
WHERE i.longName LIKE CONCAT('%', #search, '%')
|
||||||
|
OR i.id = #search
|
||||||
|
ORDER BY i.longName LIMIT 50`;
|
||||||
|
|
||||||
|
const onSearch = data => (items.value = data || []);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Teleport v-if="isHeaderMounted" to="#actions">
|
||||||
|
<VnSearchBar
|
||||||
|
:sqlQuery="query"
|
||||||
|
@onSearch="onSearch"
|
||||||
|
@onSearchError="items = []"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
|
<QPage class="vn-w-xs">
|
||||||
|
<QList class="flex justify-center">
|
||||||
|
<span v-if="!loading && !items.length" class="flex items-center">
|
||||||
|
<QIcon name="refresh" size="sm" class="q-mr-sm" />
|
||||||
|
{{ t('introduceSearchTerm') }}
|
||||||
|
</span>
|
||||||
|
<QSpinner
|
||||||
|
v-if="loading"
|
||||||
|
color="primary"
|
||||||
|
size="3em"
|
||||||
|
:thickness="2"
|
||||||
|
/>
|
||||||
|
<CardList
|
||||||
|
v-else
|
||||||
|
v-for="(item, index) in items"
|
||||||
|
:key="index"
|
||||||
|
:clickable="false"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<VnImg
|
||||||
|
storage="catalog"
|
||||||
|
size="200x200"
|
||||||
|
:id="item.id"
|
||||||
|
width="80px"
|
||||||
|
height="80px"
|
||||||
|
class="q-mr-md"
|
||||||
|
rounded
|
||||||
|
editable
|
||||||
|
editSchema="catalog"
|
||||||
|
:editImageName="item.image"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<span class="text-bold q-mb-sm">
|
||||||
|
{{ item.longName }}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{{ item.value5 }} {{ item.value6 }}
|
||||||
|
{{ item.value7 }}
|
||||||
|
</span>
|
||||||
|
<span>{{ item.id }}</span>
|
||||||
|
<span>{{ item.image }}</span>
|
||||||
|
</template>
|
||||||
|
</CardList>
|
||||||
|
</QList>
|
||||||
|
</QPage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<i18n lang="yaml">
|
||||||
|
en-US:
|
||||||
|
introduceSearchTerm: Enter a search term
|
||||||
|
es-ES:
|
||||||
|
introduceSearchTerm: Introduce un término de búsqueda
|
||||||
|
ca-ES:
|
||||||
|
introduceSearchTerm: Introdueix un terme de cerca
|
||||||
|
fr-FR:
|
||||||
|
introduceSearchTerm: Entrez un terme de recherche
|
||||||
|
pt-PT:
|
||||||
|
introduceSearchTerm: Digite um termo de pesquisa
|
||||||
|
</i18n>
|
|
@ -1,36 +1,102 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, inject } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const jApi = inject('jApi');
|
import CardList from 'src/components/ui/CardList.vue';
|
||||||
|
import VnSearchBar from 'src/components/ui/VnSearchBar.vue';
|
||||||
|
|
||||||
|
import { useAppStore } from 'stores/app';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useUserStore } from 'stores/user';
|
||||||
|
import useNotify from 'src/composables/useNotify.js';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const { isHeaderMounted } = storeToRefs(appStore);
|
||||||
|
const { notify } = useNotify();
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
const users = ref([]);
|
const users = ref([]);
|
||||||
|
|
||||||
const getUsers = async () => {
|
const query = `SELECT u.id, u.name, u.nickname, u.active
|
||||||
|
FROM account.user u
|
||||||
|
WHERE u.name LIKE CONCAT('%', #user, '%')
|
||||||
|
OR u.nickname LIKE CONCAT('%', #user, '%')
|
||||||
|
OR u.id = #user
|
||||||
|
ORDER BY u.name LIMIT 200`;
|
||||||
|
|
||||||
|
const onSearch = data => (users.value = data || []);
|
||||||
|
|
||||||
|
const supplantUser = async user => {
|
||||||
try {
|
try {
|
||||||
users.value = await jApi.query(
|
await userStore.supplantUser(user);
|
||||||
`SELECT u.id, u.name, u.nickname, u.active
|
await appStore.getMenuLinks();
|
||||||
FROM account.user u
|
router.push({ name: 'confirmedOrders' });
|
||||||
WHERE u.name LIKE CONCAT('%', #user, '%')
|
|
||||||
OR u.nickname LIKE CONCAT('%', #user, '%')
|
|
||||||
OR u.id = #user
|
|
||||||
ORDER BY u.name LIMIT 200`,
|
|
||||||
{ user: 9 }
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting users:', error);
|
console.error('Error supplanting user:', error);
|
||||||
|
notify(error.message, 'negative');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
onMounted(async () => getUsers());
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<QPage>
|
<Teleport v-if="isHeaderMounted" to="#actions">
|
||||||
|
<VnSearchBar
|
||||||
|
:sqlQuery="query"
|
||||||
|
searchField="user"
|
||||||
|
@onSearch="onSearch"
|
||||||
|
@onSearchError="users = []"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
|
<QPage class="vn-w-xs">
|
||||||
<QList class="flex justify-center">
|
<QList class="flex justify-center">
|
||||||
<!-- TODO: WIP -->
|
<span v-if="!loading && !users.length" class="flex items-center">
|
||||||
|
<QIcon name="refresh" size="sm" class="q-mr-sm" />
|
||||||
|
{{ t('noData') }}
|
||||||
|
</span>
|
||||||
|
<QSpinner
|
||||||
|
v-if="loading"
|
||||||
|
color="primary"
|
||||||
|
size="3em"
|
||||||
|
:thickness="2"
|
||||||
|
/>
|
||||||
|
<CardList
|
||||||
|
v-else
|
||||||
|
v-for="(user, index) in users"
|
||||||
|
:key="index"
|
||||||
|
:clickable="false"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<span class="text-bold q-mb-sm">
|
||||||
|
{{ user.nickname }}
|
||||||
|
</span>
|
||||||
|
<span>#{{ user.id }} - {{ user.name }} </span>
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<QBtn
|
||||||
|
icon="people"
|
||||||
|
|||||||
|
flat
|
||||||
|
rounded
|
||||||
|
@click="supplantUser(user.name)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</CardList>
|
||||||
</QList>
|
</QList>
|
||||||
</QPage>
|
</QPage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<i18n lang="yaml">
|
||||||
|
en-US:
|
||||||
<i18n lang="yaml"></i18n>
|
noData: No data
|
||||||
|
es-ES:
|
||||||
|
noData: Sin datos
|
||||||
|
ca-ES:
|
||||||
|
noData: Sense dades
|
||||||
|
fr-FR:
|
||||||
|
noData: Aucune donnée
|
||||||
|
pt-PT:
|
||||||
|
noData: Sem dados
|
||||||
|
</i18n>
|
||||||
|
|
|
@ -91,13 +91,13 @@ const routes = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'adminPhotos',
|
name: 'adminPhotos',
|
||||||
path: 'admin/photos'
|
path: 'admin/photos',
|
||||||
// component: () => import('pages/Admin/PhotosView.vue')
|
component: () => import('pages/Admin/PhotosView.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'adminItems',
|
name: 'adminItems',
|
||||||
path: 'admin/items'
|
path: 'admin/items',
|
||||||
// component: () => import('pages/Admin/ItemsView.vue')
|
component: () => import('pages/Admin/ItemsView.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue
tooltip
053b9f8457