refs 6105 claimNotes and VnNotes created #83
|
@ -0,0 +1,26 @@
|
||||||
|
<script setup>
|
||||||
|
import { useSession } from 'src/composables/useSession';
|
||||||
|
const $props = defineProps({
|
||||||
|
worker: { type: Number, required: true },
|
||||||
|
description: { type: String, default: null },
|
||||||
|
});
|
||||||
|
const session = useSession();
|
||||||
|
const token = session.getToken();
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="avatar-picture column items-center">
|
||||||
|
<QAvatar color="orange">
|
||||||
|
<QImg
|
||||||
|
:src="`/api/Images/user/160x160/${$props.worker}/download?access_token=${token}`"
|
||||||
|
spinner-color="white"
|
||||||
|
/>
|
||||||
|
</QAvatar>
|
||||||
|
<div class="description">
|
||||||
|
<slot name="description">
|
||||||
|
<p>
|
||||||
|
{{ $props.description }}
|
||||||
|
</p>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,123 @@
|
||||||
|
<script setup>
|
||||||
|
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||||
|
import VnAvatar from 'src/components/ui/VnAvatar.vue';
|
||||||
|
import { toDateHour } from 'src/filters';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import VnPaginate from './VnPaginate.vue';
|
||||||
|
|
||||||
|
const $props = defineProps({
|
||||||
|
id: { type: String, required: true },
|
||||||
|
url: { type: String, default: null },
|
||||||
|
filter: { type: Object, default: () => {} },
|
||||||
|
body: { type: Object, default: () => {} },
|
||||||
|
addNote: { type: Boolean, default: false },
|
||||||
|
});
|
||||||
|
const { t } = useI18n();
|
||||||
|
const noteModal = ref(false);
|
||||||
|
const newNote = ref('');
|
||||||
|
const vnPaginateRef = ref();
|
||||||
|
|
||||||
jorgep marked this conversation as resolved
|
|||||||
|
async function insert() {
|
||||||
|
const body = $props.body;
|
||||||
|
Object.assign(body, { text: newNote.value });
|
||||||
|
await axios.post($props.url, body);
|
||||||
|
vnPaginateRef.value.fetch();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="column items-center">
|
||||||
|
<VnPaginate
|
||||||
|
:data-key="$props.url"
|
||||||
|
:url="$props.url"
|
||||||
|
order="created DESC"
|
||||||
|
:limit="20"
|
||||||
|
:filter="$props.filter"
|
||||||
jorgep marked this conversation as resolved
Outdated
alexm
commented
Sols seria canviar el nom del ref, pq la url i tot si q ho tens dinamic Sols seria canviar el nom del ref, pq la url i tot si q ho tens dinamic
|
|||||||
|
auto-load
|
||||||
|
ref="vnPaginateRef"
|
||||||
|
>
|
||||||
|
<template #body="{ rows }">
|
||||||
|
<QCard class="q-pa-md q-mb-md" v-for="(note, index) in rows" :key="index">
|
||||||
|
<QCardSection horizontal>
|
||||||
jorgep marked this conversation as resolved
Outdated
alexm
commented
El ref este si no es gasta en cap lloc lleval El ref este si no es gasta en cap lloc lleval
|
|||||||
|
<slot name="picture">
|
||||||
jgallego marked this conversation as resolved
Outdated
alexm
commented
El QDialog dixal al final del html, si no queda confus El QDialog dixal al final del html, si no queda confus
|
|||||||
|
<VnAvatar :worker="note.workerFk" />
|
||||||
|
</slot>
|
||||||
|
<QItem class="full-width justify-between items-start">
|
||||||
|
<span class="link">
|
||||||
|
{{ `${note.worker.firstName} ${note.worker.lastName}` }}
|
||||||
|
<WorkerDescriptorProxy :id="note.worker.id" />
|
||||||
|
</span>
|
||||||
|
<slot name="actions">
|
||||||
|
{{ toDateHour(note.created) }}
|
||||||
|
</slot>
|
||||||
|
</QItem>
|
||||||
|
</QCardSection>
|
||||||
|
<QCardSection>
|
||||||
|
<slot name="text">
|
||||||
|
{{ note.text }}
|
||||||
|
</slot>
|
||||||
|
</QCardSection>
|
||||||
|
</QCard>
|
||||||
|
</template>
|
||||||
|
</VnPaginate>
|
||||||
|
<QPageSticky position="bottom-right" :offset="[25, 25]">
|
||||||
|
<QBtn
|
||||||
|
v-if="addNote"
|
||||||
|
color="primary"
|
||||||
|
icon="add"
|
||||||
|
size="lg"
|
||||||
|
round
|
||||||
|
@click="noteModal = true"
|
||||||
|
/>
|
||||||
|
</QPageSticky>
|
||||||
|
<QDialog v-model="noteModal" persistent>
|
||||||
|
<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') }}
|
||||||
jorgep marked this conversation as resolved
Outdated
alexm
commented
Podries gastar el component VnPaginate i te ahorraries el fetch de dalt i el v.for de aci.
Pots gastar el component i possarli v-bind="$attrs" i li pasa les propietats que a tu te hajen pasat. I ja no has ni de fenirles tu Podries gastar el component VnPaginate i te ahorraries el fetch de dalt i el v.for de aci.
Apart aixina tens millor logica per als filtros (VnPaginate te mes props que podrien ser utils en un futur):
```
dataKey: {
type: String,
required: true,
},
autoLoad: {
type: Boolean,
default: false,
},
data: {
type: Array,
default: null,
},
url: {
type: String,
default: '',
},
filter: {
type: Object,
default: null,
},
where: {
type: Object,
default: null,
},
order: {
type: String,
default: '',
},
limit: {
type: Number,
default: 10,
},
userParams: {
type: Object,
default: null,
},
offset: {
type: Number,
default: 500,
},
```
Pots gastar el component i possarli v-bind="$attrs" i li pasa les propietats que a tu te hajen pasat. I ja no has ni de fenirles tu
|
|||||||
|
</span>
|
||||||
|
<QBtn icon="close" flat round dense v-close-popup />
|
||||||
|
</QItem>
|
||||||
|
</QCardSection>
|
||||||
|
<QCardSection>
|
||||||
|
<QInput
|
||||||
|
autofocus
|
||||||
|
type="textarea"
|
||||||
|
:hint="t('Add note here...')"
|
||||||
|
filled
|
||||||
|
size="lg"
|
||||||
|
autogrow
|
||||||
|
v-model="newNote"
|
||||||
|
></QInput>
|
||||||
|
</QCardSection>
|
||||||
|
<QCardActions class="justify-end q-mr-sm">
|
||||||
|
<QBtn
|
||||||
|
flat
|
||||||
|
:label="t('globals.close')"
|
||||||
|
color="primary"
|
||||||
|
v-close-popup
|
||||||
|
/>
|
||||||
|
<QBtn
|
||||||
|
:label="t('globals.save')"
|
||||||
|
color="primary"
|
||||||
|
v-close-popup
|
||||||
|
@click="insert"
|
||||||
|
/>
|
||||||
|
</QCardActions>
|
||||||
|
</QCard>
|
||||||
|
</QDialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="scss" scoped>
|
||||||
jorgep marked this conversation as resolved
alexm
commented
fetch? Seria mas correcto submit, insert, etc fetch? Seria mas correcto submit, insert, etc
|
|||||||
|
.q-card {
|
||||||
|
max-width: 80em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<i18n>
|
||||||
|
es:
|
||||||
|
Add note here...: Añadir nota aquí...
|
||||||
jorgep marked this conversation as resolved
alexm
commented
Quasar te clases (paregut a bootstrap) que te dixa fer coses de estes i te ahorra CSS.
igual que la part de:
Es pot fer tambe: Quasar te clases (paregut a bootstrap) que te dixa fer coses de estes i te ahorra CSS.
Per exemple esta la clase column que ja te fa esta part:
```
display: flex;
flex-direction: column;
```
igual que la part de:
```
align-self: flex-start;
display: flex;
justify-content: space-between;
```
Es pot fer tambe:
https://quasar.dev/layout/grid/column#introduction
|
|||||||
|
Add note: Añadir nota
|
||||||
|
</i18n>
|
|
@ -49,6 +49,7 @@ const props = defineProps({
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['onFetch', 'onPaginate']);
|
const emit = defineEmits(['onFetch', 'onPaginate']);
|
||||||
|
defineExpose({ fetch });
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const pagination = ref({
|
const pagination = ref({
|
||||||
sortBy: props.order,
|
sortBy: props.order,
|
||||||
|
@ -82,7 +83,6 @@ async function fetch() {
|
||||||
if (!arrayData.hasMoreData.value) {
|
if (!arrayData.hasMoreData.value) {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('onFetch', store.data);
|
emit('onFetch', store.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import toLowerCase from './toLowerCase';
|
import toLowerCase from './toLowerCase';
|
||||||
import toDate from './toDate';
|
import toDate from './toDate';
|
||||||
import toDateString from './toDateString';
|
import toDateString from './toDateString';
|
||||||
|
import toDateHour from './toDateHour';
|
||||||
import toCurrency from './toCurrency';
|
import toCurrency from './toCurrency';
|
||||||
import toPercentage from './toPercentage';
|
import toPercentage from './toPercentage';
|
||||||
import toLowerCamel from './toLowerCamel';
|
import toLowerCamel from './toLowerCamel';
|
||||||
|
@ -11,6 +12,7 @@ export {
|
||||||
toLowerCamel,
|
toLowerCamel,
|
||||||
toDate,
|
toDate,
|
||||||
toDateString,
|
toDateString,
|
||||||
|
toDateHour,
|
||||||
toCurrency,
|
toCurrency,
|
||||||
toPercentage,
|
toPercentage,
|
||||||
dashIfEmpty,
|
dashIfEmpty,
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
export default function toDateHour(date) {
|
||||||
|
const dateHour = new Date(date).toLocaleDateString('es-ES', {
|
||||||
|
timeZone: 'Europe/Madrid',
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
});
|
||||||
|
return dateHour;
|
||||||
|
}
|
|
@ -266,6 +266,7 @@ export default {
|
||||||
rma: 'RMA',
|
rma: 'RMA',
|
||||||
photos: 'Photos',
|
photos: 'Photos',
|
||||||
log: 'Audit logs',
|
log: 'Audit logs',
|
||||||
|
notes: 'Notes',
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
customer: 'Customer',
|
customer: 'Customer',
|
||||||
|
|
|
@ -265,6 +265,7 @@ export default {
|
||||||
rma: 'RMA',
|
rma: 'RMA',
|
||||||
photos: 'Fotos',
|
photos: 'Fotos',
|
||||||
log: 'Registros de auditoría',
|
log: 'Registros de auditoría',
|
||||||
|
notes: 'Notas',
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
customer: 'Cliente',
|
customer: 'Cliente',
|
||||||
|
|
|
@ -3,11 +3,12 @@ import LeftMenu from 'components/LeftMenu.vue';
|
||||||
import { getUrl } from 'composables/getUrl';
|
import { getUrl } from 'composables/getUrl';
|
||||||
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
|
||||||
import { useStateStore } from 'stores/useStateStore';
|
import { useStateStore } from 'stores/useStateStore';
|
||||||
import { computed, onMounted } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import ClaimDescriptor from './ClaimDescriptor.vue';
|
import ClaimDescriptor from './ClaimDescriptor.vue';
|
||||||
|
|
||||||
|
import { onMounted } from 'vue';
|
||||||
const stateStore = useStateStore();
|
const stateStore = useStateStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
@ -21,11 +22,6 @@ const $props = defineProps({
|
||||||
const entityId = computed(() => {
|
const entityId = computed(() => {
|
||||||
return $props.id || route.params.id;
|
return $props.id || route.params.id;
|
||||||
});
|
});
|
||||||
const claimSections = [
|
|
||||||
{ name: 'Notes', url: '/note/index', icon: 'draft' },
|
|
||||||
{ name: 'Development', url: '/development', icon: 'vn:traceability' },
|
|
||||||
{ name: 'Action', url: '/action', icon: 'vn:actions' },
|
|
||||||
];
|
|
||||||
|
|
||||||
let salixUrl;
|
let salixUrl;
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
@ -49,17 +45,24 @@ onMounted(async () => {
|
||||||
<QSeparator />
|
<QSeparator />
|
||||||
<QList>
|
<QList>
|
||||||
<QItem
|
<QItem
|
||||||
v-for="section in claimSections"
|
|
||||||
:key="section.name"
|
|
||||||
active-class="text-primary"
|
active-class="text-primary"
|
||||||
:href="salixUrl + section.url"
|
|
||||||
clickable
|
clickable
|
||||||
v-ripple
|
v-ripple
|
||||||
|
:href="`${salixUrl}/development`"
|
||||||
>
|
>
|
||||||
<QItemSection avatar>
|
<QItemSection avatar>
|
||||||
<QIcon :name="section.icon" />
|
<QIcon name="vn:traceability"></QIcon>
|
||||||
</QItemSection>
|
</QItemSection>
|
||||||
<QItemSection> {{ t(section.name) }} </QItemSection>
|
<QItemSection>{{ t('Development') }}</QItemSection>
|
||||||
|
</QItem>
|
||||||
|
<QItem
|
||||||
|
active-class="text-primary"
|
||||||
|
clickable
|
||||||
|
v-ripple
|
||||||
|
:href="`${salixUrl}/action`"
|
||||||
|
>
|
||||||
|
<QItemSection avatar><QIcon name="vn:actions"></QIcon></QItemSection>
|
||||||
|
<QItemSection>{{ t('Action') }}</QItemSection>
|
||||||
</QItem>
|
</QItem>
|
||||||
</QList>
|
</QList>
|
||||||
</QScrollArea>
|
</QScrollArea>
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
<script setup>
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import { useState } from 'src/composables/useState';
|
||||||
|
import VnNotes from 'src/components/ui/VnNotes.vue';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const state = useState();
|
||||||
|
const user = state.getUser();
|
||||||
|
const id = route.params.id;
|
||||||
|
|
||||||
|
const claimFilter = {
|
||||||
|
where: { claimFk: id },
|
||||||
|
fields: ['created', 'workerFk', 'text'],
|
||||||
|
include: {
|
||||||
|
relation: 'worker',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'firstName', 'lastName'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
claimFk: id,
|
||||||
|
workerFk: user.value.id,
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="col items-center">
|
||||||
|
<VnNotes
|
||||||
|
:add-note="true"
|
||||||
|
:id="id"
|
||||||
|
url="claimObservations"
|
||||||
|
:filter="claimFilter"
|
||||||
|
:body="body"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -11,7 +11,14 @@ export default {
|
||||||
redirect: { name: 'ClaimMain' },
|
redirect: { name: 'ClaimMain' },
|
||||||
menus: {
|
menus: {
|
||||||
main: ['ClaimList', 'ClaimRmaList'],
|
main: ['ClaimList', 'ClaimRmaList'],
|
||||||
card: ['ClaimBasicData', 'ClaimLines', 'ClaimRma', 'ClaimPhotos', 'ClaimLog'],
|
card: [
|
||||||
|
'ClaimBasicData',
|
||||||
|
'ClaimLines',
|
||||||
|
'ClaimRma',
|
||||||
|
'ClaimPhotos',
|
||||||
|
'ClaimLog',
|
||||||
|
'ClaimNotes',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
@ -103,6 +110,15 @@ export default {
|
||||||
},
|
},
|
||||||
component: () => import('src/pages/Claim/Card/ClaimLog.vue'),
|
component: () => import('src/pages/Claim/Card/ClaimLog.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'ClaimNotes',
|
||||||
|
path: 'notes',
|
||||||
|
meta: {
|
||||||
|
title: 'notes',
|
||||||
|
icon: 'draft',
|
||||||
|
},
|
||||||
|
component: () => import('src/pages/Claim/Card/ClaimNotes.vue'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
/// <reference types="cypress" />
|
||||||
|
describe('ClaimNotes', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.login('developer');
|
||||||
|
cy.visit(`/#/claim/${2}/notes`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a new note', () => {
|
||||||
|
const message = 'This is a new message.';
|
||||||
|
cy.get('.q-page-sticky button').click();
|
||||||
|
cy.get('.q-dialog .q-card__section:nth-child(2)').type(message);
|
||||||
|
cy.get('.q-card__actions button:nth-child(2)').click();
|
||||||
|
cy.get('.q-card .q-card__section:nth-child(2)')
|
||||||
|
.eq(0)
|
||||||
|
.should('have.text', message);
|
||||||
|
});
|
||||||
|
});
|
Un component global, que se gastara en ticket, worker, etc no deuria tindre algo tan especific de claim