287 lines
8.6 KiB
Vue
287 lines
8.6 KiB
Vue
<script setup>
|
|
import axios from 'axios';
|
|
import { ref, reactive, useAttrs, computed } from 'vue';
|
|
import { onBeforeRouteLeave } from 'vue-router';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { useQuasar } from 'quasar';
|
|
|
|
import { toDateHourMin } from 'src/filters';
|
|
|
|
import VnPaginate from 'components/ui/VnPaginate.vue';
|
|
import VnUserLink from 'components/ui/VnUserLink.vue';
|
|
import VnConfirm from 'components/ui/VnConfirm.vue';
|
|
import VnAvatar from 'components/ui/VnAvatar.vue';
|
|
import VnRow from 'components/ui/VnRow.vue';
|
|
import VnSelect from 'components/common/VnSelect.vue';
|
|
import FetchData from 'components/FetchData.vue';
|
|
import VnInput from 'components/common/VnInput.vue';
|
|
|
|
const emit = defineEmits(['onFetch']);
|
|
|
|
const $attrs = useAttrs();
|
|
|
|
const isRequired = computed(() => {
|
|
return Object.keys($attrs).includes('required');
|
|
});
|
|
|
|
const $props = defineProps({
|
|
url: { type: String, default: null },
|
|
saveUrl: { type: String, default: null },
|
|
userFilter: { type: Object, default: () => {} },
|
|
filter: { type: Object, default: () => {} },
|
|
body: { type: Object, default: () => {} },
|
|
addNote: { type: Boolean, default: false },
|
|
selectType: { type: Boolean, default: false },
|
|
justInput: { type: Boolean, default: false },
|
|
});
|
|
|
|
const { t } = useI18n();
|
|
const quasar = useQuasar();
|
|
const newNote = reactive({ text: null, observationTypeFk: null });
|
|
const observationTypes = ref([]);
|
|
const vnPaginateRef = ref();
|
|
let originalText;
|
|
|
|
function handleClick(e) {
|
|
if (e.shiftKey && e.key === 'Enter') return;
|
|
if ($props.justInput) confirmAndUpdate();
|
|
else insert();
|
|
}
|
|
|
|
async function insert() {
|
|
if (!newNote.text || ($props.selectType && !newNote.observationTypeFk)) return;
|
|
|
|
const body = $props.body;
|
|
const newBody = {
|
|
...body,
|
|
...{ text: newNote.text, observationTypeFk: newNote.observationTypeFk },
|
|
};
|
|
await axios.post($props.url, newBody);
|
|
await vnPaginateRef.value.fetch();
|
|
}
|
|
|
|
function confirmAndUpdate() {
|
|
if (!newNote.text && originalText)
|
|
quasar
|
|
.dialog({
|
|
component: VnConfirm,
|
|
componentProps: {
|
|
title: t('New note is empty'),
|
|
message: t('Are you sure remove this note?'),
|
|
},
|
|
})
|
|
.onOk(update)
|
|
.onCancel(() => {
|
|
newNote.text = originalText;
|
|
});
|
|
else update();
|
|
}
|
|
|
|
async function update() {
|
|
originalText = newNote.text;
|
|
const body = $props.body;
|
|
const newBody = {
|
|
...body,
|
|
...{ notes: newNote.text },
|
|
};
|
|
await axios.patch(
|
|
`${$props.saveUrl ?? `${$props.url}/${$props.body.workerFk}`}`,
|
|
newBody,
|
|
);
|
|
}
|
|
|
|
onBeforeRouteLeave((to, from, next) => {
|
|
if (
|
|
(newNote.text && !$props.justInput) ||
|
|
(newNote.text !== originalText && $props.justInput)
|
|
)
|
|
quasar.dialog({
|
|
component: VnConfirm,
|
|
componentProps: {
|
|
title: t('globals.unsavedPopup.title'),
|
|
message: t('globals.unsavedPopup.subtitle'),
|
|
promise: () => next(),
|
|
},
|
|
});
|
|
else next();
|
|
});
|
|
|
|
function fetchData([data]) {
|
|
newNote.text = data?.notes;
|
|
originalText = data?.notes;
|
|
emit('onFetch', data);
|
|
}
|
|
</script>
|
|
<template>
|
|
<FetchData
|
|
v-if="selectType"
|
|
url="ObservationTypes"
|
|
:filter="{ fields: ['id', 'description'] }"
|
|
auto-load
|
|
@on-fetch="(data) => (observationTypes = data)"
|
|
/>
|
|
<FetchData
|
|
v-if="justInput"
|
|
:url="url"
|
|
:filter="filter"
|
|
@on-fetch="fetchData"
|
|
auto-load
|
|
/>
|
|
<QCard
|
|
class="q-pa-xs q-mb-lg full-width"
|
|
:class="{ 'just-input': $props.justInput }"
|
|
v-if="$props.addNote || $props.justInput"
|
|
>
|
|
<QCardSection horizontal v-if="!$props.justInput">
|
|
{{ t('New note') }}
|
|
</QCardSection>
|
|
<QCardSection class="q-px-xs q-my-none q-py-none">
|
|
<VnRow class="full-width">
|
|
<VnSelect
|
|
:label="t('Observation type')"
|
|
v-if="selectType"
|
|
url="ObservationTypes"
|
|
v-model="newNote.observationTypeFk"
|
|
option-label="description"
|
|
style="flex: 0.15"
|
|
:required="isRequired"
|
|
@keyup.enter.stop="insert"
|
|
/>
|
|
<VnInput
|
|
v-model.trim="newNote.text"
|
|
type="textarea"
|
|
:label="$props.justInput && newNote.text ? '' : t('Add note here...')"
|
|
filled
|
|
size="lg"
|
|
autogrow
|
|
@keyup.enter.stop="handleClick"
|
|
:required="isRequired"
|
|
clearable
|
|
>
|
|
<template #append>
|
|
<QBtn
|
|
:title="t('Save (Enter)')"
|
|
icon="save"
|
|
color="primary"
|
|
flat
|
|
@click="handleClick"
|
|
class="q-mb-xs"
|
|
dense
|
|
data-cy="saveNote"
|
|
/>
|
|
</template>
|
|
</VnInput>
|
|
</VnRow>
|
|
</QCardSection>
|
|
</QCard>
|
|
<VnPaginate
|
|
v-if="!$props.justInput"
|
|
:data-key="$props.url"
|
|
:url="$props.url"
|
|
order="created DESC"
|
|
:limit="0"
|
|
:user-filter="userFilter"
|
|
:filter="filter"
|
|
auto-load
|
|
ref="vnPaginateRef"
|
|
class="show"
|
|
v-bind="$attrs"
|
|
:search-url="false"
|
|
@on-fetch="
|
|
newNote.text = '';
|
|
newNote.observationTypeFk = null;
|
|
"
|
|
>
|
|
<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, index) in rows"
|
|
:key="note.id ?? index"
|
|
>
|
|
<QCardSection horizontal>
|
|
<VnAvatar
|
|
:descriptor="false"
|
|
:worker-id="note.workerFk"
|
|
size="md"
|
|
:title="note.worker?.user.nickname"
|
|
/>
|
|
<div class="full-width row justify-between q-pa-xs">
|
|
<div>
|
|
<VnUserLink
|
|
:name="`${note.worker.user.name}`"
|
|
:worker-id="note.worker.id"
|
|
/>
|
|
<QBadge
|
|
class="q-ml-xs"
|
|
outline
|
|
color="grey"
|
|
v-if="selectType && note.observationTypeFk"
|
|
>
|
|
{{
|
|
observationTypes.find(
|
|
(ot) => ot.id === note.observationTypeFk,
|
|
)?.description
|
|
}}
|
|
</QBadge>
|
|
</div>
|
|
<span v-text="toDateHourMin(note.created)" />
|
|
</div>
|
|
</QCardSection>
|
|
<QCardSection class="q-pa-xs q-my-none q-py-none">
|
|
{{ note.text }}
|
|
</QCardSection>
|
|
</QCard>
|
|
</TransitionGroup>
|
|
</template>
|
|
</VnPaginate>
|
|
</template>
|
|
<style lang="scss" scoped>
|
|
.q-card {
|
|
width: 90%;
|
|
}
|
|
.q-dialog .q-card {
|
|
width: 400px;
|
|
}
|
|
.list-enter-active,
|
|
.list-leave-active {
|
|
transition: all 1s ease;
|
|
}
|
|
.list-enter-from,
|
|
.list-leave-to {
|
|
opacity: 0;
|
|
background-color: $primary;
|
|
}
|
|
|
|
.vn-row > :nth-child(2) {
|
|
margin-left: 0;
|
|
}
|
|
|
|
@media (max-width: $breakpoint-xs) {
|
|
.vn-row > :deep(*) {
|
|
margin-left: 0;
|
|
}
|
|
.q-card {
|
|
width: 100%;
|
|
|
|
&__section {
|
|
padding: 0;
|
|
}
|
|
}
|
|
}
|
|
.just-input {
|
|
padding-right: 18px;
|
|
margin-bottom: 2px;
|
|
box-shadow: none;
|
|
}
|
|
</style>
|
|
<i18n>
|
|
es:
|
|
Add note here...: Añadir nota aquí...
|
|
New note: Nueva nota
|
|
Save (Enter): Guardar (Intro)
|
|
Observation type: Tipo de observación
|
|
New note is empty: La nueva nota esta vacia
|
|
Are you sure remove this note?: Estas seguro de quitar esta nota?
|
|
</i18n>
|