feat(claimLog): added claim log section
gitea/salix-front/pipeline/head This commit looks good Details

Refs: #5340
This commit is contained in:
Joan Sanchez 2023-03-06 14:29:21 +01:00
parent 818356465b
commit 34e5814b06
16 changed files with 816 additions and 581 deletions

View File

@ -48,24 +48,13 @@ onMounted(() => stateStore.setMounted());
<div id="searchbar"></div> <div id="searchbar"></div>
<q-space></q-space> <q-space></q-space>
<div class="q-pl-sm q-gutter-sm row items-center no-wrap"> <div class="q-pl-sm q-gutter-sm row items-center no-wrap">
<div id="header-actions"></div> <div id="actions-prepend"></div>
<q-btn id="pinnedModules" icon="apps" flat dense rounded> <q-btn id="pinnedModules" icon="apps" flat dense rounded>
<q-tooltip bottom> <q-tooltip bottom>
{{ t('globals.pinnedModules') }} {{ t('globals.pinnedModules') }}
</q-tooltip> </q-tooltip>
<PinnedModules /> <PinnedModules />
</q-btn> </q-btn>
<q-btn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<q-tooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</q-tooltip>
</q-btn>
<q-btn rounded dense flat no-wrap id="user"> <q-btn rounded dense flat no-wrap id="user">
<q-avatar size="lg"> <q-avatar size="lg">
<q-img <q-img
@ -79,6 +68,7 @@ onMounted(() => stateStore.setMounted());
</q-tooltip> </q-tooltip>
<UserPanel /> <UserPanel />
</q-btn> </q-btn>
<div id="actions-append"></div>
</div> </div>
</q-toolbar> </q-toolbar>
</q-header> </q-header>

View File

@ -86,6 +86,7 @@ async function paginate() {
if (!props.url) return; if (!props.url) return;
isLoading.value = true;
await arrayData.loadMore(); await arrayData.loadMore();
if (!arrayData.hasMoreData.value) { if (!arrayData.hasMoreData.value) {
@ -121,7 +122,7 @@ async function onLoad(...params) {
</script> </script>
<template> <template>
<div class="column items-center"> <div>
<div <div
v-if="store.data && store.data.length === 0 && !isLoading" v-if="store.data && store.data.length === 0 && !isLoading"
class="info-row q-pa-md text-center" class="info-row q-pa-md text-center"
@ -150,27 +151,18 @@ async function onLoad(...params) {
</q-card> </q-card>
</div> </div>
</div> </div>
<q-infinite-scroll <q-infinite-scroll v-if="store.data" @load="onLoad" :offset="offset">
v-if="store.data" <slot name="body" :rows="store.data"></slot>
@load="onLoad" <div v-if="isLoading" class="info-row q-pa-md text-center">
:offset="offset" <q-spinner color="orange" size="md" />
class="column items-center"
>
<div v-if="store" class="card-list q-gutter-y-md">
<slot name="body" :rows="store.data"></slot>
<div v-if="isLoading" class="info-row q-pa-md text-center">
<q-spinner color="orange" size="md" />
</div>
</div> </div>
</q-infinite-scroll> </q-infinite-scroll>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.card-list { // .q-infinite-scroll {
width: 100%; // width: 100%;
max-width: 60em; // }
}
.info-row { .info-row {
width: 100%; width: 100%;

View File

@ -50,7 +50,7 @@ export function useArrayData(key, userOptions) {
Object.assign(store.filter, filter); Object.assign(store.filter, filter);
const params = { const params = {
filter: JSON.stringify(filter), filter: JSON.stringify(store.filter),
}; };
Object.assign(params, store.userParams); Object.assign(params, store.userParams);

View File

@ -242,6 +242,7 @@ export default {
basicData: 'Basic Data', basicData: 'Basic Data',
rma: 'RMA', rma: 'RMA',
photos: 'Photos', photos: 'Photos',
log: 'Audit logs',
}, },
list: { list: {
customer: 'Customer', customer: 'Customer',

View File

@ -241,6 +241,7 @@ export default {
basicData: 'Datos básicos', basicData: 'Datos básicos',
rma: 'RMA', rma: 'RMA',
photos: 'Fotos', photos: 'Fotos',
log: 'Registros de auditoría',
}, },
list: { list: {
customer: 'Cliente', customer: 'Cliente',

View File

@ -2,26 +2,13 @@
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import Navbar from 'src/components/NavBar.vue'; import Navbar from 'src/components/NavBar.vue';
import { useStateStore } from 'stores/useStateStore';
const quasar = useQuasar(); const quasar = useQuasar();
const stateStore = useStateStore();
</script> </script>
<template> <template>
<q-layout view="hHh LpR fFf"> <q-layout view="hHh LpR fFf">
<Navbar /> <Navbar />
<router-view></router-view> <router-view></router-view>
<q-drawer
v-model="stateStore.rightDrawer"
side="right"
:width="256"
:persistent="false"
>
<q-scroll-area class="fit text-grey-8">
<div id="rightPanel"></div>
</q-scroll-area>
</q-drawer>
<q-footer v-if="quasar.platform.is.mobile"></q-footer> <q-footer v-if="quasar.platform.is.mobile"></q-footer>
</q-layout> </q-layout>
</template> </template>

View File

@ -81,22 +81,47 @@ const statesFilter = {
/> />
<fetch-data url="ClaimStates" @on-fetch="setClaimStates" auto-load /> <fetch-data url="ClaimStates" @on-fetch="setClaimStates" auto-load />
<div class="container"> <div class="column items-center">
<q-card> <q-card>
<form-model :url="`Claims/${route.params.id}`" :filter="claimFilter" model="claim"> <form-model
:url="`Claims/${route.params.id}`"
:filter="claimFilter"
model="claim"
>
<template #form="{ data, validate, filter }"> <template #form="{ data, validate, filter }">
<div class="row q-gutter-md q-mb-md"> <div class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<q-input v-model="data.client.name" :label="t('claim.basicData.customer')" disable /> <q-input
v-model="data.client.name"
:label="t('claim.basicData.customer')"
disable
/>
</div> </div>
<div class="col"> <div class="col">
<q-input v-model="data.created" mask="####-##-##" fill-mask="_" autofocus> <q-input
v-model="data.created"
mask="####-##-##"
fill-mask="_"
autofocus
>
<template #append> <template #append>
<q-icon name="event" class="cursor-pointer"> <q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale"> <q-popup-proxy
<q-date v-model="data.created" mask="YYYY-MM-DD"> cover
transition-show="scale"
transition-hide="scale"
>
<q-date
v-model="data.created"
mask="YYYY-MM-DD"
>
<div class="row items-center justify-end"> <div class="row items-center justify-end">
<q-btn v-close-popup label="Close" color="primary" flat /> <q-btn
v-close-popup
label="Close"
color="primary"
flat
/>
</div> </div>
</q-date> </q-date>
</q-popup-proxy> </q-popup-proxy>
@ -116,7 +141,9 @@ const statesFilter = {
:label="t('claim.basicData.assignedTo')" :label="t('claim.basicData.assignedTo')"
map-options map-options
use-input use-input
@filter="(value, update) => filter(value, update, workerFilter)" @filter="
(value, update) => filter(value, update, workerFilter)
"
:rules="validate('claim.claimStateFk')" :rules="validate('claim.claimStateFk')"
:input-debounce="0" :input-debounce="0"
> >
@ -141,7 +168,9 @@ const statesFilter = {
:label="t('claim.basicData.state')" :label="t('claim.basicData.state')"
map-options map-options
use-input use-input
@filter="(value, update) => filter(value, update, statesFilter)" @filter="
(value, update) => filter(value, update, statesFilter)
"
:rules="validate('claim.claimStateFk')" :rules="validate('claim.claimStateFk')"
:input-debounce="0" :input-debounce="0"
> >
@ -166,7 +195,10 @@ const statesFilter = {
</div> </div>
<div class="row q-gutter-md q-mb-md"> <div class="row q-gutter-md q-mb-md">
<div class="col"> <div class="col">
<q-checkbox v-model="data.hasToPickUp" :label="t('claim.basicData.picked')" /> <q-checkbox
v-model="data.hasToPickUp"
:label="t('claim.basicData.picked')"
/>
</div> </div>
</div> </div>
</template> </template>
@ -176,12 +208,8 @@ const statesFilter = {
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.container {
display: flex;
justify-content: center;
}
.q-card { .q-card {
width: 800px; width: 100%;
max-width: 60em;
} }
</style> </style>

View File

@ -1,120 +1,190 @@
<script setup> <script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
import FetchData from 'components/FetchData.vue'; import { useStateStore } from 'stores/useStateStore';
import TeleportSlot from 'components/ui/TeleportSlot.vue';
import Paginate from 'src/components/PaginateData.vue';
import ClaimLogFilter from './ClaimLogFilter.vue';
import { toDate } from 'src/filters'; import { toDate } from 'src/filters';
// const quasar = useQuasar(); const stateStore = useStateStore();
const route = useRoute(); const route = useRoute();
const session = useSession(); const session = useSession();
const token = session.getToken(); const token = session.getToken();
const { t } = useI18n(); const { t } = useI18n();
const filter = { const columns = [
include: [ {
{ name: 'property',
relation: 'user', label: 'Property',
scope: { field: (row) => t(`properties.${row.property}`),
fields: ['name'], align: 'left',
include: { relation: 'worker', scope: { fields: ['id'] } },
},
},
],
where: {
originFk: route.params.id,
}, },
}; {
name: 'before',
label: 'Before',
field: (row) => formatValue(row.before),
},
{
name: 'after',
label: 'After',
field: (row) => formatValue(row.after),
},
];
const logs = ref(); function formatValue(value) {
function onFetch(data) { if (typeof value === 'boolean') {
//rows.value = data; return value ? t('Yes') : t('No');
logs.value = [];
for (const row of data) {
const changes = [];
const oldInstance = row.oldInstance;
const newInstance = row.newInstance;
for (const property in oldInstance) {
let oldValue = oldInstance[property];
let newValue = newInstance[property];
// if (isNaN(oldValue) && !isNaN(Date.parse(oldValue))) {
// oldValue = toDate(oldValue);
// }
// if (isNaN(newValue) && !isNaN(Date.parse(newValue))) {
// newValue = toDate(newValue);
// }
const change = {
property: property,
value: `${oldValue} -> ${newValue}`,
};
changes.push(change);
}
logs.value.push({
model: row.changedModel,
created: row.creationDate,
userFk: row.userFk,
changes: changes,
});
} }
if (isNaN(value) && !isNaN(Date.parse(value))) {
return toDate(value);
}
if (value === undefined) {
return t('Nothing');
}
return `"${value}"`;
}
function actionColor(action) {
if (action === 'insert') return 'positive';
if (action === 'update') return 'positive';
if (action === 'delete') return 'negative';
} }
</script> </script>
<template> <template>
<fetch-data url="ClaimLogs" :filter="filter" @on-fetch="onFetch" auto-load /> <div class="column items-center">
<q-timeline class="q-pa-md">
<div class="q-px-lg"> <q-timeline-entry heading tag="h4"> {{ t('Audit logs') }} </q-timeline-entry>
<q-timeline> <Paginate
<q-timeline-entry heading> Logs </q-timeline-entry> data-key="ClaimLogs"
<template v-for="log of logs" :key="log.id"> :url="`Claims/${route.params.id}/logs`"
<q-timeline-entry order="id DESC"
:title="t(`models.${log.model}`)" :offset="100"
:subtitle="toDate(log.created)" :limit="5"
:avatar="`/api/Images/user/160x160/${log.userFk}/download?access_token=${token}`" auto-load
> >
<q-list <template #body="{ rows }">
v-for="change of log.changes" <template v-for="log of rows" :key="log.id">
:key="change.property" <q-timeline-entry
dense :avatar="`/api/Images/user/160x160/${log.userFk}/download?access_token=${token}`"
style="width: 500px" >
> <template #subtitle>
<q-item> {{ log.userName }} -
<q-item-section> {{
<q-item-label caption> toDate(log.created, {
{{ t(`properties.${change.property}`) }} dateStyle: 'medium',
</q-item-label> timeStyle: 'short',
</q-item-section> })
<q-item-section> }}
<q-item-label>{{ change.value }}</q-item-label> </template>
</q-item-section> <template #title>
</q-item> <q-chip :color="actionColor(log.action)">
</q-list> {{ t(`actions.${log.action}`) }}
<!-- <div v-for="change of log.changes" :key="change.property"> </q-chip>
{{ change.property }}: {{ change.value }} {{ t(`models.${log.model}`) }}
</div> --> </template>
</q-timeline-entry> <q-table
</template> :rows="log.changes"
:columns="columns"
row-key="property"
hide-pagination
dense
flat
>
<template #header="props">
<q-tr :props="props">
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
>
{{ t(col.label) }}
</q-th>
</q-tr>
</template>
</q-table>
</q-timeline-entry>
</template>
</template>
</Paginate>
</q-timeline> </q-timeline>
</div> </div>
<TeleportSlot to="#actions-append">
<div class="row q-gutter-x-sm">
<q-btn flat @click="stateStore.toggleRightDrawer()" round dense icon="menu">
<q-tooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</q-tooltip>
</q-btn>
</div>
</TeleportSlot>
<q-drawer v-model="stateStore.rightDrawer" show-if-above side="right" :width="300">
<q-scroll-area class="fit text-grey-8">
<ClaimLogFilter data-key="ClaimLogs" />
</q-scroll-area>
</q-drawer>
</template> </template>
<style lang="scss" scoped>
.q-timeline {
width: 100%;
max-width: 80em;
}
</style>
<i18n> <i18n>
en: en:
actions:
insert: Creates
update: Updates
delete: Deletes
models: models:
Claim: Claim Claim: Claim
ClaimDms: Document
ClaimBeginning: Claimed Sales
ClaimObservation: Observation
properties: properties:
id: ID id: ID
claimFk: Claim ID
saleFk: Sale ID
quantity: Quantity
observation: Observation observation: Observation
ticketCreated: Created
created: Created
isChargedToMana: Charged to mana
hasToPickUp: Has to pick Up
dmsFk: Document ID
text: Description
es: es:
Audit logs: Registros de auditoría
Property: Propiedad
Before: Antes
After: Después
Yes: Si
Nothing: Nada
actions:
insert: Crea
update: Actualiza
delete: Elimina
models: models:
Claim: Reclamación Claim: Reclamación
ClaimDms: Documento
ClaimBeginning: Línea reclamada
ClaimObservation: Observación
properties: properties:
id: ID id: ID
claimFk: ID reclamación
saleFk: ID linea de venta
quantity: Cantidad
observation: Observación observation: Observación
ticketCreated: Creado
created: Creado
isChargedToMana: Cargado a maná
hasToPickUp: Se debe recoger
dmsFk: ID documento
text: Descripción
</i18n> </i18n>

View File

@ -0,0 +1,79 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const workers = ref();
</script>
<template>
<fetch-data
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
@on-fetch="(data) => (workers = data)"
auto-load
/>
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params, searchFn }">
<q-date
v-model="params.created"
@update:model-value="searchFn()"
dense
flat
minimal
>
</q-date>
<q-list dense>
<q-separator />
<q-item>
<q-item-section v-if="!workers">
<q-skeleton type="QInput" class="full-width" />
</q-item-section>
<q-item-section v-if="workers">
<q-select
:label="t('User')"
v-model="params.userFk"
@update:model-value="searchFn()"
:options="workers"
option-value="id"
option-label="name"
emit-value
map-options
use-input
:input-debounce="0"
/>
</q-item-section>
</q-item>
</q-list>
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
search: Contains
userFk: User
created: Created
es:
params:
search: Contiene
userFk: Usuario
created: Creada
User: Usuario
</i18n>

View File

@ -239,7 +239,7 @@ function onDrag() {
</div> </div>
</div> </div>
<teleport-slot v-if="!quasar.platform.is.mobile" to="#header-actions"> <teleport-slot v-if="!quasar.platform.is.mobile" to="#actions-prepend">
<div class="row q-gutter-x-sm"> <div class="row q-gutter-x-sm">
<label for="fileInput"> <label for="fileInput">
<q-btn <q-btn

View File

@ -84,58 +84,61 @@ async function remove(id) {
@on-fetch="onFetch" @on-fetch="onFetch"
auto-load auto-load
/> />
<paginate data-key="ClaimRma" url="ClaimRmas"> <div class="column items-center">
<template #body="{ rows }"> <div class="list">
<q-card class="card"> <paginate data-key="ClaimRma" url="ClaimRmas">
<template v-for="(row, index) of rows" :key="row.id"> <template #body="{ rows }">
<q-item class="q-pa-none items-start"> <q-card class="card">
<q-item-section class="q-pa-md"> <template v-for="(row, index) of rows" :key="row.id">
<q-list> <q-item class="q-pa-none items-start">
<q-item class="q-pa-none"> <q-item-section class="q-pa-md">
<q-item-section> <q-list>
<q-item-label caption> <q-item class="q-pa-none">
{{ t('claim.rma.user') }} <q-item-section>
</q-item-label> <q-item-label caption>
<q-item-label> {{ t('claim.rma.user') }}
{{ row.worker.user.name }} </q-item-label>
</q-item-label> <q-item-label>
</q-item-section> {{ row.worker.user.name }}
</q-item> </q-item-label>
<q-item class="q-pa-none"> </q-item-section>
<q-item-section> </q-item>
<q-item-label caption> <q-item class="q-pa-none">
{{ t('claim.rma.created') }} <q-item-section>
</q-item-label> <q-item-label caption>
<q-item-label> {{ t('claim.rma.created') }}
{{ </q-item-label>
toDate(row.created, { <q-item-label>
timeStyle: 'medium', {{
}) toDate(row.created, {
}} timeStyle: 'medium',
</q-item-label> })
</q-item-section> }}
</q-item> </q-item-label>
</q-list> </q-item-section>
</q-item-section> </q-item>
<q-card-actions vertical class="justify-between"> </q-list>
<q-btn </q-item-section>
flat <q-card-actions vertical class="justify-between">
round <q-btn
color="orange" flat
icon="vn:bin" round
@click="confirmRemove(row.id)" color="orange"
> icon="vn:bin"
<q-tooltip>{{ t('globals.remove') }}</q-tooltip> @click="confirmRemove(row.id)"
</q-btn> >
</q-card-actions> <q-tooltip>{{ t('globals.remove') }}</q-tooltip>
</q-item> </q-btn>
<q-separator v-if="index !== rows.length - 1" /> </q-card-actions>
</q-item>
<q-separator v-if="index !== rows.length - 1" />
</template>
</q-card>
</template> </template>
</q-card> </paginate>
</template> </div>
</paginate> </div>
<teleport-slot v-if="!quasar.platform.is.mobile" to="#actions-prepend">
<teleport-slot v-if="!quasar.platform.is.mobile" to="#header-actions">
<div class="row q-gutter-x-sm"> <div class="row q-gutter-x-sm">
<q-btn @click="addRow()" icon="add" color="primary" dense rounded> <q-btn @click="addRow()" icon="add" color="primary" dense rounded>
<q-tooltip bottom> {{ t('globals.add') }} </q-tooltip> <q-tooltip bottom> {{ t('globals.add') }} </q-tooltip>
@ -152,6 +155,10 @@ async function remove(id) {
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.list {
width: 100%;
max-width: 60em;
}
.q-toolbar { .q-toolbar {
background-color: $grey-9; background-color: $grey-9;
} }

View File

@ -1,5 +1,4 @@
<script setup> <script setup>
import { onMounted, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
@ -17,9 +16,6 @@ const router = useRouter();
const quasar = useQuasar(); const quasar = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
function stateColor(code) { function stateColor(code) {
if (code === 'pending') return 'green'; if (code === 'pending') return 'green';
if (code === 'managed') return 'orange'; if (code === 'managed') return 'orange';
@ -48,67 +44,83 @@ function viewSummary(id) {
:info="t('You can search by claim id or customer name')" :info="t('You can search by claim id or customer name')"
/> />
</teleport-slot> </teleport-slot>
<teleport-slot to="#rightPanel"> <teleport-slot to="#actions-append">
<ClaimFilter data-key="ClaimList" /> <div class="row q-gutter-x-sm">
<q-btn flat @click="stateStore.toggleRightDrawer()" round dense icon="menu">
<q-tooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</q-tooltip>
</q-btn>
</div>
</teleport-slot> </teleport-slot>
<q-page class="q-pa-md"> <q-drawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<paginate data-key="ClaimList" url="Claims/filter" order="id DESC" auto-load> <q-scroll-area class="fit text-grey-8">
<template #body="{ rows }"> <ClaimFilter data-key="ClaimList" />
<q-card class="card" v-for="row of rows" :key="row.id"> </q-scroll-area>
<q-item </q-drawer>
class="q-pa-none items-start cursor-pointer q-hoverable" <q-page class="column items-center q-pa-md">
v-ripple <div class="card-list">
clickable <paginate data-key="ClaimList" url="Claims/filter" order="id DESC" auto-load>
> <template #body="{ rows }">
<q-item-section class="q-pa-md" @click="navigate(row.id)"> <q-card class="card q-mb-md" v-for="row of rows" :key="row.id">
<div class="text-h6 link"> <q-item
{{ row.clientName }} class="q-pa-none items-start cursor-pointer q-hoverable"
</div> v-ripple
<q-item-label caption>#{{ row.id }}</q-item-label> clickable
<q-list> >
<q-item class="q-pa-none"> <q-item-section class="q-pa-md" @click="navigate(row.id)">
<q-item-section> <div class="text-h6 link">
<q-item-label caption> {{ row.clientName }}
{{ t('claim.list.customer') }} </div>
</q-item-label> <q-item-label caption>#{{ row.id }}</q-item-label>
<q-item-label>{{ row.clientName }}</q-item-label> <q-list>
</q-item-section> <q-item class="q-pa-none">
<q-item-section> <q-item-section>
<q-item-label caption> <q-item-label caption>
{{ t('claim.list.assignedTo') }} {{ t('claim.list.customer') }}
</q-item-label> </q-item-label>
<q-item-label>{{ row.workerName }}</q-item-label> <q-item-label>
</q-item-section> {{ row.clientName }}
</q-item> </q-item-label>
<q-item class="q-pa-none"> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label caption> <q-item-label caption>
{{ t('claim.list.created') }} {{ t('claim.list.assignedTo') }}
</q-item-label> </q-item-label>
<q-item-label> <q-item-label>
{{ toDate(row.created) }} {{ row.workerName }}
</q-item-label> </q-item-label>
</q-item-section> </q-item-section>
<q-item-section> </q-item>
<q-item-label caption> <q-item class="q-pa-none">
{{ t('claim.list.state') }} <q-item-section>
</q-item-label> <q-item-label caption>
<q-item-label> {{ t('claim.list.created') }}
<q-badge </q-item-label>
:color="stateColor(row.stateCode)" <q-item-label>
class="q-ma-none" {{ toDate(row.created) }}
dense </q-item-label>
> </q-item-section>
{{ row.stateDescription }} <q-item-section>
</q-badge> <q-item-label caption>
</q-item-label> {{ t('claim.list.state') }}
</q-item-section> </q-item-label>
</q-item> <q-item-label>
</q-list> <q-badge
</q-item-section> :color="stateColor(row.stateCode)"
<q-separator vertical /> class="q-ma-none"
<q-card-actions vertical class="justify-between"> dense
<!-- <q-btn color="grey-7" round flat icon="more_vert"> >
{{ row.stateDescription }}
</q-badge>
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-item-section>
<q-separator vertical />
<q-card-actions vertical class="justify-between">
<!-- <q-btn color="grey-7" round flat icon="more_vert">
<q-tooltip>{{ t('customer.list.moreOptions') }}</q-tooltip> <q-tooltip>{{ t('customer.list.moreOptions') }}</q-tooltip>
<q-menu cover auto-close> <q-menu cover auto-close>
<q-list> <q-list>
@ -128,44 +140,52 @@ function viewSummary(id) {
</q-menu> </q-menu>
</q-btn> --> </q-btn> -->
<q-btn <q-btn
flat flat
round round
color="orange" color="orange"
icon="arrow_circle_right" icon="arrow_circle_right"
@click="navigate(row.id)" @click="navigate(row.id)"
> >
<q-tooltip> <q-tooltip>
{{ t('components.smartCard.openCard') }} {{ t('components.smartCard.openCard') }}
</q-tooltip> </q-tooltip>
</q-btn> </q-btn>
<q-btn <q-btn
flat flat
round round
color="grey-7" color="grey-7"
icon="preview" icon="preview"
@click="viewSummary(row.id)" @click="viewSummary(row.id)"
> >
<q-tooltip> <q-tooltip>
{{ t('components.smartCard.openSummary') }} {{ t('components.smartCard.openSummary') }}
</q-tooltip> </q-tooltip>
</q-btn> </q-btn>
<q-btn flat round color="grey-7" icon="vn:client"> <q-btn flat round color="grey-7" icon="vn:client">
<q-tooltip> <q-tooltip>
{{ t('components.smartCard.viewDescription') }} {{ t('components.smartCard.viewDescription') }}
</q-tooltip> </q-tooltip>
<q-popup-proxy> <q-popup-proxy>
<CustomerDescriptorPopover :id="row.clientFk" /> <CustomerDescriptorPopover :id="row.clientFk" />
</q-popup-proxy> </q-popup-proxy>
</q-btn> </q-btn>
</q-card-actions> </q-card-actions>
</q-item> </q-item>
</q-card> </q-card>
</template> </template>
</paginate> </paginate>
</div>
</q-page> </q-page>
</template> </template>
<style lang="scss" scoped>
.card-list {
width: 100%;
max-width: 60em;
}
</style>
<i18n> <i18n>
es: es:
Search claim: Buscar reclamación Search claim: Buscar reclamación

View File

@ -58,7 +58,7 @@ const filterOptions = {
@on-fetch="(data) => (businessTypes = data)" @on-fetch="(data) => (businessTypes = data)"
auto-load auto-load
/> />
<div class="container"> <div class="column items-center">
<q-card> <q-card>
<form-model :url="`Clients/${route.params.id}`" model="customer"> <form-model :url="`Clients/${route.params.id}`" model="customer">
<template #form="{ data, validate, filter }"> <template #form="{ data, validate, filter }">
@ -172,11 +172,6 @@ const filterOptions = {
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.container {
display: flex;
justify-content: center;
}
.q-card { .q-card {
width: 800px; width: 800px;
} }

View File

@ -1,5 +1,4 @@
<script setup> <script setup>
import { onMounted, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
@ -15,9 +14,6 @@ const router = useRouter();
const quasar = useQuasar(); const quasar = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
function navigate(id) { function navigate(id) {
router.push({ path: `/customer/${id}` }); router.push({ path: `/customer/${id}` });
} }
@ -40,44 +36,61 @@ function viewSummary(id) {
:info="t('You can search by customer id or name')" :info="t('You can search by customer id or name')"
/> />
</teleport-slot> </teleport-slot>
<teleport-slot to="#rightPanel"> <teleport-slot to="#actions-append">
<CustomerFilter data-key="CustomerList" /> <div class="row q-gutter-x-sm">
<q-btn flat @click="stateStore.toggleRightDrawer()" round dense icon="menu">
<q-tooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</q-tooltip>
</q-btn>
</div>
</teleport-slot> </teleport-slot>
<q-page class="q-pa-md"> <q-drawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<paginate data-key="CustomerList" url="/Clients/filter" order="id DESC" auto-load> <q-scroll-area class="fit text-grey-8">
<template #body="{ rows }"> <CustomerFilter data-key="CustomerList" />
<q-card class="card" v-for="row of rows" :key="row.id"> </q-scroll-area>
<q-item </q-drawer>
class="q-pa-none items-start cursor-pointer q-hoverable" <q-page class="column items-center q-pa-md">
v-ripple <div class="card-list">
clickable <paginate
> data-key="CustomerList"
<q-item-section class="q-pa-md" @click="navigate(row.id)"> url="/Clients/filter"
<div class="text-h6">{{ row.name }}</div> order="id DESC"
<q-item-label caption>#{{ row.id }}</q-item-label> auto-load
>
<template #body="{ rows }">
<q-card class="card q-mb-md" v-for="row of rows" :key="row.id">
<q-item
class="q-pa-none items-start cursor-pointer q-hoverable"
v-ripple
clickable
>
<q-item-section class="q-pa-md" @click="navigate(row.id)">
<div class="text-h6">{{ row.name }}</div>
<q-item-label caption>#{{ row.id }}</q-item-label>
<q-list> <q-list>
<q-item class="q-pa-none"> <q-item class="q-pa-none">
<q-item-section> <q-item-section>
<q-item-label caption> <q-item-label caption>
{{ t('customer.list.email') }} {{ t('customer.list.email') }}
</q-item-label> </q-item-label>
<q-item-label>{{ row.email }}</q-item-label> <q-item-label>{{ row.email }}</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item class="q-pa-none"> <q-item class="q-pa-none">
<q-item-section> <q-item-section>
<q-item-label caption> <q-item-label caption>
{{ t('customer.list.phone') }} {{ t('customer.list.phone') }}
</q-item-label> </q-item-label>
<q-item-label>{{ row.phone }}</q-item-label> <q-item-label>{{ row.phone }}</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
</q-list> </q-list>
</q-item-section> </q-item-section>
<q-separator vertical /> <q-separator vertical />
<q-card-actions vertical class="justify-between"> <q-card-actions vertical class="justify-between">
<!-- <q-btn color="grey-7" round flat icon="more_vert"> <!-- <q-btn color="grey-7" round flat icon="more_vert">
<q-tooltip>{{ t('customer.list.moreOptions') }}</q-tooltip> <q-tooltip>{{ t('customer.list.moreOptions') }}</q-tooltip>
<q-menu cover auto-close> <q-menu cover auto-close>
<q-list> <q-list>
@ -97,39 +110,47 @@ function viewSummary(id) {
</q-menu> </q-menu>
</q-btn> --> </q-btn> -->
<q-btn <q-btn
flat flat
round round
color="orange" color="orange"
icon="arrow_circle_right" icon="arrow_circle_right"
@click="navigate(row.id)" @click="navigate(row.id)"
> >
<q-tooltip> <q-tooltip>
{{ t('components.smartCard.openCard') }} {{ t('components.smartCard.openCard') }}
</q-tooltip> </q-tooltip>
</q-btn> </q-btn>
<q-btn <q-btn
flat flat
round round
color="grey-7" color="grey-7"
icon="preview" icon="preview"
@click="viewSummary(row.id)" @click="viewSummary(row.id)"
> >
<q-tooltip> <q-tooltip>
{{ t('components.smartCard.openSummary') }} {{ t('components.smartCard.openSummary') }}
</q-tooltip> </q-tooltip>
</q-btn> </q-btn>
<!-- <q-btn flat round color="grey-7" icon="vn:ticket"> <!-- <q-btn flat round color="grey-7" icon="vn:ticket">
<q-tooltip>{{ t('customer.list.customerOrders') }}</q-tooltip> <q-tooltip>{{ t('customer.list.customerOrders') }}</q-tooltip>
</q-btn> --> </q-btn> -->
</q-card-actions> </q-card-actions>
</q-item> </q-item>
</q-card> </q-card>
</template> </template>
</paginate> </paginate>
</div>
</q-page> </q-page>
</template> </template>
<style lang="scss" scoped>
.card-list {
width: 100%;
max-width: 60em;
}
</style>
<i18n> <i18n>
es: es:
Search customer: Buscar cliente Search customer: Buscar cliente

View File

@ -41,113 +41,135 @@ function viewSummary(id) {
:info="t('You can search by invoice reference')" :info="t('You can search by invoice reference')"
/> />
</teleport-slot> </teleport-slot>
<teleport-slot to="#rightPanel"> <teleport-slot to="#actions-append">
<InvoiceOutFilter data-key="InvoiceOutList" /> <div class="row q-gutter-x-sm">
<q-btn flat @click="stateStore.toggleRightDrawer()" round dense icon="menu">
<q-tooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</q-tooltip>
</q-btn>
</div>
</teleport-slot> </teleport-slot>
<q-page class="q-pa-md"> <q-drawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<paginate <q-scroll-area class="fit text-grey-8">
data-key="InvoiceOutList" <InvoiceOutFilter data-key="InvoiceOutList" />
url="InvoiceOuts/filter" </q-scroll-area>
order="issued DESC, id DESC" </q-drawer>
auto-load <q-page class="column items-center q-pa-md">
> <div class="card-list">
<template #body="{ rows }"> <paginate
<q-card class="card" v-for="row of rows" :key="row.id"> data-key="InvoiceOutList"
<q-item url="InvoiceOuts/filter"
class="q-pa-none items-start cursor-pointer q-hoverable" order="issued DESC, id DESC"
v-ripple auto-load
clickable >
> <template #body="{ rows }">
<q-item-section class="q-pa-md" @click="navigate(row.id)"> <q-card class="card q-mb-md" v-for="row of rows" :key="row.id">
<div class="text-h6">{{ row.ref }}</div> <q-item
<q-item-label caption>#{{ row.id }}</q-item-label> class="q-pa-none items-start cursor-pointer q-hoverable"
<q-list> v-ripple
<q-item class="q-pa-none"> clickable
<q-item-section> >
<q-item-label caption> <q-item-section class="q-pa-md" @click="navigate(row.id)">
{{ t('invoiceOut.list.issued') }} <div class="text-h6">{{ row.ref }}</div>
</q-item-label> <q-item-label caption>#{{ row.id }}</q-item-label>
<q-item-label> <q-list>
{{ toDate(row.issued) }} <q-item class="q-pa-none">
</q-item-label> <q-item-section>
</q-item-section> <q-item-label caption>
<q-item-section> {{ t('invoiceOut.list.issued') }}
<q-item-label caption> </q-item-label>
{{ t('invoiceOut.list.amount') }} <q-item-label>
</q-item-label> {{ toDate(row.issued) }}
<q-item-label> </q-item-label>
{{ toCurrency(row.amount) }} </q-item-section>
</q-item-label> <q-item-section>
</q-item-section> <q-item-label caption>
</q-item> {{ t('invoiceOut.list.amount') }}
<q-item class="q-pa-none"> </q-item-label>
<q-item-section> <q-item-label>
<q-item-label caption> {{ toCurrency(row.amount) }}
{{ t('invoiceOut.list.client') }} </q-item-label>
</q-item-label> </q-item-section>
<q-item-label> </q-item>
{{ row.clientSocialName }} <q-item class="q-pa-none">
</q-item-label> <q-item-section>
</q-item-section> <q-item-label caption>
<q-item-section> {{ t('invoiceOut.list.client') }}
<q-item-label caption> </q-item-label>
{{ t('invoiceOut.list.created') }} <q-item-label>
</q-item-label> {{ row.clientSocialName }}
<q-item-label> </q-item-label>
{{ toDate(row.created) }} </q-item-section>
</q-item-label> <q-item-section>
</q-item-section> <q-item-label caption>
</q-item> {{ t('invoiceOut.list.created') }}
<q-item class="q-pa-none"> </q-item-label>
<q-item-section> <q-item-label>
<q-item-label caption> {{ toDate(row.created) }}
{{ t('invoiceOut.list.company') }} </q-item-label>
</q-item-label> </q-item-section>
<q-item-label>{{ row.companyCode }}</q-item-label> </q-item>
</q-item-section> <q-item class="q-pa-none">
<q-item-section> <q-item-section>
<q-item-label caption> <q-item-label caption>
{{ t('invoiceOut.list.dued') }} {{ t('invoiceOut.list.company') }}
</q-item-label> </q-item-label>
<q-item-label> <q-item-label>{{
{{ toDate(row.dued) }} row.companyCode
</q-item-label> }}</q-item-label>
</q-item-section> </q-item-section>
</q-item> <q-item-section>
</q-list> <q-item-label caption>
</q-item-section> {{ t('invoiceOut.list.dued') }}
<q-separator vertical /> </q-item-label>
<q-card-actions vertical class="justify-between"> <q-item-label>
<q-btn {{ toDate(row.dued) }}
flat </q-item-label>
round </q-item-section>
color="orange" </q-item>
icon="arrow_circle_right" </q-list>
@click="navigate(row.id)" </q-item-section>
> <q-separator vertical />
<q-tooltip> <q-card-actions vertical class="justify-between">
{{ t('components.smartCard.openCard') }} <q-btn
</q-tooltip> flat
</q-btn> round
<q-btn color="orange"
flat icon="arrow_circle_right"
round @click="navigate(row.id)"
color="grey-7" >
icon="preview" <q-tooltip>
@click="viewSummary(row.id)" {{ t('components.smartCard.openCard') }}
> </q-tooltip>
<q-tooltip> </q-btn>
{{ t('components.smartCard.openSummary') }} <q-btn
</q-tooltip> flat
</q-btn> round
</q-card-actions> color="grey-7"
</q-item> icon="preview"
</q-card> @click="viewSummary(row.id)"
</template> >
</paginate> <q-tooltip>
{{ t('components.smartCard.openSummary') }}
</q-tooltip>
</q-btn>
</q-card-actions>
</q-item>
</q-card>
</template>
</paginate>
</div>
</q-page> </q-page>
</template> </template>
<style lang="scss" scoped>
.card-list {
width: 100%;
max-width: 60em;
}
</style>
<i18n> <i18n>
es: es:
Search invoice: Buscar factura emitida Search invoice: Buscar factura emitida

View File

@ -78,120 +78,142 @@ function viewSummary(id) {
:info="t('You can search by ticket id or alias')" :info="t('You can search by ticket id or alias')"
/> />
</teleport-slot> </teleport-slot>
<teleport-slot to="#rightPanel"> <teleport-slot to="#actions-append">
<TicketFilter data-key="TicketList" /> <div class="row q-gutter-x-sm">
<q-btn flat @click="stateStore.toggleRightDrawer()" round dense icon="menu">
<q-tooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</q-tooltip>
</q-btn>
</div>
</teleport-slot> </teleport-slot>
<q-page class="q-pa-md"> <q-drawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<paginate <q-scroll-area class="fit text-grey-8">
data-key="TicketList" <TicketFilter data-key="TicketList" />
url="Tickets/filter" </q-scroll-area>
:filter="filter" </q-drawer>
order="id DESC" <q-page class="column items-center q-pa-md">
auto-load <div class="card-list">
> <paginate
<template #body="{ rows }"> data-key="TicketList"
<q-card class="card" v-for="row of rows" :key="row.id"> url="Tickets/filter"
<q-item :filter="filter"
class="q-pa-none items-start cursor-pointer q-hoverable" order="id DESC"
v-ripple auto-load
clickable >
> <template #body="{ rows }">
<q-item-section class="q-pa-md" @click="navigate(row.id)"> <q-card class="card q-mb-md" v-for="row of rows" :key="row.id">
<div class="text-h6">{{ row.name }}</div> <q-item
<q-item-label caption>#{{ row.id }}</q-item-label> class="q-pa-none items-start cursor-pointer q-hoverable"
<q-list> v-ripple
<q-item class="q-pa-none"> clickable
<q-item-section> >
<q-item-label caption> <q-item-section class="q-pa-md" @click="navigate(row.id)">
{{ t('ticket.list.nickname') }} <div class="text-h6">{{ row.name }}</div>
</q-item-label> <q-item-label caption>#{{ row.id }}</q-item-label>
<q-item-label>{{ row.nickname }}</q-item-label> <q-list>
</q-item-section> <q-item class="q-pa-none">
<q-item-section> <q-item-section>
<q-item-label caption> <q-item-label caption>
{{ t('ticket.list.state') }} {{ t('ticket.list.nickname') }}
</q-item-label> </q-item-label>
<q-item-label> <q-item-label>{{
<q-badge row.nickname
:color="stateColor(row)" }}</q-item-label>
class="q-ma-none" </q-item-section>
dense <q-item-section>
> <q-item-label caption>
{{ row.state }} {{ t('ticket.list.state') }}
</q-badge> </q-item-label>
</q-item-label> <q-item-label>
</q-item-section> <q-badge
</q-item> :color="stateColor(row)"
<q-item class="q-pa-none"> class="q-ma-none"
<q-item-section> dense
<q-item-label caption> >
{{ t('ticket.list.shipped') }} {{ row.state }}
</q-item-label> </q-badge>
<q-item-label> </q-item-label>
{{ toDate(row.shipped) }} </q-item-section>
</q-item-label> </q-item>
</q-item-section> <q-item class="q-pa-none">
<q-item-section> <q-item-section>
<q-item-label caption> <q-item-label caption>
{{ t('Zone') }} {{ t('ticket.list.shipped') }}
</q-item-label> </q-item-label>
<q-item-label> <q-item-label>
{{ row.zoneName }} {{ toDate(row.shipped) }}
</q-item-label> </q-item-label>
</q-item-section> </q-item-section>
</q-item> <q-item-section>
<q-item class="q-pa-none"> <q-item-label caption>
<q-item-section> {{ t('Zone') }}
<q-item-label caption> </q-item-label>
{{ t('ticket.list.salesPerson') }} <q-item-label>
</q-item-label> {{ row.zoneName }}
<q-item-label> </q-item-label>
{{ row.salesPerson }} </q-item-section>
</q-item-label> </q-item>
</q-item-section> <q-item class="q-pa-none">
<q-item-section> <q-item-section>
<q-item-label caption> <q-item-label caption>
{{ t('ticket.list.total') }} {{ t('ticket.list.salesPerson') }}
</q-item-label> </q-item-label>
<q-item-label> <q-item-label>
{{ toCurrency(row.totalWithVat) }} {{ row.salesPerson }}
</q-item-label> </q-item-label>
</q-item-section> </q-item-section>
</q-item> <q-item-section>
</q-list> <q-item-label caption>
</q-item-section> {{ t('ticket.list.total') }}
<q-separator vertical /> </q-item-label>
<q-card-actions vertical class="justify-between"> <q-item-label>
<q-btn {{ toCurrency(row.totalWithVat) }}
flat </q-item-label>
round </q-item-section>
color="orange" </q-item>
icon="arrow_circle_right" </q-list>
@click="navigate(row.id)" </q-item-section>
> <q-separator vertical />
<q-tooltip> <q-card-actions vertical class="justify-between">
{{ t('components.smartCard.openCard') }} <q-btn
</q-tooltip> flat
</q-btn> round
<q-btn color="orange"
flat icon="arrow_circle_right"
round @click="navigate(row.id)"
color="grey-7" >
icon="preview" <q-tooltip>
@click="viewSummary(row.id)" {{ t('components.smartCard.openCard') }}
> </q-tooltip>
<q-tooltip> </q-btn>
{{ t('components.smartCard.openSummary') }} <q-btn
</q-tooltip> flat
</q-btn> round
</q-card-actions> color="grey-7"
</q-item> icon="preview"
</q-card> @click="viewSummary(row.id)"
</template> >
</paginate> <q-tooltip>
{{ t('components.smartCard.openSummary') }}
</q-tooltip>
</q-btn>
</q-card-actions>
</q-item>
</q-card>
</template>
</paginate>
</div>
</q-page> </q-page>
</template> </template>
<style lang="scss" scoped>
.card-list {
width: 100%;
max-width: 60em;
}
</style>
<i18n> <i18n>
es: es:
Search ticket: Buscar ticket Search ticket: Buscar ticket