Fixes primera revisión #86

Merged
jsegarra merged 13 commits from wbuezas/hedera-web-mindshore:bugfix/fixes-primera-review into 4922-vueMigration 2024-09-23 21:18:13 +00:00
16 changed files with 395 additions and 52 deletions

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
const props = defineProps({ const props = defineProps({
clickable: { type: Boolean, default: true }, clickable: { type: Boolean, default: true },
rounded: { type: Boolean, default: true } rounded: { type: Boolean, default: false }
}); });
const emit = defineEmits(['click']); const emit = defineEmits(['click']);
@ -18,17 +18,23 @@ const handleClick = () => {
v-bind="$attrs" v-bind="$attrs"
v-ripple="clickable" v-ripple="clickable"
:clickable="clickable" :clickable="clickable"
class="full-width row items-center justify-between card no-border-radius bg-white" class="full-width row items-center justify-between card bg-white"
:class="{ 'cursor-pointer': clickable, 'no-radius': !rounded }" :class="[
rounded ? 'default-radius' : 'no-radius',
{ 'cursor-pointer': clickable }
]"
@click="handleClick()" @click="handleClick()"
> >
<div class="no-padding content-container col-10"> <div
class="no-padding content-container"
:class="$slots.actions ? 'col-9' : 'full-width'"
>
<slot name="prepend" /> <slot name="prepend" />
<div class="content"> <div class="content">
<slot name="content" /> <slot name="content" />
</div> </div>
</div> </div>
<div class="no-padding flex full-width justify-center"> <div class="no-padding flex column justify-center">
<slot name="actions" /> <slot name="actions" />
</div> </div>
</QItem> </QItem>

View File

@ -93,7 +93,7 @@ const url = computed(() => {
class="main-image" class="main-image"
:src="url" :src="url"
v-bind="$attrs" v-bind="$attrs"
@click="showZoom = !showZoom" @click.stop.prevent="showZoom = !showZoom"
spinner-color="primary" spinner-color="primary"
:width="props.width" :width="props.width"
:height="props.height" :height="props.height"

View File

@ -64,7 +64,7 @@ export default {
Users: 'Usuaris', Users: 'Usuaris',
Connections: 'Connexions', Connections: 'Connexions',
Visits: 'Visites', Visits: 'Visites',
News: 'Notícies', News: 'Gestió de noticies',
Photos: 'Imatges', Photos: 'Imatges',
Images: 'Imatges', Images: 'Imatges',
Items: 'Articles', Items: 'Articles',
@ -78,6 +78,7 @@ export default {
Checkout: `Configurar l'encarrec`, Checkout: `Configurar l'encarrec`,
'Address details': 'Configuració', 'Address details': 'Configuració',
'Admin news details': `Afegir o editar notícia`, 'Admin news details': `Afegir o editar notícia`,
'Access log': 'Registre daccés',
// //
orderLoadedIntoBasket: 'Comanda carregada a la cistella!', orderLoadedIntoBasket: 'Comanda carregada a la cistella!',
loadAnOrder: loadAnOrder:
@ -92,7 +93,6 @@ export default {
delete: 'Esborrar', delete: 'Esborrar',
confirmDelete: 'Estàs segur que vols esborrar la línia?', confirmDelete: 'Estàs segur que vols esborrar la línia?',
emptyList: 'Llista buida', emptyList: 'Llista buida',
logInAsGuest: `Accedir com a convidat`, logInAsGuest: `Accedir com a convidat`,
haveForgottenPassword: '¿Has oblidat la teva contrasenya?', haveForgottenPassword: '¿Has oblidat la teva contrasenya?',
signUp: 'Registrar-me', signUp: 'Registrar-me',
@ -102,5 +102,34 @@ export default {
remindMe: "Recorda'm", remindMe: "Recorda'm",
user: 'Usuari', user: 'Usuari',
password: 'Contrasenya', password: 'Contrasenya',
modify: 'Modificar' modify: 'Modificar',
// Image related translations
'Cant lock cache': 'No es pot bloquejar la memòria cau',
'Bad file format': 'Format de fitxer no reconegut',
'File not choosed': 'No has seleccionat cap fitxer',
'Permission denied': 'No tens permís per pujar el fitxer',
'File upload error':
"No s'ha pogut pujar el fitxer, comprova que la mida no sigui massa gran",
'File save error': "No s'ha pogut desar el fitxer: %s",
'File size error': 'El fitxer no ha de superar %.2f MB',
'Bad file name':
"El nom del fitxer només ha de contenir lletres minúscules, dígits o el caràcter '_'",
'Bad collection name': 'Nom de col·lecció no vàlid',
'Collection not exists': 'La col·lecció no existeix',
'Unreferenced file': 'El fitxer no està referenciat per la base de dades',
'Cannot update matching id': "No es pot actualitzar l'id coincident",
'Com error': 'Error de comunicació amb el servidor',
'Image open error': "Error en obrir el fitxer d'imatge",
'Operation disabled': 'Operació deshabilitada per seguretat',
'Image added': 'Imatge afegida correctament',
ErrIniSize: 'El fitxer supera la directiva upload_max_filesize a php.ini',
ErrFormSize:
'El fitxer supera el MAX_FILE_SIZE especificat al formulari HTML',
ErrPartial: "El fitxer s'ha pujat parcialment",
ErrNoFile: "No s'ha pujat cap fitxer",
ErrNoTmpDir: 'Falta una carpeta temporal',
ErrCantWrite: "No s'ha pogut escriure el fitxer al disc",
ErrExtension: "La pujada del fitxer s'ha aturat per una extensió",
ErrDefault: 'Error de pujada desconegut',
'Sync complete': 'Sincronització completa'
}; };

View File

@ -76,7 +76,7 @@ export default {
Users: 'Users', Users: 'Users',
Connections: 'Connections', Connections: 'Connections',
Visits: 'Visits', Visits: 'Visits',
News: 'News', News: 'News management',
Photos: 'Images', Photos: 'Images',
Images: 'Images', Images: 'Images',
Items: 'Items', Items: 'Items',
@ -90,6 +90,7 @@ export default {
Checkout: 'Configure order', Checkout: 'Configure order',
'Address details': 'Configuration', 'Address details': 'Configuration',
'Admin news details': 'Add or edit new', 'Admin news details': 'Add or edit new',
'Access log': 'Access log',
// //
orderLoadedIntoBasket: 'Order loaded into basket!', orderLoadedIntoBasket: 'Order loaded into basket!',
loadAnOrder: 'Please load a pending order to the cart or start a new one', loadAnOrder: 'Please load a pending order to the cart or start a new one',
@ -134,5 +135,33 @@ export default {
loginMail: "{'info'}{'@'}{'verdnatura.es'}", loginMail: "{'info'}{'@'}{'verdnatura.es'}",
remindMe: 'Remember me', remindMe: 'Remember me',
password: 'Password', password: 'Password',
modify: 'Modify' modify: 'Modify',
// Image related translations
'Cant lock cache': 'The cache could not be blocked',
'Bad file format': 'Unrecognized file format',
'File not choosed': 'You have not selected any file',
'Permission denied': 'You are not allowed to upload the file',
'File upload error':
'Failed to upload the file, check that size is not too large',
'File save error': 'Failed to save the file: %s',
'File size error': 'The file must be no longer than %.2f MB',
'Bad file name':
"The file name must contain only lowercase letters, digits or the '_' character",
'Bad collection name': 'Invalid collection name',
'Collection not exists': 'Collection does not exist',
'Unreferenced file': 'The file is not referenced by the database',
'Cannot update matching id': 'Cannot update matching id',
'Com error': 'Error communicating with the server',
'Image open error': 'Error opening the image file',
'Operation disabled': 'Operation disabled for security',
'Image added': 'Image added correctly',
ErrIniSize: 'File exceeds the upload_max_filesize directive in php.ini',
ErrFormSize: 'File exceeds the MAX_FILE_SIZE specified in the HTML form',
ErrPartial: 'File was partially uploaded',
ErrNoFile: 'No file was uploaded',
ErrNoTmpDir: 'Missing a temporary folder',
ErrCantWrite: 'Failed to write file to disk',
ErrExtension: 'File upload stopped by extension',
ErrDefault: 'Unknown upload error',
'Sync complete': 'Synchronization complete'
}; };

View File

@ -73,7 +73,7 @@ export default {
Users: 'Usuarios', Users: 'Usuarios',
Connections: 'Conexiones', Connections: 'Conexiones',
Visits: 'Visitas', Visits: 'Visitas',
News: 'Noticias', News: 'Gestión de noticias',
Photos: 'Imágenes', Photos: 'Imágenes',
Images: 'Imágenes', Images: 'Imágenes',
Items: 'Artículos', Items: 'Artículos',
@ -87,6 +87,7 @@ export default {
Checkout: 'Configurar pedido', Checkout: 'Configurar pedido',
'Address details': 'Configuración', 'Address details': 'Configuración',
'Admin news details': 'Añadir o editar noticia', 'Admin news details': 'Añadir o editar noticia',
'Access log': 'Registro de accesos',
// //
orderLoadedIntoBasket: '¡Pedido cargado en la cesta!', orderLoadedIntoBasket: '¡Pedido cargado en la cesta!',
loadAnOrder: loadAnOrder:
@ -133,5 +134,34 @@ export default {
save: 'Guardar', save: 'Guardar',
cancel: 'Cancelar', cancel: 'Cancelar',
of: 'de', of: 'de',
modify: 'Modificar' modify: 'Modificar',
// Image related translations
'Cant lock cache': 'La caché no pudo ser bloqueada',
'Bad file format': 'Formato de archivo no reconocido',
'File not choosed': 'No has seleccionado ningún archivo',
'Permission denied': 'No tienes permiso para subir el fichero',
'File upload error':
'Error al subir el fichero, comprueba que su tamaño no sea demasiado grande',
'File save error': 'Error al guardar el fichero: %s',
'File size error': 'El fichero no debe ocupar más de %.2f MB',
'Bad file name':
"El nombre del archivo solo debe contener letras minúsculas, dígitos o el carácter '_'",
'Bad collection name': 'Nombre de colección no válido',
'Collection not exists': 'La colección no existe',
'Unreferenced file': 'El archivo no está referenciado por la base de datos',
'Cannot update matching id':
'No es posible actualizar los ítems con id coincidente',
'Com error': 'Error en la comunicación con el servidor',
'Image open error': 'Error al abrir el archivo de imagen',
'Operation disabled': 'Operación deshabilitada por seguridad',
'Image added': 'Imagen añadida correctamente',
ErrIniSize: 'File exceeds the upload_max_filesize directive in php.ini',
ErrFormSize: 'File exceeds the MAX_FILE_SIZE specified in the HTML form',
ErrPartial: 'File was partially uploaded',
ErrNoFile: 'No file was uploaded',
ErrNoTmpDir: 'Missing a temporary folder',
ErrCantWrite: 'Failed to write file to disk',
ErrExtension: 'File upload stopped by extension',
ErrDefault: 'Unknown upload error',
'Sync complete': 'Sincronización completada'
}; };

View File

@ -64,7 +64,7 @@ export default {
Users: 'Utilisateurs', Users: 'Utilisateurs',
Connections: 'Connexions', Connections: 'Connexions',
Visits: 'Visites', Visits: 'Visites',
News: 'Nouvelles', News: 'Gestion des nouvelles',
Photos: 'Images', Photos: 'Images',
Images: 'Images', Images: 'Images',
Items: 'Articles', Items: 'Articles',
@ -78,6 +78,7 @@ export default {
Checkout: 'Configurer la commande', Checkout: 'Configurer la commande',
'Address details': 'Configuration', 'Address details': 'Configuration',
'Admin news details': 'Ajouter ou éditer une nouvelle', 'Admin news details': 'Ajouter ou éditer une nouvelle',
'Access log': "Journal d'accès",
// //
orderLoadedIntoBasket: 'Commande chargée dans le panier!', orderLoadedIntoBasket: 'Commande chargée dans le panier!',
loadAnOrder: loadAnOrder:
@ -90,9 +91,8 @@ export default {
noData: 'Aucune donnée', noData: 'Aucune donnée',
confirm: 'Confirmer', confirm: 'Confirmer',
delete: 'Effacer', delete: 'Effacer',
emptyList: 'Vider la liste',
confirmDelete: 'Voulez-vous vraiment supprimer la ligne?', confirmDelete: 'Voulez-vous vraiment supprimer la ligne?',
emptyList: 'Liste vide',
logInAsGuest: `Entrez en tant qu'invité`, logInAsGuest: `Entrez en tant qu'invité`,
haveForgottenPassword: 'Avez-vous oublié votre mot de passe?', haveForgottenPassword: 'Avez-vous oublié votre mot de passe?',
signUp: `S'inscrire`, signUp: `S'inscrire`,
@ -102,5 +102,37 @@ export default {
remindMe: `Rappelle-moi`, remindMe: `Rappelle-moi`,
user: 'Utilisateur', user: 'Utilisateur',
password: 'Mot de passe', password: 'Mot de passe',
modify: 'Modifier' modify: 'Modifier',
// Image related translations
'Cant lock cache': "Le cache n'a pas pu être verrouillé",
'Bad file format': 'Format de fichier non reconnu',
'File not choosed': "Vous n'avez sélectionné aucun fichier",
'Permission denied': "Vous n'êtes pas autorisé à télécharger le fichier",
'File upload error':
"Échec du téléchargement du fichier, vérifiez que la taille n'est pas trop grande",
'File save error': "Échec de l'enregistrement du fichier : %s",
'File size error': 'Le fichier ne doit pas dépasser %.2f MB',
'Bad file name':
"Le nom du fichier ne doit contenir que des lettres minuscules, des chiffres ou le caractère '_'",
'Bad collection name': 'Nom de collection invalide',
'Collection not exists': "La collection n'existe pas",
'Unreferenced file':
"Le fichier n'est pas référencé par la base de données",
'Cannot update matching id':
"Impossible de mettre à jour l'ID correspondant",
'Com error': 'Erreur de communication avec le serveur',
'Image open error': "Erreur lors de l'ouverture du fichier image",
'Operation disabled': 'Opération désactivée pour des raisons de sécurité',
'Image added': 'Image ajoutée correctement',
ErrIniSize:
'Le fichier dépasse la directive upload_max_filesize dans php.ini',
ErrFormSize:
'Le fichier dépasse la taille maximale spécifiée dans le formulaire HTML',
ErrPartial: 'Le fichier a été partiellement téléchargé',
ErrNoFile: "Aucun fichier n'a été téléchargé",
ErrNoTmpDir: 'Il manque un dossier temporaire',
ErrCantWrite: "Échec de l'écriture du fichier sur le disque",
ErrExtension: 'Téléchargement du fichier arrêté par extension',
ErrDefault: 'Erreur de téléchargement inconnue',
'Sync complete': 'Synchronisation terminée'
}; };

View File

@ -63,7 +63,7 @@ export default {
Users: 'Usuários', Users: 'Usuários',
Connections: 'Conexões', Connections: 'Conexões',
Visits: 'Visitas', Visits: 'Visitas',
News: 'Notícias', News: 'Gestão de noticias',
Photos: 'Imagens', Photos: 'Imagens',
Images: 'Imagens', Images: 'Imagens',
Items: 'Artigos', Items: 'Artigos',
@ -77,6 +77,7 @@ export default {
Checkout: 'Configurar encomenda', Checkout: 'Configurar encomenda',
'Address details': 'Configuração', 'Address details': 'Configuração',
'Admin news details': 'Adicionar ou editar notícia', 'Admin news details': 'Adicionar ou editar notícia',
'Access log': 'Registo de acessos',
// //
orderLoadedIntoBasket: 'Pedido carregado na cesta!', orderLoadedIntoBasket: 'Pedido carregado na cesta!',
loadAnOrder: 'Carregue um pedido pendente no carrinho ou inicie um novo', loadAnOrder: 'Carregue um pedido pendente no carrinho ou inicie um novo',
@ -90,7 +91,6 @@ export default {
delete: 'Eliminar', delete: 'Eliminar',
confirmDelete: 'Tens certeza que queres eliminar esta linha?', confirmDelete: 'Tens certeza que queres eliminar esta linha?',
emptyList: 'Lista vazia', emptyList: 'Lista vazia',
logInAsGuest: 'Entrar como convidado', logInAsGuest: 'Entrar como convidado',
haveForgottenPassword: 'Esqueceu a senha?', haveForgottenPassword: 'Esqueceu a senha?',
signUp: 'Registar', signUp: 'Registar',
@ -100,5 +100,33 @@ export default {
remindMe: 'Lembrar-me', remindMe: 'Lembrar-me',
user: 'Utilizador', user: 'Utilizador',
password: 'Senha', password: 'Senha',
modify: 'Modificar' modify: 'Modificar',
// Image related translations
'Cant lock cache': 'O cache não pôde ser bloqueado',
'Bad file format': 'Formato de arquivo inválido',
'File not choosed': 'Não selecionastes nenhum arquivo',
'Permission denied': 'Não estas autorizado a subir o arquivo',
'File upload error': 'Erro ao subir o arquivo, verifique o tamanho',
'File save error': 'Erro ao salvar o arquivo: %s',
'File size error': 'O arquivo não deve ser maior que %.2f MB',
'Bad file name':
"O nome do arquivo deve conter somente letras minusculas, numeros ou '_'",
'Bad collection name': 'Nome de coleção inválido',
'Collection not exists': 'Coleção não existe',
'Unreferenced file': 'O arquivo não é referenciado pelo banco de dados',
'Cannot update matching id':
'Não é possível atualizar os itens com id coincidente',
'Com error': 'Erro de comunicação com o servidor',
'Image open error': 'Erro ao abrir a imagem',
'Operation disabled': 'Operação desativada por segurança',
'Image added': 'Imagem adicionada corretamente',
ErrIniSize: 'Arquivo supera o tamanho maximo de protocolo em php.ini',
ErrFormSize: 'Arquivo supera o tamanho maximo de protocolo em HTML form',
ErrPartial: 'Arquivo subido parcialmente',
ErrNoFile: 'Nenhum arquivo subido',
ErrNoTmpDir: 'Falta a pasta de arquivo temporal',
ErrCantWrite: 'Erro ao gravar arquivo no disco',
ErrExtension: 'Erro de extensão do arquivo',
ErrDefault: 'Erro desconhecido ao subir arquivo',
'Sync complete': 'Sincronização completa'
}; };

View File

@ -31,7 +31,13 @@ export const formatDate = (timeStamp, format = 'YYYY-MM-DD') => {
*/ */
export const formatDateTitle = ( export const formatDateTitle = (
timeStamp, timeStamp,
options = { showTime: false, showSeconds: false, shortDay: false } options = {
showTime: false,
showSeconds: false,
shortDay: false,
shortMonth: false,
includeOfString: true
}
) => { ) => {
if (!timeStamp) return ''; if (!timeStamp) return '';
const { t } = i18n.global; const { t } = i18n.global;
@ -42,8 +48,10 @@ export const formatDateTitle = (
: ` [${t('at')}] HH:mm` : ` [${t('at')}] HH:mm`
: ''; : '';
const day = options.shortDay ? 'dd' : 'dddd'; const day = options.shortDay ? 'dd' : 'dddd';
const month = options.shortMonth ? 'MMM' : 'MMMM';
const ofString = options.includeOfString ? ` [${t('of')}] ` : '';
const formatString = `${day}, D [${t('of')}] MMMM [${t('of')}] YYYY${timeFormat}`; const formatString = `${day}, D${ofString} ${month}${ofString} YYYY${timeFormat}`;
const formattedString = formatDate(timeStamp, formatString); const formattedString = formatDate(timeStamp, formatString);
return formattedString; return formattedString;

View File

@ -15,6 +15,7 @@ const { t } = useI18n();
const jApi = inject('jApi'); const jApi = inject('jApi');
const appStore = useAppStore(); const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore); const { isHeaderMounted } = storeToRefs(appStore);
const { user } = storeToRefs(userStore);
const vnFormRef = ref(null); const vnFormRef = ref(null);
const vnFormRef2 = ref(null); const vnFormRef2 = ref(null);
@ -43,6 +44,15 @@ const fetchLanguagesSql = async () => {
} }
}; };
const updateUserNickname = async nickname => {
try {
await vnFormRef.value.submit();
user.value.nickname = nickname;
} catch (error) {
console.error(error);
}
};
onMounted(() => fetchLanguagesSql()); onMounted(() => fetchLanguagesSql());
</script> </script>
@ -90,8 +100,8 @@ onMounted(() => fetchLanguagesSql());
<VnInput <VnInput
v-model="data.nickname" v-model="data.nickname"
:label="t('nickname')" :label="t('nickname')"
@keyup.enter="vnFormRef.submit()" @keyup.enter="updateUserNickname(data.nickname)"
@blur="vnFormRef.submit()" @blur="updateUserNickname(data.nickname)"
/> />
<VnSelect <VnSelect
v-model="data.lang" v-model="data.lang"

View File

@ -0,0 +1,106 @@
<script setup>
import { onMounted, inject, ref } from 'vue';
import { useRoute } from 'vue-router';
import CardList from 'src/components/ui/CardList.vue';
import { formatDateTitle } from 'src/lib/filters.js';
const jApi = inject('jApi');
const route = useRoute();
const accessLogs = ref([]);
const user = ref(null);
const getUser = async () => {
try {
if (!route.params.id) return;
const [data] = await jApi.query(
`SELECT u.id, u.name user, u.nickname, u.email, c.phone, r.name role
FROM account.user u
JOIN account.role r ON r.id = u.role
LEFT JOIN vn.client c ON c.id = u.id
WHERE u.id = #user`,
{ user: route.params.id }
);
user.value = data;
} catch (error) {
console.error('Error getting user:', error);
}
};
const getAccessLogs = async () => {
try {
accessLogs.value = await jApi.query(
`SELECT u.stamp, a.platform, a.browser, a.version, a.javascript, a.cookies
FROM visitUser u
JOIN visitAccess c ON c.id = u.accessFk
JOIN visitAgent a ON a.id = c.agentFk
WHERE u.userFk = #user
ORDER BY u.stamp DESC
LIMIT 8`,
{ user: route.params.id }
);
} catch (error) {
console.error('Error getting access logs:', error);
}
};
onMounted(async () => {
getUser();
getAccessLogs();
});
</script>
<template>
<QPage class="vn-w-xs">
<CardList
v-if="user"
:key="index"
:clickable="false"
rounded
class="q-mb-md"
>
<template #content>
<span class="text-bold text-h5 q-mb-sm">
{{ user?.nickname }}
</span>
<span>#{{ user?.id }} - {{ user.user }} </span>
<span>{{ user?.role }} </span>
<span>{{ user?.email }} </span>
<span>{{ user?.phone }} </span>
</template>
</CardList>
<QList>
<CardList
v-for="(accessLog, index) in accessLogs"
:key="index"
:clickable="false"
>
<template #content>
<span>
{{
formatDateTitle(accessLog.stamp, {
showTime: true,
shortDay: true,
shortMonth: true,
showSeconds: true
})
}}
</span>
<span
v-if="
accessLog.platform &&
accessLog.browser &&
accessLog.version
"
>
{{ accessLog.platform }} - {{ accessLog.browser }} -
{{ accessLog.version }}
</span>
</template>
</CardList>
</QList>
</QPage>
</template>

View File

@ -91,6 +91,7 @@ onBeforeUnmount(() => clearInterval(intervalId.value));
v-else v-else
v-for="(connection, index) in connections" v-for="(connection, index) in connections"
:key="index" :key="index"
:to="{ name: 'accessLog', params: { id: connection.userId } }"
> >
<template #content> <template #content>
<span class="text-bold q-mb-sm"> <span class="text-bold q-mb-sm">
@ -124,7 +125,7 @@ onBeforeUnmount(() => clearInterval(intervalId.value));
icon="people" icon="people"
flat flat
rounded rounded
@click="supplantUser(connection.user)" @click.stop.prevent="supplantUser(connection.user)"
> >
<QTooltip> <QTooltip>
{{ t('supplantUser') }} {{ t('supplantUser') }}
@ -132,6 +133,7 @@ onBeforeUnmount(() => clearInterval(intervalId.value));
</QBtn> </QBtn>
</template> </template>
</CardList> </CardList>
<pre>{{ connections }}</pre>
</QList> </QList>
</QPage> </QPage>
</template> </template>

View File

@ -65,7 +65,7 @@ onMounted(async () => getNews());
<QTooltip>{{ t('addNew') }}</QTooltip> <QTooltip>{{ t('addNew') }}</QTooltip>
</QBtn> </QBtn>
</Teleport> </Teleport>
<QPage class="vn-w-xs"> <QPage class="vn-w-sm">
<QList class="flex justify-center"> <QList class="flex justify-center">
<QSpinner <QSpinner
v-if="loading" v-if="loading"

View File

@ -13,11 +13,24 @@ const { t } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
const fileUploaderRef = ref(null); const fileUploaderRef = ref(null);
const statusIcons = { const statusIcons = {
uploading: 'cloud_upload', uploading: {
fulfilled: 'cloud_done', icon: 'cloud_upload',
rejected: 'error', tooltip: 'Uploading'
pending: 'add' },
fulfilled: {
icon: 'cloud_done',
tooltip: 'imageUploaded'
},
rejected: {
icon: 'error',
tooltip: 'Error'
},
pending: {
icon: 'add',
tooltip: 'pendingUpload'
}
}; };
const formInitialData = reactive({ const formInitialData = reactive({
schema: 'catalog', schema: 'catalog',
@ -72,6 +85,12 @@ const onSubmit = async data => {
results.forEach((result, index) => { results.forEach((result, index) => {
const fileIndex = filteredFiles[index].index; const fileIndex = filteredFiles[index].index;
addedFiles.value[fileIndex].uploadStatus = result.status; addedFiles.value[fileIndex].uploadStatus = result.status;
if (result.status === 'rejected') {
addedFiles.value[fileIndex].errorMessage = t(
result.reason?.response?.data?.data?.message
);
}
}); });
const allSuccessful = results.every( const allSuccessful = results.every(
@ -122,10 +141,10 @@ onMounted(async () => getImageCollections());
<QPage class="vn-w-sm"> <QPage class="vn-w-sm">
<VnForm <VnForm
ref="vnFormRef" ref="vnFormRef"
:defaultActions="false" :default-actions="false"
:formInitialData="formInitialData" :form-initial-data="formInitialData"
separationBetweenInputs="md" separation-between-inputs="md"
showBottomActions show-bottom-actions
> >
<template #form="{ data }"> <template #form="{ data }">
<VnSelect <VnSelect
@ -146,7 +165,7 @@ onMounted(async () => getImageCollections());
hide-upload-btn hide-upload-btn
@added="onFilesAdded" @added="onFilesAdded"
> >
<template v-slot:list="scope"> <template #list="scope">
<QList v-if="addedFiles.length" separator> <QList v-if="addedFiles.length" separator>
<QItem <QItem
v-for="(file, index) in scope.files" v-for="(file, index) in scope.files"
@ -182,10 +201,22 @@ onMounted(async () => getImageCollections());
:name=" :name="
statusIcons[ statusIcons[
addedFiles[index].uploadStatus addedFiles[index].uploadStatus
] ].icon
" "
size="sm" size="sm"
/> >
<QTooltip>
{{
addedFiles[index].errorMessage ||
t(
statusIcons[
addedFiles[index]
.uploadStatus
].tooltip
)
}}
</QTooltip>
</QIcon>
<QBtn <QBtn
v-if=" v-if="
addedFiles[index].uploadStatus !== addedFiles[index].uploadStatus !==
@ -198,7 +229,9 @@ onMounted(async () => getImageCollections());
round round
icon="delete" icon="delete"
@click="removeFile(file, index)" @click="removeFile(file, index)"
/> >
<QTooltip>{{ t('remove') }}</QTooltip>
</QBtn>
</QItem> </QItem>
</QList> </QList>
</template> </template>
@ -239,6 +272,8 @@ en-US:
uploadSuccess: Upload finished successfully uploadSuccess: Upload finished successfully
uploadError: Some errors happened on upload uploadError: Some errors happened on upload
noFilesToUpload: There are no files to upload noFilesToUpload: There are no files to upload
pendingUpload: Pending upload
imageUploaded: Image uploaded
es-ES: es-ES:
collection: Colección collection: Colección
updateMatching: Actualizar artículos con id coincidente updateMatching: Actualizar artículos con id coincidente
@ -248,6 +283,8 @@ es-ES:
uploadSuccess: Imágenes subidas correctamente uploadSuccess: Imágenes subidas correctamente
uploadError: Ocurrieron errores al subir alguna de las imágenes uploadError: Ocurrieron errores al subir alguna de las imágenes
noFilesToUpload: No se han seleccionado archivos para subir noFilesToUpload: No se han seleccionado archivos para subir
pendingUpload: Subida pendiente
imageUploaded: Imagen subida
ca-ES: ca-ES:
collection: Col·lecció collection: Col·lecció
updateMatching: Actualitzar els elements amb id coincident updateMatching: Actualitzar els elements amb id coincident
@ -257,6 +294,8 @@ ca-ES:
uploadSuccess: Imatges pujades correctament uploadSuccess: Imatges pujades correctament
uploadError: Van ocórrer errors en pujar alguna de les imatges uploadError: Van ocórrer errors en pujar alguna de les imatges
noFilesToUpload: No s'ha seleccionat arxius per pujar noFilesToUpload: No s'ha seleccionat arxius per pujar
pendingUpload: Pujada pendent
imageUploaded: Imatge pujada
fr-FR: fr-FR:
collection: Collection collection: Collection
updateMatching: Mettre à jour les éléments avec l'identifiant correspondant updateMatching: Mettre à jour les éléments avec l'identifiant correspondant
@ -266,6 +305,8 @@ fr-FR:
uploadSuccess: Les images téléchargées correctement uploadSuccess: Les images téléchargées correctement
uploadError: Des erreurs sont survenues lors du téléchargement des images uploadError: Des erreurs sont survenues lors du téléchargement des images
noFilesToUpload: Aucun fichier sélectionné pour télécharger noFilesToUpload: Aucun fichier sélectionné pour télécharger
pendingUpload: Téléchargement en attente
imageUploaded: Image téléchargée
pt-PT: pt-PT:
collection: Coleção collection: Coleção
updateMatching: Atualizar itens com id correspondente updateMatching: Atualizar itens com id correspondente
@ -275,4 +316,6 @@ pt-PT:
uploadSuccess: Upload concluído com sucesso uploadSuccess: Upload concluído com sucesso
uploadError: Ocorreram erros ao subir alguma das imagens uploadError: Ocorreram erros ao subir alguma das imagens
noFilesToUpload: Não arquivos selecionados para upload noFilesToUpload: Não arquivos selecionados para upload
pendingUpload: Upload pendente
imageUploaded: Imagem carregada
</i18n> </i18n>

View File

@ -9,14 +9,12 @@ import VnSearchBar from 'src/components/ui/VnSearchBar.vue';
import { useAppStore } from 'stores/app'; import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useUserStore } from 'stores/user'; import { useUserStore } from 'stores/user';
import useNotify from 'src/composables/useNotify.js';
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter(); const router = useRouter();
const userStore = useUserStore(); const userStore = useUserStore();
const appStore = useAppStore(); const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore); const { isHeaderMounted } = storeToRefs(appStore);
const { notify } = useNotify();
const loading = ref(false); const loading = ref(false);
const users = ref([]); const users = ref([]);
@ -37,7 +35,6 @@ const supplantUser = async user => {
router.push({ name: 'confirmedOrders' }); router.push({ name: 'confirmedOrders' });
} catch (error) { } catch (error) {
console.error('Error supplanting user:', error); console.error('Error supplanting user:', error);
notify(error.message, 'negative');
} }
}; };
</script> </script>
@ -67,7 +64,7 @@ const supplantUser = async user => {
v-else v-else
v-for="(user, index) in users" v-for="(user, index) in users"
:key="index" :key="index"
:clickable="false" :to="{ name: 'accessLog', params: { id: user.id } }"
> >
<template #content> <template #content>
<span class="text-bold q-mb-sm"> <span class="text-bold q-mb-sm">
@ -77,14 +74,17 @@ const supplantUser = async user => {
</template> </template>
<template #actions> <template #actions>
<QBtn <QBtn
v-if="user.active"
icon="people" icon="people"
flat flat
rounded rounded
@click="supplantUser(user.name)" @click.stop.prevent="supplantUser(user.name)"
><QTooltip>
{{ t('Impersonate user') }}
</QTooltip></QBtn
> >
<QTooltip>
{{ t('Impersonate user') }}
</QTooltip>
</QBtn>
<QBadge v-else color="negative">{{ t('Disabled') }}</QBadge>
</template> </template>
</CardList> </CardList>
</QList> </QList>

View File

@ -85,9 +85,7 @@ onMounted(async () => {
> >
<template #content> <template #content>
<QItemLabel class="text-bold q-mb-sm"> <QItemLabel class="text-bold q-mb-sm">
{{ {{ formatDateTitle(order.sent) }}
formatDateTitle(order.sent)
}}
</QItemLabel> </QItemLabel>
<QItemLabel> #{{ order.id }} </QItemLabel> <QItemLabel> #{{ order.id }} </QItemLabel>
<QItemLabel>{{ order.nickname }}</QItemLabel> <QItemLabel>{{ order.nickname }}</QItemLabel>
@ -106,13 +104,17 @@ onMounted(async () => {
() => removeOrder(order.id, index) () => removeOrder(order.id, index)
) )
" "
/> >
<QTooltip>{{ t('deleteOrder') }}</QTooltip>
</QBtn>
<QBtn <QBtn
icon="shopping_bag" icon="shopping_bag"
flat flat
rounded rounded
@click.stop.prevent="loadOrder(order.id)" @click.stop.prevent="loadOrder(order.id)"
/> >
<QTooltip>{{ t('loadOrderIntoCart') }}</QTooltip>
</QBtn>
</template> </template>
</CardList> </CardList>
</QPage> </QPage>
@ -124,16 +126,26 @@ onMounted(async () => {
en-US: en-US:
newOrder: New order newOrder: New order
areYouSureDeleteOrder: Are you sure you want to delete the order? areYouSureDeleteOrder: Are you sure you want to delete the order?
deleteOrder: Delete order
loadOrderIntoCart: Load order into cart
es-ES: es-ES:
newOrder: Nuevo pedido newOrder: Nuevo pedido
areYouSureDeleteOrder: ¿Seguro que quieres borrar el pedido? areYouSureDeleteOrder: ¿Seguro que quieres borrar el pedido?
deleteOrder: Eliminar pedido
loadOrderIntoCart: Cargar pedido en la cesta
ca-ES: ca-ES:
newOrder: Nova comanda newOrder: Nova comanda
areYouSureDeleteOrder: Segur que vols esborrar la comanda? areYouSureDeleteOrder: Segur que vols esborrar la comanda?
deleteOrder: Eliminar comanda
loadOrderIntoCart: Carregar comanda a la cistella
fr-FR: fr-FR:
newOrder: Nouvelle commande newOrder: Nouvelle commande
areYouSureDeleteOrder: Êtes-vous sûr de vouloir supprimer la commande? areYouSureDeleteOrder: Êtes-vous sûr de vouloir supprimer la commande?
deleteOrder: Supprimer la commande
loadOrderIntoCart: Charger la commande dans le panier
pt-PT: pt-PT:
newOrder: Novo pedido newOrder: Novo pedido
areYouSureDeleteOrder: Tem certeza de que deseja excluir o pedido? areYouSureDeleteOrder: Tem certeza de que deseja excluir o pedido?
deleteOrder: Excluir pedido
loadOrderIntoCart: Carregar pedido no carrinho
</i18n> </i18n>

View File

@ -143,12 +143,20 @@ const routes = [
}, },
{ {
name: 'adminUsers', name: 'adminUsers',
path: 'admin/users', path: 'admin/users:/:id?',
meta: { meta: {
title: 'Users' title: 'Users'
}, },
component: () => import('pages/Admin/UsersView.vue') component: () => import('pages/Admin/UsersView.vue')
}, },
{
name: 'accessLog',
path: 'admin/access-log/:id?',
meta: {
title: 'Access log'
},
component: () => import('pages/Admin/AccessLogView.vue')
},
{ {
name: 'adminConnections', name: 'adminConnections',
path: 'admin/connections', path: 'admin/connections',