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 <QCardSection horizontal>
:data-key="$props.url" <VnAvatar :descriptor="false" :worker-id="1" size="md" />
:url="$props.url" <div class="full-width row justify-between q-pa-xs">
order="created DESC" <VnUserLink :name="t('New note')" :worker-id="currentUser.id" />
:limit="20" {{ t('globals.now') }}
:filter="$props.filter" </div>
auto-load </QCardSection>
ref="vnPaginateRef" <QCardSection class="q-pa-xs q-my-none q-py-none" horizontal>
> <QInput
<template #body="{ rows }"> v-model="newNote"
<div class="column items-center full-width"> class="full-width"
<QCard type="textarea"
class="q-pa-xs q-mb-sm full-width" :label="t('Add note here...')"
v-for="(note, index) in rows" filled
:key="index" size="lg"
> autogrow
<QCardSection horizontal> autofocus
<slot name="picture"> @keyup.ctrl.enter.stop="insert"
<VnAvatar clearable
:descriptor="false" >
:worker-id="note.workerFk" <template #append
size="md" ><QBtn
/> :title="t('Save (ctrl + Enter)')"
</slot> icon="save"
<div class="full-width row justify-between q-pa-xs"> color="primary"
<VnUserLink
:name="`${note.worker.user.nickname}`"
:worker-id="note.worker.id"
/>
<slot name="actions">
{{ toDateHour(note.created) }}
</slot>
</div>
</QCardSection>
<QCardSection class="q-pa-xs q-my-none q-py-none">
<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
autofocus
type="textarea"
:label="t('Add note here...')"
filled
size="lg"
autogrow
v-model="newNote"
></QInput>
</QCardSection>
<QCardActions class="justify-end q-mr-sm">
<QBtn
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>
</QCard> </QInput>
</QDialog> </QCardSection>
</div> </QCard>
<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>
</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,161 +180,135 @@ 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" :url="`Claims/${route.params.id}/lines`"
:url="`Claims/${route.params.id}/lines`" save-url="ClaimBeginnings/crud"
save-url="ClaimBeginnings/crud" :filter="linesFilter"
:filter="linesFilter" @on-fetch="onFetch"
@on-fetch="onFetch" @save-changes="onFetch"
@save-changes="onFetch" v-model:selected="selected"
v-model:selected="selected" :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
:columns="columns" :columns="columns"
:rows="rows" :rows="rows"
:dense="$q.screen.lt.md" :dense="$q.screen.lt.md"
row-key="id" row-key="id"
selection="multiple" selection="multiple"
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> <QInput
v-model="row.quantity"
<QPopupEdit type="number"
v-model="row.quantity" dense
v-slot="scope" @keyup.enter="saveWhenHasChanges()"
:title="t('Claimed quantity')" @blur="saveWhenHasChanges()"
@update:model-value="updateQuantity(row)" />
buttons </QTd>
> </template>
<QInput <template #body-cell-description="{ row, value }">
v-model="scope.value" <QTd auto-width align="right" class="text-primary">
type="number" {{ value }}
dense <ItemDescriptorProxy
autofocus :id="row.sale.itemFk"
@keyup.enter="scope.set" ></ItemDescriptorProxy>
@focus="($event) => $event.target.select()" </QTd>
/> </template>
</QPopupEdit> <template #body-cell-discount="{ row, value, rowIndex }">
</QTd> <QTd auto-width align="right" class="text-primary">
</template> {{ value }}
<template #body-cell-description="{ row, value }"> <VnDiscount
<QTd auto-width align="right" class="text-primary"> :quantity="row.quantity"
{{ value }} :price="row.sale.price"
<ItemDescriptorProxy :discount="row.sale.discount"
:id="row.sale.itemFk" :mana="mana"
></ItemDescriptorProxy> :promise="updateDiscount"
</QTd> :data="{ saleFk: row.sale.id, rowIndex: rowIndex }"
</template> @on-update="onUpdateDiscount"
<template #body-cell-discount="{ row, value, rowIndex }"> />
<QTd auto-width align="right" class="text-primary"> </QTd>
{{ value }} </template>
<VnDiscount <!-- View for grid mode -->
:quantity="row.quantity" <template #item="props">
:price="row.sale.price" <div
:discount="row.sale.discount" class="q-mb-md col-12 grid-style-transition"
:mana="mana" :style="props.selected ? 'transform: scale(0.95);' : ''"
:promise="updateDiscount" >
:data="{ saleFk: row.sale.id, rowIndex: rowIndex }" <QCard>
@on-update="onUpdateDiscount" <QCardSection>
/> <QCheckbox v-model="props.selected" />
</QTd> </QCardSection>
</template> <QSeparator inset />
<!-- View for grid mode --> <QList dense>
<template #item="props"> <QItem
<div v-for="column of props.cols"
class="q-mb-md col-12 grid-style-transition" :key="column.name"
:style="props.selected ? 'transform: scale(0.95);' : ''" >
> <QItemSection>
<QCard> <QItemLabel caption>
<QCardSection> {{ column.label }}
<QCheckbox v-model="props.selected" /> </QItemLabel>
</QCardSection> </QItemSection>
<QSeparator inset /> <QItemSection side>
<QList dense> <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
<QItem <QItemLabel class="text-primary">
v-for="column of props.cols" <QInput
:key="column.name" v-model="props.row.quantity"
> type="number"
<QItemSection> dense
<QItemLabel caption> autofocus
{{ column.label }} @keyup.enter="
saveWhenHasChanges()
"
@blur="saveWhenHasChanges()"
/>
</QItemLabel> </QItemLabel>
</QItemSection> </template>
<QItemSection side> <template
<template v-else-if="column.name === 'discount'"
v-if="column.name === 'claimed'" >
> <QItemLabel class="text-primary">
<QItemLabel class="text-primary"> {{ column.value }}
{{ column.value }} <VnDiscount
<QPopupEdit :quantity="props.row.quantity"
v-model="props.row.quantity" :price="props.row.sale.price"
v-slot="scope" :discount="
:title="t('Claimed quantity')" props.row.sale.discount
@update:model-value=" "
updateQuantity(props.row) :mana="mana"
" :promise="updateDiscount"
buttons :data="{
> saleFk: props.row.sale.id,
<QInput rowIndex: props.rowIndex,
v-model="scope.value" }"
type="number" @on-update="onUpdateDiscount"
dense />
autofocus </QItemLabel>
@keyup.enter="scope.set" </template>
@focus=" <template v-else>
($event) => <QItemLabel>
$event.target.select() {{ column.value }}
" </QItemLabel>
/> </template>
</QPopupEdit> </QItemSection>
</QItemLabel> </QItem>
</template> </QList>
<template </QCard>
v-else-if="column.name === 'discount'" </div>
> </template>
<QItemLabel class="text-primary"> </QTable>
{{ column.value }} </template>
<VnDiscount </CrudModel>
:quantity="props.row.quantity"
:price="props.row.sale.price"
:discount="
props.row.sale.discount
"
:mana="mana"
:promise="updateDiscount"
:data="{
saleFk: props.row.sale.id,
rowIndex: props.rowIndex,
}"
@on-update="onUpdateDiscount"
/>
</QItemLabel>
</template>
<template v-else>
<QItemLabel>
{{ column.value }}
</QItemLabel>
</template>
</QItemSection>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
</template>
</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

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);
}); });
}); });