refs #6336 feat(claim): improvements #288

Merged
alexm merged 5 commits from 6336-claim_changes_v3 into dev 2024-04-19 09:20:56 +00:00
11 changed files with 288 additions and 338 deletions

View File

@ -24,6 +24,10 @@ const $props = defineProps({
type: String, type: String,
default: '', default: '',
}, },
limit: {
type: Number,
default: 20,
alexm marked this conversation as resolved
Review

20 por algun motivo?
Quiero decir, tenemos 2 ocurrencias de prop a 10 y otras 2 a 20
No es significativo, no?

20 por algun motivo? Quiero decir, tenemos 2 ocurrencias de prop a 10 y otras 2 a 20 No es significativo, no?
},
saveUrl: { saveUrl: {
type: String, type: String,
default: null, default: null,
@ -76,6 +80,7 @@ defineExpose({
reset, reset,
hasChanges, hasChanges,
saveChanges, saveChanges,
getChanges,
}); });
async function fetch(data) { async function fetch(data) {
@ -260,6 +265,7 @@ watch(formUrl, async () => {
<template> <template>
<VnPaginate <VnPaginate
:url="url" :url="url"
:limit="limit"
v-bind="$attrs" v-bind="$attrs"
@on-fetch="fetch" @on-fetch="fetch"
:skeleton="false" :skeleton="false"

View File

@ -6,6 +6,7 @@ import axios from 'axios';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnPaginate from './VnPaginate.vue'; import VnPaginate from './VnPaginate.vue';
import VnUserLink from '../ui/VnUserLink.vue'; import VnUserLink from '../ui/VnUserLink.vue';
import { useState } from 'src/composables/useState';
const $props = defineProps({ const $props = defineProps({
url: { type: String, default: null }, url: { type: String, default: null },
@ -13,8 +14,10 @@ const $props = defineProps({
body: { type: Object, default: () => {} }, body: { type: Object, default: () => {} },
addNote: { type: Boolean, default: false }, addNote: { type: Boolean, default: false },
}); });
const { t } = useI18n(); const { t } = useI18n();
const noteModal = ref(false); const state = useState();
const currentUser = ref(state.getUser());
const newNote = ref(''); const newNote = ref('');
const vnPaginateRef = ref(); const vnPaginateRef = ref();
@ -22,98 +25,83 @@ async function insert() {
const body = $props.body; const body = $props.body;
Object.assign(body, { text: newNote.value }); Object.assign(body, { text: newNote.value });
await axios.post($props.url, body); await axios.post($props.url, body);
vnPaginateRef.value.fetch(); await vnPaginateRef.value.fetch();
newNote.value = ''; newNote.value = '';
} }
</script> </script>
<template> <template>
<div class="column items-center full-height full-width"> <QCard class="q-pa-xs q-mb-xl full-width" v-if="$props.addNote">
<VnPaginate
:data-key="$props.url"
:url="$props.url"
order="created DESC"
:limit="20"
:filter="$props.filter"
auto-load
ref="vnPaginateRef"
>
<template #body="{ rows }">
<div class="column items-center full-width">
<QCard
class="q-pa-xs q-mb-sm full-width"
v-for="(note, index) in rows"
:key="index"
>
<QCardSection horizontal> <QCardSection horizontal>
<slot name="picture"> <VnAvatar :descriptor="false" :worker-id="1" size="md" />
<VnAvatar
:descriptor="false"
:worker-id="note.workerFk"
size="md"
/>
</slot>
<div class="full-width row justify-between q-pa-xs"> <div class="full-width row justify-between q-pa-xs">
<VnUserLink <VnUserLink :name="t('New note')" :worker-id="currentUser.id" />
:name="`${note.worker.user.nickname}`" {{ t('globals.now') }}
:worker-id="note.worker.id"
/>
<slot name="actions">
{{ toDateHour(note.created) }}
</slot>
</div> </div>
</QCardSection> </QCardSection>
<QCardSection class="q-pa-xs q-my-none q-py-none"> <QCardSection class="q-pa-xs q-my-none q-py-none" horizontal>
<slot name="text">
{{ note.text }}
</slot>
</QCardSection>
</QCard>
</div>
</template>
</VnPaginate>
<QPageSticky position="bottom-right" :offset="[25, 25]" v-if="addNote">
<QBtn color="primary" icon="add" size="lg" round @click="noteModal = true" />
</QPageSticky>
<QDialog v-model="noteModal" @hide="newNote = ''">
<QCard>
<QCardSection>
<QItem class="q-px-none">
<span class="text-primary text-h6 full-width">
<QIcon name="draft" class="q-mr-xs" />
{{ t('Add note') }}
</span>
<QBtn icon="close" flat round dense v-close-popup />
</QItem>
</QCardSection>
<QCardSection>
<QInput <QInput
autofocus v-model="newNote"
class="full-width"
type="textarea" type="textarea"
:label="t('Add note here...')" :label="t('Add note here...')"
filled filled
size="lg" size="lg"
autogrow autogrow
v-model="newNote" autofocus
></QInput> @keyup.ctrl.enter.stop="insert"
</QCardSection> clearable
<QCardActions class="justify-end q-mr-sm"> >
<QBtn <template #append
><QBtn
:title="t('Save (ctrl + Enter)')"
icon="save"
color="primary"
flat flat
:label="t('globals.close')"
color="primary"
v-close-popup
/>
<QBtn
:label="t('globals.save')"
color="primary"
v-close-popup
@click="insert" @click="insert"
/> />
</QCardActions> </template>
</QInput>
</QCardSection>
</QCard> </QCard>
</QDialog> <VnPaginate
:data-key="$props.url"
:url="$props.url"
order="created DESC"
:limit="0"
:filter="$props.filter"
auto-load
ref="vnPaginateRef"
class="show"
v-bind="$attrs"
>
<template #body="{ rows }">
<TransitionGroup name="list" tag="div" class="column items-center full-width">
<QCard
class="q-pa-xs q-mb-sm full-width"
v-for="note in rows"
:key="note.id"
>
<QCardSection horizontal>
<VnAvatar
:descriptor="false"
:worker-id="note.workerFk"
size="md"
/>
<div class="full-width row justify-between q-pa-xs">
<VnUserLink
:name="`${note.worker.user.nickname}`"
:worker-id="note.worker.id"
/>
{{ toDateHour(note.created) }}
</div> </div>
</QCardSection>
<QCardSection class="q-pa-xs q-my-none q-py-none">
{{ note.text }}
</QCardSection>
</QCard>
</TransitionGroup>
</template>
</VnPaginate>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.q-card { .q-card {
@ -128,9 +116,20 @@ async function insert() {
.q-dialog .q-card { .q-dialog .q-card {
width: 400px; width: 400px;
} }
.list-enter-active,
.list-leave-active {
transition: all 1s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
background-color: $primary;
}
</style> </style>
<i18n> <i18n>
es: es:
Add note here...: Añadir nota aquí... Add note here...: Añadir nota aquí...
Add note: Añadir nota New note: Nueva nota
Save (ctrl + Enter): Guardar (Ctrl + Intro)
</i18n> </i18n>

View File

@ -96,9 +96,9 @@ export function useArrayData(key, userOptions) {
}); });
const { limit } = filter; const { limit } = filter;
hasMoreData.value = limit && response.data.length >= limit;
hasMoreData.value = response.data.length >= limit;
store.hasMoreData = hasMoreData.value; store.hasMoreData = hasMoreData.value;
if (append) { if (append) {
if (!store.data) store.data = []; if (!store.data) store.data = [];
for (const row of response.data) store.data.push(row); for (const row of response.data) store.data.push(row);

View File

@ -90,6 +90,7 @@ globals:
parkingList: Parkings list parkingList: Parkings list
created: Created created: Created
worker: Worker worker: Worker
now: Now
errors: errors:
statusUnauthorized: Access denied statusUnauthorized: Access denied
statusInternalServerError: An internal server error has ocurred statusInternalServerError: An internal server error has ocurred

View File

@ -90,6 +90,7 @@ globals:
parkingList: Listado de parkings parkingList: Listado de parkings
created: Fecha creación created: Fecha creación
worker: Trabajador worker: Trabajador
now: Ahora
errors: errors:
statusUnauthorized: Acceso denegado statusUnauthorized: Acceso denegado
statusInternalServerError: Ha ocurrido un error interno del servidor statusInternalServerError: Ha ocurrido un error interno del servidor

View File

@ -121,11 +121,6 @@ async function fetchMana() {
mana.value = response.data; mana.value = response.data;
} }
async function updateQuantity({ id, quantity }) {
if (!id) return;
await axios.patch(`ClaimBeginnings/${id}`, { quantity });
}
async function updateDiscount({ saleFk, discount, canceller }) { async function updateDiscount({ saleFk, discount, canceller }) {
const body = { salesIds: [saleFk], newDiscount: discount }; const body = { salesIds: [saleFk], newDiscount: discount };
const claimId = claim.value.ticketFk; const claimId = claim.value.ticketFk;
@ -155,6 +150,10 @@ function showImportDialog() {
}) })
.onOk(() => claimLinesForm.value.reload()); .onOk(() => claimLinesForm.value.reload());
} }
function saveWhenHasChanges() {
claimLinesForm.value.getChanges().updates && claimLinesForm.value.onSubmit();
}
</script> </script>
<template> <template>
<Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()"> <Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()">
@ -181,8 +180,7 @@ function showImportDialog() {
@on-fetch="onFetchClaim" @on-fetch="onFetchClaim"
auto-load auto-load
/> />
<div class="column items-center"> <div class="q-pa-md">
<div class="list">
<CrudModel <CrudModel
data-key="ClaimLines" data-key="ClaimLines"
ref="claimLinesForm" ref="claimLinesForm"
@ -195,6 +193,7 @@ function showImportDialog() {
:default-save="false" :default-save="false"
:default-reset="false" :default-reset="false"
auto-load auto-load
:limit="0"
> >
<template #body="{ rows }"> <template #body="{ rows }">
<QTable <QTable
@ -206,26 +205,15 @@ function showImportDialog() {
v-model:selected="selected" v-model:selected="selected"
:grid="$q.screen.lt.md" :grid="$q.screen.lt.md"
> >
<template #body-cell-claimed="{ row, value }"> <template #body-cell-claimed="{ row }">
<QTd auto-width align="right" class="text-primary"> <QTd auto-width align="right" class="text-primary">
<span>{{ value }}</span>
<QPopupEdit
v-model="row.quantity"
v-slot="scope"
:title="t('Claimed quantity')"
@update:model-value="updateQuantity(row)"
buttons
>
<QInput <QInput
v-model="scope.value" v-model="row.quantity"
type="number" type="number"
dense dense
autofocus @keyup.enter="saveWhenHasChanges()"
@keyup.enter="scope.set" @blur="saveWhenHasChanges()"
@focus="($event) => $event.target.select()"
/> />
</QPopupEdit>
</QTd> </QTd>
</template> </template>
<template #body-cell-description="{ row, value }"> <template #body-cell-description="{ row, value }">
@ -272,32 +260,18 @@ function showImportDialog() {
</QItemLabel> </QItemLabel>
</QItemSection> </QItemSection>
<QItemSection side> <QItemSection side>
<template <template v-if="column.name === 'claimed'">
alexm marked this conversation as resolved
Review

Aqui estamos usando un estilo diferente que es habilitar el input en la tabla en vez del popup.

Entiendo que maná sea diferente porque tiene complejidad

Aqui estamos usando un estilo diferente que es habilitar el input en la tabla en vez del popup. Entiendo que maná sea diferente porque tiene complejidad
Review

Eso ya estaba asi, que es como esta en salix

Eso ya estaba asi, que es como esta en salix
v-if="column.name === 'claimed'"
>
<QItemLabel class="text-primary"> <QItemLabel class="text-primary">
{{ column.value }}
<QPopupEdit
v-model="props.row.quantity"
v-slot="scope"
:title="t('Claimed quantity')"
@update:model-value="
updateQuantity(props.row)
"
buttons
>
<QInput <QInput
v-model="scope.value" v-model="props.row.quantity"
type="number" type="number"
dense dense
autofocus autofocus
@keyup.enter="scope.set" @keyup.enter="
@focus=" saveWhenHasChanges()
($event) =>
$event.target.select()
" "
@blur="saveWhenHasChanges()"
/> />
</QPopupEdit>
</QItemLabel> </QItemLabel>
</template> </template>
<template <template
@ -336,7 +310,6 @@ function showImportDialog() {
</template> </template>
</CrudModel> </CrudModel>
</div> </div>
</div>
<QPageSticky position="bottom-right" :offset="[25, 25]"> <QPageSticky position="bottom-right" :offset="[25, 25]">
alexm marked this conversation as resolved
Review

Propuesta: Añadir tooltip al boton

Propuesta: Añadir tooltip al boton
<QBtn fab color="primary" icon="add" @click="showImportDialog()" /> <QBtn fab color="primary" icon="add" @click="showImportDialog()" />

View File

@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import { toDate, toCurrency, toPercentage } from 'filters/index'; import { toDate, toCurrency, toPercentage } from 'filters/index';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import axios from 'axios'; import axios from 'axios';
defineEmits([...useDialogPluginComponent.emits]); defineEmits([...useDialogPluginComponent.emits]);
@ -118,7 +119,6 @@ function cancel() {
<QBtn icon="close" flat round dense v-close-popup /> <QBtn icon="close" flat round dense v-close-popup />
</QCardSection> </QCardSection>
<QTable <QTable
class="my-sticky-header-table"
:columns="columns" :columns="columns"
:rows="claimableSales" :rows="claimableSales"
row-key="saleFk" row-key="saleFk"
@ -126,7 +126,14 @@ function cancel() {
v-model:selected="selected" v-model:selected="selected"
square square
flat flat
/> >
alexm marked this conversation as resolved
Review

Se que no aplica pero al darle a confirmar, el texto del alert está en inglés

Se que no aplica pero al darle a confirmar, el texto del alert está en inglés
Review

Esta ya con la traduccion:
:label="t('globals.confirm')"

Esta ya con la traduccion: `:label="t('globals.confirm')"`
<template #body-cell-description="{ row, value }">
<QTd auto-width align="right" class="link">
{{ value }}
<ItemDescriptorProxy :id="row.itemFk"></ItemDescriptorProxy>
</QTd>
</template>
</QTable>
<QSeparator /> <QSeparator />
<QCardActions align="right"> <QCardActions align="right">
<QBtn :label="t('globals.cancel')" color="primary" flat @click="cancel" /> <QBtn :label="t('globals.cancel')" color="primary" flat @click="cancel" />
alexm marked this conversation as resolved
Review

Propuesta: Desactivar el botón si no hay registros seleccionados

Propuesta: Desactivar el botón si no hay registros seleccionados
@ -148,33 +155,6 @@ function cancel() {
} }
</style> </style>
<style lang="scss">
Outdated
Review

No hacian falta

No hacian falta
.my-sticky-header-table {
height: 400px;
thead tr th {
position: sticky;
z-index: 1;
}
thead tr:first-child th {
/* this is when the loading indicator appears */
top: 0;
}
&.q-table--loading thead tr:last-child th {
/* height of all previous header rows */
top: 48px;
}
// /* prevent scrolling behind sticky top row on focus */
tbody {
/* height of all previous header rows */
scroll-margin-top: 48px;
}
}
</style>
<i18n> <i18n>
es: es:
Available sales lines: Líneas de venta disponibles Available sales lines: Líneas de venta disponibles

View File

@ -38,10 +38,11 @@ const body = {
</script> </script>
<template> <template>
<VnNotes <VnNotes
style="overflow-y: auto"
:add-note="$props.addNote"
url="claimObservations" url="claimObservations"
:add-note="$props.addNote"
:filter="claimFilter" :filter="claimFilter"
:body="body" :body="body"
v-bind="$attrs"
style="overflow-y: auto"
/> />
</template> </template>

View File

@ -222,8 +222,8 @@ function openDialog(dmsId) {
</template> </template>
</VnLv> </VnLv>
<VnLv <VnLv
:label="t('claim.summary.pickup')" :label="t('claim.basicData.pickup')"
Review

estaban mal

estaban mal
:value="t(`claim.summary.${claim.pickup}`)" :value="t(`claim.basicData.${claim.pickup}`)"
/> />
</QCard> </QCard>
<QCard class="vn-three"> <QCard class="vn-three">
@ -280,6 +280,48 @@ function openDialog(dmsId) {
</template> </template>
</QTable> </QTable>
</QCard> </QCard>
<QCard class="vn-two" v-if="claimDms.length > 0">
Review

Movido

Movido
<VnTitle
:url="`#/claim/${entityId}/photos`"
:text="t('claim.summary.photos')"
/>
<div class="container">
<div
class="multimedia-container"
v-for="(media, index) of claimDms"
:key="index"
>
<div class="relative-position">
<QIcon
name="play_circle"
color="primary"
size="xl"
class="absolute-center zindex"
v-if="media.isVideo"
@click.stop="openDialog(media.dmsFk)"
>
<QTooltip>Video</QTooltip>
</QIcon>
<QCard class="multimedia relative-position">
<QImg
:src="media.url"
class="rounded-borders cursor-pointer fit"
@click="openDialog(media.dmsFk)"
v-if="!media.isVideo"
>
</QImg>
<video
:src="media.url"
class="rounded-borders cursor-pointer fit"
muted="muted"
v-if="media.isVideo"
@click="openDialog(media.dmsFk)"
/>
</QCard>
</div>
</div>
</div>
</QCard>
<QCard class="vn-two" v-if="developments.length > 0"> <QCard class="vn-two" v-if="developments.length > 0">
<VnTitle <VnTitle
:url="claimUrl + 'development'" :url="claimUrl + 'development'"
@ -302,49 +344,6 @@ function openDialog(dmsId) {
</template> </template>
</QTable> </QTable>
</QCard> </QCard>
<QCard class="vn-max" v-if="claimDms.length > 0">
<VnTitle
:url="`#/claim/${entityId}/photos`"
:text="t('claim.summary.photos')"
/>
<div class="container">
<div
class="multimedia-container"
v-for="(media, index) of claimDms"
:key="index"
>
<div class="relative-position">
<QIcon
name="play_circle"
color="primary"
size="xl"
class="absolute-center zindex"
v-if="media.isVideo"
@click.stop="openDialog(media.dmsFk)"
>
<QTooltip>Video</QTooltip>header
</QIcon>
<QCard class="multimedia relative-position">
<QImg
:src="media.url"
class="rounded-borders cursor-pointer fit"
@click="openDialog(media.dmsFk)"
v-if="!media.isVideo"
>
</QImg>
<video
:src="media.url"
class="rounded-borders cursor-pointer fit"
muted="muted"
v-if="media.isVideo"
@click="openDialog(media.dmsFk)"
/>
</QCard>
</div>
</div>
</div>
</QCard>
<QCard class="vn-max"> <QCard class="vn-max">
<VnTitle :url="claimUrl + 'action'" :text="t('claim.summary.actions')" /> <VnTitle :url="claimUrl + 'action'" :text="t('claim.summary.actions')" />
<div id="slider-container" class="q-px-xl q-py-md"> <div id="slider-container" class="q-px-xl q-py-md">

View File

@ -28,11 +28,5 @@ const body = {
</script> </script>
<template> <template>
<VnNotes <VnNotes :add-note="true" url="WorkerObservations" :filter="filter" :body="body" />
style="overflow-y: auto"
Outdated
Review

No hace falta

No hace falta
:add-note="{ type: Boolean, default: true }"
url="WorkerObservations"
:filter="filter"
:body="body"
/>
</template> </template>

View File

@ -1,4 +1,3 @@
/// <reference types="cypress" />
describe('ClaimNotes', () => { describe('ClaimNotes', () => {
beforeEach(() => { beforeEach(() => {
cy.login('developer'); cy.login('developer');
@ -7,11 +6,8 @@ describe('ClaimNotes', () => {
it('should add a new note', () => { it('should add a new note', () => {
const message = 'This is a new message.'; const message = 'This is a new message.';
cy.get('.q-page-sticky > div > button').click(); cy.get('.q-textarea').type(message);
cy.get('.q-dialog .q-card__section:nth-child(2)').type(message); cy.get('.q-field__append > .q-btn > .q-btn__content > .q-icon').click(); //save
cy.get('.q-card__actions button:nth-child(2)').click(); cy.get(':nth-child(1) > .q-card__section--vert').should('have.text', message);
cy.get('.q-card .q-card__section:nth-child(2)')
.eq(0)
.should('have.text', message);
}); });
}); });