feature/FaltantesModuloTravel #185

Merged
jsegarra merged 17 commits from :feature/FaltantesModuloTravel into dev 2024-02-09 13:11:23 +00:00
23 changed files with 1387 additions and 353 deletions

View File

@ -0,0 +1,114 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FormModelPopup from './FormModelPopup.vue';
const emit = defineEmits(['onDataSaved']);
const { t } = useI18n();
const thermographFormData = reactive({
thermographId: null,
model: 'DISPOSABLE',
warehouseId: null,
temperatureFk: 'cool',
});
const thermographsModels = ref(null);
const warehousesOptions = ref([]);
const temperaturesOptions = ref([]);
const onDataSaved = (dataSaved) => {
emit('onDataSaved', dataSaved);
};
</script>
<template>
<FetchData
@on-fetch="(data) => (thermographsModels = data)"
auto-load
url="Thermographs/getThermographModels"
/>
<FetchData
@on-fetch="(data) => (warehousesOptions = data)"
auto-load
url="Warehouses"
:filter="{ fields: ['id', 'name'], order: 'name ASC', limit: 30 }"
/>
<FetchData
@on-fetch="(data) => (temperaturesOptions = data)"
auto-load
url="Temperatures"
/>
<FormModelPopup
url-create="Thermographs/createThermograph"
model="thermograph"
:title="t('New thermograph')"
:form-initial-data="thermographFormData"
@on-data-saved="onDataSaved($event)"
>
<template #form-inputs="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
:label="t('Identifier')"
v-model="data.thermographId"
:required="true"
:rules="validate('thermograph.id')"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('Model')"
:options="thermographsModels"
hide-selected
option-label="value"
option-value="value"
v-model="data.model"
:required="true"
:rules="validate('thermograph.model')"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-xl">
<div class="col">
<VnSelectFilter
:label="t('Warehouse')"
:options="warehousesOptions"
hide-selected
option-label="name"
option-value="id"
v-model="data.warehouseId"
:required="true"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('Temperature')"
:options="temperaturesOptions"
hide-selected
option-label="name"
option-value="code"
v-model="data.temperatureFk"
:required="true"
/>
</div>
</VnRow>
</template>
</FormModelPopup>
</template>
<i18n>
es:
Identifier: Identificador
Model: Modelo
Warehouse: Almacén
Temperature: Temperatura
New thermograph: Nuevo termógrafo
</i18n>

View File

@ -276,13 +276,9 @@ const makeRequest = async () => {
</QIcon>
<QIcon name="info" class="cursor-pointer">
<QTooltip>{{
t(
'components.editPictureForm.allowedFilesText',
{
allowedContentTypes:
allowedContentTypes,
}
)
t('globals.allowedFilesText', {
allowedContentTypes: allowedContentTypes,
})
}}</QTooltip>
</QIcon>
</template>

View File

@ -1,5 +1,6 @@
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
const emit = defineEmits(['update:modelValue', 'update:options', 'keyup.enter']);
@ -14,6 +15,9 @@ const $props = defineProps({
},
});
const { t } = useI18n();
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired');
const value = computed({
get() {
return $props.modelValue;
@ -46,6 +50,7 @@ const onEnterPress = () => {
type="text"
:class="{ required: $attrs.required }"
@keyup.enter="onEnterPress()"
:rules="$attrs.required ? [requiredFieldRule] : null"
>
<template v-if="$slots.prepend" #prepend>
<slot name="prepend" />

View File

@ -24,6 +24,10 @@ const $props = defineProps({
type: String,
default: 'add',
},
tooltip: {
type: String,
default: '',
},
});
const role = useRole();
@ -55,7 +59,9 @@ const toggleForm = () => {
:name="actionIcon"
:size="actionIcon === 'add' ? 'xs' : 'sm'"
:class="['default-icon', { '--add-icon': actionIcon === 'add' }]"
/>
>
<QTooltip v-if="tooltip">{{ tooltip }}</QTooltip>
</QIcon>
<QDialog v-model="showForm" transition-show="scale" transition-hide="scale">
<slot name="form" />
</QDialog>

View File

@ -1,7 +1,8 @@
<script setup>
import FetchData from 'src/components/FetchData.vue';
import { onMounted } from 'vue';
import { ref, toRefs, computed, watch } from 'vue';
import { onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'src/components/FetchData.vue';
const emit = defineEmits(['update:modelValue', 'update:options']);
const $props = defineProps({
@ -55,6 +56,9 @@ const $props = defineProps({
},
});
const { t } = useI18n();
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired');
const { optionLabel, optionValue, options, modelValue } = toRefs($props);
const myOptions = ref([]);
const myOptionsOriginal = ref([]);
@ -164,6 +168,7 @@ watch(modelValue, (newValue) => {
fill-input
ref="vnSelectRef"
:class="{ required: $attrs.required }"
:rules="$attrs.required ? [requiredFieldRule] : null"
>
<template v-if="isClearable" #append>
<QIcon

View File

@ -9,6 +9,7 @@ const user = ref({
lang: '',
darkMode: null,
companyFk: null,
warehouseFk: null,
});
const roles = ref([]);

View File

@ -68,6 +68,8 @@ export default {
reason: 'reason',
noResults: 'No results',
system: 'System',
fieldRequired: 'Field required',
allowedFilesText: 'Allowed file types: { allowedContentTypes }',
},
errors: {
statusUnauthorized: 'Access denied',
@ -1084,8 +1086,8 @@ export default {
extraCommunity: 'Extra community',
travelCreate: 'New travel',
basicData: 'Basic data',
history: 'History',
thermographs: 'Termographs',
history: 'Log',
thermographs: 'Thermograph',
},
summary: {
confirmed: 'Confirmed',
@ -1097,7 +1099,10 @@ export default {
entries: 'Entries',
cloneShipping: 'Clone travel',
CloneTravelAndEntries: 'Clone travel and his entries',
deleteTravel: 'Delete travel',
AddEntry: 'Add entry',
thermographs: 'Thermographs',
hb: 'HB',
},
variables: {
search: 'Id/Reference',
@ -1109,6 +1114,31 @@ export default {
continent: 'Continent out',
totalEntries: 'Total entries',
},
basicData: {
reference: 'Reference',
agency: 'Agency',
shipped: 'Shipped',
landed: 'Landed',
warehouseOut: 'Warehouse Out',
warehouseIn: 'Warehouse In',
delivered: 'Delivered',
received: 'Received',
},
thermographs: {
code: 'Code',
temperature: 'Temperature',
state: 'State',
destination: 'Destination',
created: 'Created',
thermograph: 'Thermograph',
reference: 'Reference',
type: 'Type',
company: 'Company',
warehouse: 'Warehouse',
travelFileDescription: 'Travel id { travelId }',
file: 'File',
description: 'Description',
},
},
item: {
pageTitles: {
@ -1152,9 +1182,6 @@ export default {
addToPinned: 'Add to pinned',
removeFromPinned: 'Remove from pinned',
},
editPictureForm: {
allowedFilesText: 'Allowed file types: { allowedContentTypes }',
},
VnLv: {
copyText: '{copyValue} has been copied to the clipboard',
},

View File

@ -68,6 +68,8 @@ export default {
reason: 'motivo',
noResults: 'Sin resultados',
system: 'Sistema',
fieldRequired: 'Campo requerido',
allowedFilesText: 'Tipos de archivo permitidos: { allowedContentTypes }',
},
errors: {
statusUnauthorized: 'Acceso denegado',
@ -1097,7 +1099,10 @@ export default {
entries: 'Entradas',
cloneShipping: 'Clonar envío',
CloneTravelAndEntries: 'Clonar travel y sus entradas',
deleteTravel: 'Eliminar envío',
AddEntry: 'Añadir entrada',
thermographs: 'Termógrafos',
hb: 'HB',
},
variables: {
search: 'Id/Referencia',
@ -1109,6 +1114,31 @@ export default {
continent: 'Cont. Salida',
totalEntries: 'Ent. totales',
},
basicData: {
reference: 'Referencia',
agency: 'Agencia',
shipped: 'F. Envío',
landed: 'F. entrega',
warehouseOut: 'Alm. salida',
warehouseIn: 'Alm. entrada',
delivered: 'Enviada',
received: 'Recibida',
},
thermographs: {
code: 'Código',
temperature: 'Temperatura',
state: 'Estado',
destination: 'Destino',
created: 'Fecha creación',
thermograph: 'Termógrafo',
reference: 'Referencia',
type: 'Tipo',
company: 'Empresa',
warehouse: 'Almacén',
travelFileDescription: 'Id envío { travelId }',
file: 'Fichero',
description: 'Descripción',
},
},
item: {
pageTitles: {
@ -1152,9 +1182,6 @@ export default {
addToPinned: 'Añadir a fijados',
removeFromPinned: 'Eliminar de fijados',
},
editPictureForm: {
allowedFilesText: 'Tipos de archivo permitidos: { allowedContentTypes }',
},
VnLv: {
copyText: '{copyValue} se ha copiado al portapepeles',
},

View File

@ -23,11 +23,11 @@ const stateStore = useStateStore();
const user = state.getUser();
const newEntryForm = reactive({
supplierFk: null,
travelFk: route.query?.travelFk || null,
travelFk: Number(route.query?.travelFk) || null,
companyFk: user.value.companyFk || null,
});
const suppliersOptions = ref([]);
const travelsOptionsOptions = ref([]);
const travelsOptions = ref([]);
const companiesOptions = ref([]);
const redirectToEntryBasicData = (_, { id }) => {
@ -47,7 +47,7 @@ const redirectToEntryBasicData = (_, { id }) => {
url="Travels/filter"
:filter="{ fields: ['id', 'warehouseInName'] }"
order="id"
@on-fetch="(data) => (travelsOptionsOptions = data)"
@on-fetch="(data) => (travelsOptions = data)"
auto-load
/>
<FetchData
@ -110,7 +110,7 @@ const redirectToEntryBasicData = (_, { id }) => {
:label="t('Travel')"
class="full-width"
v-model="data.travelFk"
:options="travelsOptionsOptions"
:options="travelsOptions"
option-value="id"
option-label="warehouseInName"
map-options

View File

@ -0,0 +1,104 @@
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const route = useRoute();
const { t } = useI18n();
const agenciesOptions = ref([]);
</script>
<template>
<FetchData
url="AgencyModes"
@on-fetch="(data) => (agenciesOptions = data)"
auto-load
/>
<FormModel
:url="`Travels/${route.params.id}`"
:url-update="`Travels/${route.params.id}`"
model="travel"
auto-load
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
v-model="data.ref"
:label="t('travel.basicData.reference')"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('travel.basicData.agency')"
v-model="data.agencyModeFk"
:options="agenciesOptions"
option-value="id"
option-label="name"
map-options
hide-selected
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInputDate
v-model="data.shipped"
:label="t('travel.basicData.shipped')"
/>
</div>
<div class="col">
<VnInputDate
v-model="data.landed"
:label="t('travel.basicData.landed')"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('travel.basicData.warehouseOut')"
v-model="data.warehouseOutFk"
:options="agenciesOptions"
option-value="id"
option-label="name"
map-options
hide-selected
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('travel.basicData.warehouseIn')"
v-model="data.warehouseInFk"
:options="agenciesOptions"
option-value="id"
option-label="name"
map-options
hide-selected
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QCheckbox
:label="t('travel.basicData.delivered')"
v-model="data.isDelivered"
/>
</div>
<div class="col">
<QCheckbox
:label="t('travel.basicData.received')"
v-model="data.isReceived"
/>
</div>
</VnRow>
</template>
</FormModel>
</template>

View File

@ -17,7 +17,6 @@ const stateStore = useStateStore();
<QPageContainer>
<QPage>
<VnSubToolbar />
<div class="q-pa-md"><RouterView></RouterView></div>
</QPage>
</QPageContainer>

View File

@ -2,10 +2,13 @@
import { ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { toDate } from 'src/filters';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import TravelDescriptorMenuItems from './TravelDescriptorMenuItems.vue';
import useCardDescription from 'src/composables/useCardDescription';
import { toDate } from 'src/filters';
const $props = defineProps({
id: {
@ -28,6 +31,7 @@ const filter = {
'warehouseInFk',
'warehouseOutFk',
'cargoSupplierFk',
'agencyModeFk',
],
include: [
{
@ -66,6 +70,25 @@ const setData = (entity) => {
@on-fetch="setData"
data-key="travelData"
>
<template #header-extra-action>
<QBtn
round
flat
dense
size="md"
icon="local_airport"
color="white"
class="link"
:to="{ name: 'TravelList' }"
>
<QTooltip>
{{ t('Go to module index') }}
</QTooltip>
</QBtn>
</template>
<template #menu="{ entity }">
<TravelDescriptorMenuItems :travel="entity" />
</template>
<template #body="{ entity }">
<VnLv :label="t('globals.wareHouseIn')" :value="entity.warehouseIn.name" />
<VnLv :label="t('globals.wareHouseOut')" :value="entity.warehouseOut.name" />
@ -73,8 +96,32 @@ const setData = (entity) => {
<VnLv :label="t('globals.landed')" :value="toDate(entity.landed)" />
<VnLv :label="t('globals.totalEntries')" :value="entity.totalEntries" />
</template>
<template #actions="{ entity }">
<QCardActions>
<QBtn
:to="{
name: 'TravelList',
query: {
params: JSON.stringify({
agencyModeFk: entity.agencyModeFk,
}),
},
}"
size="md"
icon="local_airport"
color="primary"
>
<QTooltip>{{ t('All travels with current agency') }}</QTooltip>
</QBtn>
</QCardActions>
</template>
</CardDescriptor>
</template>
<i18n>
<i18n>
es:
Go to module index: Ir al índice del módulo
The travel will be deleted: El envío será eliminado
Do you want to delete this travel?: ¿Quieres eliminar este envío?
All travels with current agency: Todos los envíos con la agencia actual
</i18n>

View File

@ -0,0 +1,108 @@
<script setup>
import { computed } from 'vue';
import { useQuasar } from 'quasar';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import VnConfirm from 'components/ui/VnConfirm.vue';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
import { useRole } from 'src/composables/useRole';
const $props = defineProps({
travel: {
type: Object,
default: () => {},
},
});
const { t } = useI18n();
const router = useRouter();
const quasar = useQuasar();
const { notify } = useNotify();
const role = useRole();
const redirectToCreateView = (queryParams) => {
router.push({ name: 'TravelCreate', query: { travelData: queryParams } });
};
const cloneTravel = () => {
const stringifiedTravelData = JSON.stringify($props.travel);
redirectToCreateView(stringifiedTravelData);
};
const cloneTravelWithEntries = () => {
try {
axios.post(`Travels/${$props.travel.id}/cloneWithEntries`);
notify('globals.dataSaved', 'positive');
} catch (err) {
console.err('Error cloning travel with entries');
}
};
const isBuyer = computed(() => {
return role.hasAny(['buyer']);
});
const openDeleteEntryDialog = (id) => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('The travel will be deleted'),
message: t('Do you want to delete this travel?'),
},
})
.onOk(async () => {
await deleteTravel(id);
});
};
const deleteTravel = async (id) => {
try {
await axios.delete(`Travels/${id}`);
router.push({ name: 'TravelList' });
notify('globals.dataDeleted', 'positive');
} catch (err) {
console.error('Error deleting travel');
}
};
</script>
<template>
<QItem v-ripple clickable @click="cloneTravel(travel)">
<QItemSection>{{ t('travel.summary.cloneShipping') }}</QItemSection>
</QItem>
<QItem v-ripple clickable @click="cloneTravelWithEntries()">
<QItemSection>
{{ t('travel.summary.CloneTravelAndEntries') }}
</QItemSection>
</QItem>
<QItem
v-if="isBuyer && travel.totalEntries === 0"
v-ripple
clickable
@click="openDeleteEntryDialog(travel.id)"
>
<QItemSection>
{{ t('travel.summary.deleteTravel') }}
</QItemSection>
</QItem>
<QItem v-ripple clickable>
<QItemSection>
<RouterLink
:to="{ name: 'EntryCreate', query: { travelFk: travel.id } }"
class="color-vn-text"
>
{{ t('travel.summary.AddEntry') }}
</RouterLink>
</QItemSection>
</QItem>
</template>
<i18n>
es:
The travel will be deleted: El envío será eliminado
Do you want to delete this travel?: ¿Quieres eliminar este envío?
</i18n>

View File

@ -0,0 +1,6 @@
<script setup>
import VnLog from 'src/components/common/VnLog.vue';
</script>
<template>
<VnLog model="Entry" url="/TravelLogs"></VnLog>
</template>

View File

@ -1,22 +1,19 @@
<script setup>
import { onMounted, ref, computed, onUpdated } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ref, computed, onUpdated } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { QCheckbox, QIcon } from 'quasar';
import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue';
import FetchData from 'src/components/FetchData.vue';
import TravelDescriptorMenuItems from './TravelDescriptorMenuItems.vue';
import travelService from 'src/services/travel.service';
import { toDate, toCurrency } from 'src/filters';
import { getUrl } from 'src/composables/getUrl';
import axios from 'axios';
onUpdated(() => summaryRef.value.fetch());
const route = useRoute();
const { t } = useI18n();
const $props = defineProps({
id: {
type: Number,
@ -25,89 +22,15 @@ const $props = defineProps({
},
});
const router = useRouter();
const route = useRoute();
const { t } = useI18n();
const entityId = computed(() => $props.id || route.params.id);
const entries = ref([]);
const summaryRef = ref();
const travel = ref();
const travelUrl = ref();
onMounted(async () => {
travelUrl.value = (await getUrl('travel/')) + entityId.value;
});
const cloneTravel = () => {
const stringifiedTravelData = JSON.stringify(travel.value);
redirectToCreateView(stringifiedTravelData);
};
const cloneTravelWithEntries = () => {
try {
axios.post(`Travels/${$props.id}/cloneWithEntries`);
} catch (err) {
console.err('Error cloning travel with entries');
}
};
const headerMenuOptions = [
{ name: t('travel.summary.cloneShipping'), action: cloneTravel },
{ name: t('travel.summary.CloneTravelAndEntries'), action: cloneTravelWithEntries },
{ name: t('travel.summary.AddEntry'), action: null },
];
const tableColumnComponents = {
isConfirmed: {
component: () => QCheckbox,
props: (prop) => ({
disable: true,
'model-value': Boolean(prop.value),
}),
},
id: {
component: () => 'span',
props: () => {},
event: () => openEntryDescriptor(),
},
supplierName: {
component: () => 'span',
props: () => {},
event: () => {},
},
reference: {
component: () => 'span',
props: () => {},
event: () => {},
},
freightValue: {
component: () => 'span',
props: () => {},
event: () => {},
},
packageValue: {
component: () => 'span',
props: () => {},
event: () => {},
},
cc: {
component: () => 'span',
props: () => {},
event: () => {},
},
pallet: {
component: () => 'span',
props: () => {},
event: () => {},
},
m3: {
component: () => 'span',
props: () => {},
event: () => {},
},
observation: {
component: (props) => (props.value ? QIcon : null),
props: () => ({ name: 'insert_drive_file', color: 'primary', size: '25px' }),
},
};
const entries = ref([]);
const thermographs = ref([]);
const warehouses = ref([]);
const entriesTableColumns = computed(() => {
return [
@ -116,24 +39,35 @@ const entriesTableColumns = computed(() => {
field: 'isConfirmed',
name: 'isConfirmed',
align: 'left',
showValue: false,
},
{
label: t('travel.summary.entryId'),
field: 'id',
name: 'id',
align: 'left',
showValue: false,
},
{
label: t('supplier.pageTitles.supplier'),
field: 'supplierName',
name: 'supplierName',
align: 'left',
showValue: true,
},
{
label: t('globals.reference'),
field: 'reference',
name: 'reference',
align: 'left',
showValue: true,
},
{
label: t('travel.summary.hb'),
field: 'hb',
name: 'hb',
align: 'left',
showValue: true,
},
{
label: t('travel.summary.freight'),
@ -143,6 +77,7 @@ const entriesTableColumns = computed(() => {
format: (val) => {
return toCurrency(val);
},
showValue: true,
},
{
label: t('travel.summary.package'),
@ -152,16 +87,62 @@ const entriesTableColumns = computed(() => {
format: (val) => {
return toCurrency(val);
},
showValue: true,
},
{ label: 'CC', field: 'cc', name: 'cc', align: 'left' },
{ label: 'Pallet', field: 'pallet', name: 'pallet', align: 'left' },
{ label: 'm³', field: 'm3', name: 'm3', align: 'left' },
{ label: 'CC', field: 'cc', name: 'cc', align: 'left', showValue: true },
{
label: 'Pallet',
field: 'pallet',
name: 'pallet',
align: 'left',
showValue: true,
},
{ label: 'm³', field: 'm3', name: 'm3', align: 'left', showValue: true },
{
label: '',
field: 'observation',
name: 'observation',
align: 'left',
toolTip: 'Observation three',
showValue: false,
},
];
});
const thermographsTableColumns = computed(() => {
return [
{
label: t('travel.thermographs.code'),
field: 'thermographFk',
name: 'thermographFk',
align: 'left',
},
{
label: t('travel.thermographs.temperature'),
field: 'temperatureFk',
name: 'temperatureFk',
align: 'left',
},
{
label: t('travel.thermographs.state'),
field: 'result',
name: 'result',
align: 'left',
},
{
label: t('travel.thermographs.destination'),
field: 'warehouseFk',
name: 'destination',
align: 'left',
format: (val) =>
warehouses.value.find((warehouse) => warehouse.id === val).name,
},
{
label: t('travel.thermographs.created'),
field: 'created',
name: 'created',
align: 'left',
format: (val) => toDate(val),
},
];
});
@ -171,31 +152,98 @@ const entriesTableRows = computed(() => {
return entries.value;
});
async function setTravelData(data) {
if (data) {
travel.value = data;
const entriesResponse = await travelService.getTravelEntries(travel.value.id);
if (entriesResponse.data) entries.value = entriesResponse.data;
}
}
const entriesTotalHb = computed(() =>
entriesTableRows.value.reduce((acc, { hb }) => acc + hb, 0)
);
const redirectToCreateView = (queryParams) => {
router.push({ name: 'TravelCreate', query: { travelData: queryParams } });
const entriesTotalFreight = computed(() =>
toCurrency(
entriesTableRows.value.reduce((acc, { freightValue }) => acc + freightValue, 0)
)
);
const entriesTotalPackageValue = computed(() =>
toCurrency(
entriesTableRows.value.reduce((acc, { packageValue }) => acc + packageValue, 0)
)
);
const entriesTotalCc = computed(() =>
entriesTableRows.value.reduce((acc, { cc }) => acc + cc, 0)
);
const entriesTotalPallet = computed(() =>
entriesTableRows.value.reduce((acc, { pallet }) => acc + pallet, 0)
);
const entriesTotalM3 = computed(() =>
entriesTableRows.value.reduce((acc, { m3 }) => acc + m3, 0)
);
const getTravelEntries = async (id) => {
try {
const { data } = await axios.get(`Travels/${id}/getEntries`);
entries.value = data;
} catch (err) {
console.error('Error fetching travel entries');
}
};
const openEntryDescriptor = () => {};
const getTravelThermographs = async (id) => {
try {
const filter = {
include: {
relation: 'warehouse',
scope: {
fields: ['id', 'name'],
},
},
where: { travelFk: id },
};
const { data } = await axios.get('TravelThermographs', {
params: { filter: JSON.parse(JSON.stringify(filter)) },
});
thermographs.value = data;
} catch (err) {
console.error('Error fetching travel thermographs');
}
};
async function setTravelData(travelData) {
try {
if (travelData) {
travel.value = travelData;
await getTravelEntries(travel.value.id);
await getTravelThermographs(travel.value.id);
}
} catch (err) {
console.error(`Error setting travel data`, err);
}
}
</script>
<template>
<FetchData
url="Warehouses"
:filter="{ fields: ['id', 'name'] }"
order="name"
@on-fetch="(data) => (warehouses = data)"
auto-load
/>
<CardSummary
ref="summaryRef"
:url="`Travels/${entityId}/getTravel`"
@on-fetch="(data) => setTravelData(data)"
>
<template #header-left>
<a class="header link" :href="travelUrl">
<router-link
class="header link"
:to="{ name: 'TravelSummary', params: { id: entityId } }"
>
<QIcon name="open_in_new" color="white" size="sm" />
</a>
<QTooltip>{{ t('travel.pageTitles.summary') }}</QTooltip>
</router-link>
</template>
<template #header>
<span>{{ travel.ref }} - {{ travel.id }}</span>
@ -207,10 +255,8 @@ const openEntryDescriptor = () => {};
{{ t('components.cardDescriptor.moreOptions') }}
</QTooltip>
<QMenu>
<QList dense v-for="option in headerMenuOptions" :key="option">
<QItem v-ripple clickable @click="option.action">
{{ option.name }}
</QItem>
<QList>
<TravelDescriptorMenuItems :travel="travel" />
</QList>
</QMenu>
</QBtn>
@ -225,11 +271,10 @@ const openEntryDescriptor = () => {};
/>
<VnLv :label="t('travel.summary.delivered')" class="q-mb-xs">
<template #value>
<QCheckbox
v-model="travel.isDelivered"
disable
dense
class="full-width q-my-xs"
<QIcon
:name="travel.isDelivered ? 'check' : 'close'"
:color="travel.isDelivered ? 'positive' : 'negative'"
size="sm"
/>
</template>
</VnLv>
@ -242,11 +287,10 @@ const openEntryDescriptor = () => {};
/>
<VnLv :label="t('travel.summary.received')" class="q-mb-xs">
<template #value>
<QCheckbox
v-model="travel.isReceived"
disable
dense
class="full-width q-mb-xs"
<QIcon
:name="travel.isReceived ? 'check' : 'close'"
:color="travel.isReceived ? 'positive' : 'negative'"
size="sm"
/>
</template>
</VnLv>
@ -255,13 +299,12 @@ const openEntryDescriptor = () => {};
<VnLv :label="t('globals.agency')" :value="travel.agency?.name" />
<VnLv :label="t('globals.reference')" :value="travel.ref" />
<VnLv label="m³" :value="travel.m3" />
<VnLv :label="t('globals.totalEntries')" :value="travel.m3" />
<VnLv :label="t('globals.totalEntries')" :value="travel.totalEntries" />
</QCard>
<QCard class="full-width" v-if="entriesTableRows.length > 0">
<a class="header" :href="travelUrl + 'entry'">
<span class="header">
{{ t('travel.summary.entries') }}
<QIcon name="open_in_new" color="primary" />
</a>
</span>
<QTable
:rows="entriesTableRows"
:columns="entriesTableColumns"
@ -269,36 +312,72 @@ const openEntryDescriptor = () => {};
row-key="id"
class="full-width q-mt-md"
>
<template #body-cell="props">
<QTd :props="props">
<component
:is="
tableColumnComponents[props.col.name].component(props)
"
v-bind="
tableColumnComponents[props.col.name].props(props)
"
@click="
tableColumnComponents[props.col.name].event(props)
"
class="col-content"
>
<template
v-if="
props.col.name !== 'observation' &&
props.col.name !== 'isConfirmed'
"
>{{ props.value }}</template
>
<QTooltip v-if="props.col.toolTip">{{
props.col.toolTip
}}</QTooltip>
</component>
<template #header="props">
<QTr :props="props" class="bg">
<QTh v-for="col in props.cols" :key="col.name" :props="props">
{{ t(col.label) }}
</QTh>
</QTr>
</template>
<template #body-cell-isConfirmed="{ col, value }">
<QTd>
<QIcon
v-if="col.name === 'isConfirmed'"
:name="value ? 'check' : 'close'"
:color="value ? 'positive' : 'negative'"
size="sm"
/>
</QTd>
</template>
<template #body-cell-id="{ col, value }">
<QTd>
<QBtn v-if="col.name === 'id'" flat color="blue">
{{ value }}
<EntryDescriptorProxy :id="value" />
</QBtn>
</QTd>
</template>
<template #body-cell-observation="{ value }">
<QTd>
<QIcon name="insert_drive_file" color="primary" size="24px">
<QTooltip>{{ value }}</QTooltip>
</QIcon>
</QTd>
</template>
<template #bottom-row>
<QTd></QTd>
<QTd></QTd>
<QTd></QTd>
<QTd></QTd>
<QTd class="text-bold">{{ entriesTotalHb }}</QTd>
<QTd class="text-bold">{{ entriesTotalFreight }}</QTd>
<QTd class="text-bold">{{ entriesTotalPackageValue }}</QTd>
<QTd class="text-bold">{{ entriesTotalCc }}</QTd>
<QTd class="text-bold">{{ entriesTotalPallet }}</QTd>
<QTd class="text-bold">{{ entriesTotalM3 }}</QTd>
</template>
</QTable>
</QCard>
<QCard class="full-width" v-if="thermographs.length > 0">
<RouterLink
class="header"
:to="{
name: 'TravelThermographsIndex',
params: { id: travel.id },
}"
>
{{ t('travel.summary.thermographs') }}
<QIcon name="open_in_new" color="primary" />
</RouterLink>
<QTable
:rows="thermographs"
:columns="thermographsTableColumns"
hide-bottom
row-key="id"
class="full-width q-mt-md"
/>
</QCard>
</template>
</CardSummary>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,216 @@
<script setup>
import { computed, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import FetchData from 'src/components/FetchData.vue';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
import { toDate } from 'src/filters';
import { downloadFile } from 'src/composables/downloadFile';
const route = useRoute();
const quasar = useQuasar();
const router = useRouter();
const { t } = useI18n();
const { notify } = useNotify();
const thermographPaginateRef = ref(null);
const warehouses = ref([]);
const thermographFilter = {
include: {
relation: 'warehouse',
scope: {
fields: ['id', 'name'],
},
},
where: { travelFk: route.params.id },
order: ['created'],
};
const TableColumns = computed(() => {
return [
{
label: t('travel.thermographs.code'),
field: 'thermographFk',
name: 'thermographFk',
align: 'left',
},
{
label: t('travel.thermographs.temperature'),
field: 'temperatureFk',
name: 'temperatureFk',
align: 'left',
},
{
label: t('travel.thermographs.state'),
field: 'result',
name: 'result',
align: 'left',
},
{
label: t('travel.thermographs.destination'),
field: 'warehouseFk',
name: 'destination',
align: 'left',
format: (val) =>
warehouses.value.find((warehouse) => warehouse.id === val)?.name,
},
{
label: t('travel.thermographs.created'),
field: 'created',
name: 'created',
align: 'left',
format: (val) => toDate(val),
},
{
name: 'downloadFile',
align: 'left',
},
{
name: 'editFile',
align: 'left',
},
{
name: 'removeThermograph',
align: 'left',
},
];
});
const openRemoveDialog = async (id) => {
quasar
.dialog({
component: VnConfirm,
componentProps: {
message: t('Are you sure you want to remove the thermograph?'),
},
})
.onOk(async () => {
try {
await removeThermograph(id);
} catch (err) {
console.error('Error removing thermograph');
}
});
};
const redirectToThermographForm = (action, id) => {
const routeDetails = {
name: action === 'create' ? 'TravelThermographsCreate' : 'TravelThermographsEdit',
};
if (action === 'edit' && id) {
const params = {};
params.thermographId = id;
routeDetails.params = params;
}
router.push(routeDetails);
};
const removeThermograph = async (id) => {
try {
await axios.delete(`Travels/deleteThermograph?id=${id}`);
await thermographPaginateRef.value.fetch();
notify(t('Thermograph removed'), 'positive');
} catch (err) {
console.error('Error removing termograph');
}
};
</script>
<template>
<FetchData
url="Warehouses"
:filter="{ fields: ['id', 'name'] }"
order="name"
@on-fetch="(data) => (warehouses = data)"
auto-load
/>
<VnPaginate
ref="thermographPaginateRef"
data-key="TravelThermographs"
url="TravelThermographs"
:filter="thermographFilter"
:params="{ travelFk: route.params.id }"
auto-load
>
<template #body="{ rows }">
<QTable
:rows="rows"
:columns="TableColumns"
:no-data-label="t('No results')"
:rows-per-page-options="[0]"
row-key="id"
class="full-width q-mt-md"
>
<template #body-cell-downloadFile="{ row }">
<QTd auto-width>
<QIcon
name="cloud_download"
color="primary"
size="sm"
class="cursor-pointer"
@click="downloadFile(row.dmsFk)"
>
<QTooltip>{{ t('Download file') }}</QTooltip>
</QIcon>
</QTd>
</template>
<template #body-cell-editFile="{ row }">
<QTd auto-width>
<QIcon
name="edit"
color="primary"
size="sm"
class="cursor-pointer"
@click="redirectToThermographForm('edit', row.id)"
>
<QTooltip>{{ t('Edit file') }}</QTooltip>
</QIcon>
</QTd>
</template>
<template #body-cell-removeThermograph="{ row }">
<QTd auto-width>
<QIcon
name="delete"
color="primary"
size="sm"
class="cursor-pointer"
@click="openRemoveDialog(row.id)"
>
<QTooltip>{{ t('Remove thermograph') }}</QTooltip>
</QIcon>
</QTd>
</template>
</QTable>
</template>
</VnPaginate>
<QPageSticky :offset="[20, 20]">
<QBtn
fab
icon="add"
color="primary"
@click="redirectToThermographForm('create')"
/>
<QTooltip class="text-no-wrap">
{{ t('Add thermograph') }}
</QTooltip>
</QPageSticky>
</template>
<i18n>
es:
Add thermograph: Añadir termógrafo
Download file: Descargar fichero
Edit file: Editar fichero
Remove thermograph: Eliminar termógrafo
Thermograph removed: Termógrafo eliminado
Are you sure you want to remove the thermograph?: ¿Seguro que quieres quitar el termógrafo?
No results: Sin resultados
</i18n>

View File

@ -0,0 +1,348 @@
<script setup>
jsegarra marked this conversation as resolved
Review

Desde el listado de termógrafos, si le das a editar te muestra el formulario pero:

  1. Te lanza esta llamada al api http://localhost:9000/api/TravelThermographs/[object%20Object]?filter=%7B%22include%22%3A%7B%22relation%22%3A%22dms%22%7D%7D
    . Como puedes ver tienes [object%20Object]
  2. La URL del edit queda algo así http://localhost:9000/#/travel/2/thermographs/[object%20Object]/edit
Desde el listado de termógrafos, si le das a editar te muestra el formulario pero: 1. Te lanza esta llamada al api http://localhost:9000/api/TravelThermographs/[object%20Object]?filter=%7B%22include%22%3A%7B%22relation%22%3A%22dms%22%7D%7D . Como puedes ver tienes [object%20Object] 2. La URL del edit queda algo así http://localhost:9000/#/travel/2/thermographs/[object%20Object]/edit
Review

Corregido.

Commit: 770f17a362

Corregido. Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/770f17a3624ff4f8a08f677f9e90e02d2da3ba6c
import { useI18n } from 'vue-i18n';
jsegarra marked this conversation as resolved
Review

Si desde el listado de termógrafos, le das a eliminar te dice:

  1. Que ha habido un error
  2. Que se ha eliminado
  3. No elimina el registro de la tabla.
Si desde el listado de termógrafos, le das a eliminar te dice: 1. Que ha habido un error 2. Que se ha eliminado 3. No elimina el registro de la tabla.
Review

@jsegarra en salix me da el mismo comportamiento que en lilium, por ejemplo si vas a los thermographs del first travel

Aparecen 4 registros donde dos te deja borrar exitosamente y dos arroja un error el backend.

Adjunto captura tomada de salix:

@jsegarra en salix me da el mismo comportamiento que en lilium, por ejemplo si vas a los thermographs del [first travel](http://localhost:9000/travel/3/summary#/travel/1/thermographs/index?limit=10) Aparecen 4 registros donde dos te deja borrar exitosamente y dos arroja un error el backend. Adjunto captura tomada de `salix`:
Review

Se fixea el problema de las notificaciones.

Commit: f24b9f5a75

Se fixea el problema de las notificaciones. Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/f24b9f5a75e55eb17903482da337d440189bcdb0
Review
  1. Si subes un fichero que es de tipo zip por ejemplo sólo te muestra el triangulo de advertencia en un toast rojo.
  2. Al crear te redirige a index pero la consola muestra errores. No sé muy bien si es por los errores o por el error que ocurre en mas partes de la aplicación.
  3. Darle a eliminar termógrafo, da error porque no se pasa el Id
1. Si subes un fichero que es de tipo zip por ejemplo sólo te muestra el triangulo de advertencia en un toast rojo. 2. Al crear te redirige a index pero la consola muestra errores. No sé muy bien si es por los errores o por el error que ocurre en mas partes de la aplicación. 3. Darle a eliminar termógrafo, da error porque no se pasa el Id
import { reactive, ref, onBeforeMount } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnSelectDialog from 'components/common/VnSelectDialog.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import CreateThermographForm from 'src/components/CreateThermographForm.vue';
import { useState } from 'src/composables/useState';
import { useStateStore } from 'stores/useStateStore';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
const props = defineProps({
viewAction: {
type: String,
default: 'create',
},
});
const stateStore = useStateStore();
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const state = useState();
const { notify } = useNotify();
const thermographFilter = {
fields: ['thermographFk'],
where: {
travelFk: null,
},
order: 'thermographFk ASC',
};
const fetchTravelThermographsRef = ref(null);
const allowedContentTypes = ref('');
const user = state.getUser();
const thermographsOptions = ref([]);
const dmsTypesOptions = ref([]);
const companiesOptions = ref([]);
const warehousesOptions = ref([]);
const inputFileRef = ref(null);
const thermographForm = reactive({
thermographId: null,
state: null,
reference: null,
dmsTypeId: null,
companyId: null,
warehouseId: null,
files: [],
description: null,
});
onBeforeMount(async () => {
if (props.viewAction === 'create') {
setCreateDefaultParams();
} else {
await setEditDefaultParams();
}
if (route.query.thermographData) {
const thermographData = JSON.parse(route.query.thermographData);
for (let key in thermographForm) {
thermographForm[key] = thermographData[key];
}
}
});
const fetchDmsTypes = async () => {
try {
const params = {
filter: {
where: { code: 'miscellaneous' },
},
};
const { data } = await axios.get('DmsTypes/findOne', { params });
return data;
} catch (err) {
console.error('Error fetching Dms Types');
}
};
const setCreateDefaultParams = async () => {
const dataResponse = await fetchDmsTypes();
thermographForm.companyId = user.value.companyFk;
thermographForm.warehouseId = user.value.warehouseFk;
thermographForm.reference = route.params.id;
thermographForm.dmsTypeId = dataResponse.id;
thermographForm.state = 'Ok';
thermographForm.description = t('travel.thermographs.travelFileDescription', {
travelId: route.params.id,
}).toUpperCase();
};
const setEditDefaultParams = async () => {
try {
const filterObj = { include: { relation: 'dms' } };
const filter = encodeURIComponent(JSON.stringify(filterObj));
const { data } = await axios.get(
`TravelThermographs/${route.params.thermographId}?filter=${filter}`
);
if (data) {
thermographForm.thermographId = data.thermographFk;
thermographForm.state = data.result;
thermographForm.reference = data.dms?.reference;
thermographForm.warehouseId = data.dms?.warehouseFk;
thermographForm.companyId = data.dms?.companyFk;
thermographForm.dmsTypeId = data.dms?.dmsTypeFk;
thermographForm.description = data.dms?.description || '';
thermographForm.hasFile = data.dms?.hasFile;
thermographForm.hasFileAttached = false;
}
} catch (err) {
console.error('Error fetching termograph');
}
};
const onSubmit = () => {
props.viewAction === 'create' ? createThermograph() : updateThermograph();
};
const createThermograph = async () => {
const formData = new FormData();
thermographForm.files.forEach((file) => {
formData.append(file.name, file);
});
try {
await axios.post(`/travels/${route.params.id}/createThermograph`, formData, {
params: thermographForm,
headers: {
'Content-Type': 'multipart/form-data',
},
});
router.push({ name: 'TravelThermographsIndex' });
notify(t('Thermograph created'), 'positive');
} catch (error) {
console.error('Error creating thermograph');
}
};
const updateThermograph = async () => {
const formData = new FormData();
thermographForm.files.forEach((file) => {
formData.append(file.name, file);
});
try {
await axios.post(`travels/${route.params.id}/updateThermograph`, formData, {
params: thermographForm,
headers: {
'Content-Type': 'multipart/form-data',
},
});
router.push({ name: 'TravelThermographsIndex' });
notify(t('Thermograph created'), 'positive');
} catch (error) {
console.error('Error creating thermograph');
}
};
const onThermographCreated = async (data) => {
await fetchTravelThermographsRef.value.fetch();
thermographForm.thermographId = data.thermographId;
};
</script>
<template>
<FetchData
url="DmsContainers/allowedContentTypes"
@on-fetch="(data) => (allowedContentTypes = data.join(', '))"
auto-load
/>
<FetchData
ref="fetchTravelThermographsRef"
url="TravelThermographs"
@on-fetch="(data) => (thermographsOptions = data)"
:filter="thermographFilter"
auto-load
/>
<FetchData
url="DmsTypes"
:filter="{ order: 'name' }"
@on-fetch="(data) => (dmsTypesOptions = data)"
auto-load
/>
<FetchData
url="Companies"
@on-fetch="(data) => (companiesOptions = data)"
:filter="{ order: 'code' }"
auto-load
/>
<FetchData
url="Warehouses"
@on-fetch="(data) => (warehousesOptions = data)"
:filter="{ order: 'name' }"
auto-load
/>
<QPage class="column items-center full-width">
<QForm
model="travel"
:form-initial-data="thermographForm"
:observe-form-changes="viewAction === 'create'"
:default-actions="true"
@submit="onSubmit()"
class="full-width"
style="max-width: 800px"
>
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
<div>
<QBtnGroup push class="q-gutter-x-sm">
<slot name="moreActions" />
<QBtn
color="primary"
icon="restart_alt"
flat
@click="reset()"
:label="t('globals.reset')"
/>
<QBtn
color="primary"
icon="save"
@click="onSubmit()"
:label="t('globals.save')"
/>
</QBtnGroup>
</div>
</Teleport>
<QCard class="q-pa-lg">
jsegarra marked this conversation as resolved
Review

No hay opción de crear termógrafo
Y por tanto no se puede evaluar el diálogo

No hay opción de crear termógrafo Y por tanto no se puede evaluar el diálogo
Review

Probe y pude acceder con el boton (+) que esta abajo a la derecha, que se encuentra en la vista de TravelThermographs, no te aparecio a vos?

Probe y pude acceder con el boton `(+)` que esta abajo a la derecha, que se encuentra en la vista de `TravelThermographs`, no te aparecio a vos?
Review

@jsegarra ahora si, cree el form necesario para crear un thermograph y lo aplique al input correspondiente.

Ademas de esto que era el requerimiento principal, implemente algunas cosas como:

  • Un estado de error en los inputs VnInput y VnSelectFilter cuando se le agrega la prop :required="true"
  • Algunas traducciones al objeto global para reutilizar
  • Implementacion del icon (i) con informacion sobre los tipos de files aceptados por el input QFile en el componente TravelThermographsForm

Commit: c67f4cf858

@jsegarra ahora si, cree el form necesario para crear un `thermograph` y lo aplique al input correspondiente. Ademas de esto que era el requerimiento principal, implemente algunas cosas como: - Un estado de error en los inputs `VnInput` y `VnSelectFilter` cuando se le agrega la prop `:required="true"` - Algunas traducciones al objeto global para reutilizar - Implementacion del icon `(i)` con informacion sobre los tipos de files aceptados por el input `QFile` en el componente `TravelThermographsForm` Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/c67f4cf858897cd2684946f44bd02c6eae808c68
Review

Se ve mal. Esto creo que ya nos pasó en otra PR antigua, creo que PR-3

Se ve mal. Esto creo que ya nos pasó en otra PR antigua, creo que PR-3
Review

Falta traducir el título del modal

Falta traducir el título del modal
Review

Listo! Ambos comentarios corregidos.

Además de esto me atreví a implementar la posibilidad de agregar un Tooltip al ícono de acción del input VnSelectDialog si se desea

Commit: 1b31657338

Listo! Ambos comentarios corregidos. Además de esto me atreví a implementar la posibilidad de agregar un `Tooltip` al ícono de acción del input `VnSelectDialog` si se desea Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/1b316573385f08d51a2b8279ace91d71732fb6d6
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectDialog
:label="t('travel.thermographs.thermograph')"
v-model="thermographForm.thermographId"
:options="thermographsOptions"
option-value="thermographFk"
option-label="thermographFk"
:disable="viewAction === 'edit'"
:tooltip="t('New thermograph')"
>
<template #form>
<CreateThermographForm
@on-data-saved="onThermographCreated($event, data)"
/>
</template>
</VnSelectDialog>
</div>
<div class="col">
<VnInput
v-model="thermographForm.state"
:label="t('travel.thermographs.state')"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
v-model="thermographForm.reference"
:label="t('travel.thermographs.reference')"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('travel.thermographs.type')"
v-model="thermographForm.dmsTypeId"
:options="dmsTypesOptions"
option-value="id"
option-label="name"
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelectFilter
:label="t('travel.thermographs.company')"
v-model="thermographForm.companyId"
:options="companiesOptions"
option-value="id"
option-label="code"
/>
</div>
<div class="col">
<VnSelectFilter
:label="t('travel.thermographs.warehouse')"
v-model="thermographForm.warehouseId"
:options="warehousesOptions"
option-value="id"
option-label="name"
/>
</div>
</VnRow>
<VnRow v-if="viewAction === 'edit'" class="row q-gutter-md q-mb-md">
jsegarra marked this conversation as resolved Outdated

El icono del clip debería funcionar como cuando haces clic en el input.
Esto está subsanado en varios sitios.

El icono del clip debería funcionar como cuando haces clic en el input. Esto está subsanado en varios sitios.

Corregido.

Commit: c8153b6414

Corregido. Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/c8153b641463dced58dc2e31a99744823d6c259c
<div class="col">
<QInput
:label="t('travel.thermographs.description')"
type="textarea"
v-model="thermographForm.description"
fill-input
/>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QFile
ref="inputFileRef"
:label="t('travel.thermographs.file')"
multiple
:accept="allowedContentTypes"
v-model="thermographForm.files"
>
<template #append>
<QIcon
name="vn:attach"
class="cursor-pointer q-mr-sm"
@click="inputFileRef.pickFiles()"
>
<QTooltip>{{ t('Select files') }}</QTooltip>
</QIcon>
<QIcon name="info" class="cursor-pointer">
<QTooltip>{{
t('globals.allowedFilesText', {
allowedContentTypes: allowedContentTypes,
})
}}</QTooltip>
</QIcon>
</template>
</QFile>
</div>
</VnRow>
</QCard>
</QForm>
</QPage>
</template>
<i18n>
es:
Select files: Selecciona ficheros
Thermograph created: Termógrafo creado
New thermograph: Nuevo termógrafo
</i18n>

View File

@ -16,8 +16,8 @@ import { toCurrency } from 'src/filters';
import { useArrayData } from 'composables/useArrayData';
import { toDate } from 'src/filters';
import { usePrintService } from 'composables/usePrintService';
import travelService from 'src/services/travel.service';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import axios from 'axios';
const router = useRouter();
const stateStore = useStateStore();
@ -223,9 +223,13 @@ const openReportPdf = () => {
};
const saveFieldValue = async (val, field, index) => {
const id = rows.value[index].id;
const params = { [field]: val };
await travelService.updateTravel(id, params);
try {
const id = rows.value[index].id;
const params = { [field]: val };
await axios.patch(`Travels/${id}`, params);
} catch (err) {
console.error('Error updating travel');
}
};
const navigateToTravelId = (id) => {

View File

@ -1,7 +1,7 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { reactive, ref, onBeforeMount } from 'vue';
import { useRoute } from 'vue-router';
import { useRoute, useRouter } from 'vue-router';
import FetchData from 'components/FetchData.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
@ -9,10 +9,11 @@ import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { toDate } from 'src/filters';
import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const newTravelForm = reactive({
ref: null,
@ -35,14 +36,14 @@ onBeforeMount(() => {
if (route.query.travelData) {
const travelData = JSON.parse(route.query.travelData);
for (let key in newTravelForm) {
if (key === 'landed' || key === 'shipped') {
newTravelForm[key] = travelData[key].substring(0, 10);
} else {
newTravelForm[key] = travelData[key];
}
newTravelForm[key] = travelData[key];
}
}
});
const redirectToTravelBasicData = (_, { id }) => {
router.push({ name: 'TravelBasicData', params: { id } });
};
</script>
<template>
@ -63,6 +64,7 @@ onBeforeMount(() => {
model="travel"
:form-initial-data="newTravelForm"
:observe-form-changes="viewAction === 'create'"
@on-data-saved="redirectToTravelBasicData"
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
@ -82,62 +84,13 @@ onBeforeMount(() => {
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QInput
rounded
placeholder="dd-mm-aaa"
<VnInputDate
v-model="data.shipped"
:label="t('globals.shipped')"
:model-value="toDate(data.shipped)"
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="data.shipped">
<div class="row items-center justify-end">
<QBtn
v-close-popup
:label="t('globals.close')"
color="primary"
flat
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
/>
</div>
<div class="col">
<QInput
rounded
placeholder="dd-mm-aaa"
:label="t('globals.landed')"
:model-value="toDate(data.landed)"
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="data.landed">
<div class="row items-center justify-end">
<QBtn
v-close-popup
:label="t('globals.close')"
color="primary"
flat
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
<VnInputDate :label="t('globals.landed')" v-model="data.landed" />
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">

View File

@ -6,8 +6,7 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelectFilter from 'src/components/common/VnSelectFilter.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FetchData from 'components/FetchData.vue';
import { toDate } from 'src/filters';
import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n();
const props = defineProps({
@ -60,7 +59,7 @@ const decrement = (paramsObj, key) => {
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<template #body="{ params, searchFn }">
<QItem>
<QItemSection>
<VnInput
@ -75,6 +74,7 @@ const decrement = (paramsObj, key) => {
<VnSelectFilter
:label="t('params.agencyModeFk')"
v-model="params.agencyModeFk"
@update:model-value="searchFn()"
:options="agenciesOptions"
option-value="agencyFk"
option-label="name"
@ -90,6 +90,7 @@ const decrement = (paramsObj, key) => {
<VnSelectFilter
:label="t('params.warehouseOutFk')"
v-model="params.warehouseOutFk"
@update:model-value="searchFn()"
:options="warehousesOptions"
option-value="id"
option-label="name"
@ -105,6 +106,7 @@ const decrement = (paramsObj, key) => {
<VnSelectFilter
:label="t('params.warehouseInFk')"
v-model="params.warehouseInFk"
@update:model-value="searchFn()"
:options="warehousesOptions"
option-value="id"
option-label="name"
@ -147,68 +149,20 @@ const decrement = (paramsObj, key) => {
</QItem>
<QItem>
<QItemSection>
<QInput
dense
outlined
rounded
placeholder="dd-mm-aaa"
<VnInputDate
v-model="params.landedFrom"
:label="t('params.landedFrom')"
:model-value="toDate(params.landedFrom)"
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="params.landedFrom">
<div class="row items-center justify-end">
<QBtn
v-close-popup
:label="t('globals.close')"
color="primary"
flat
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QInput
dense
outlined
rounded
placeholder="dd-mm-aaa"
:model-value="toDate(params.landedTo)"
<VnInputDate
:label="t('params.landedTo')"
>
<template #append>
<QIcon name="event" class="cursor-pointer">
<QPopupProxy
cover
transition-show="scale"
transition-hide="scale"
>
<QDate v-model="params.landedTo">
<div class="row items-center justify-end">
<QBtn
v-close-popup
:label="t('globals.close')"
color="primary"
flat
/>
</div>
</QDate>
</QPopupProxy>
</QIcon>
</template>
</QInput>
v-model="params.landedTo"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
@ -216,6 +170,7 @@ const decrement = (paramsObj, key) => {
<VnSelectFilter
:label="t('params.continent')"
v-model="params.continent"
@update:model-value="searchFn()"
:options="continentsOptions"
option-value="code"
option-label="name"
@ -261,18 +216,6 @@ const decrement = (paramsObj, key) => {
</VnFilterPanel>
</template>
<style scoped>
.input-number >>> input[type='number'] {
-moz-appearance: textfield;
}
.input-number >>> input::-webkit-outer-spin-button,
.input-number >>> input::-webkit-inner-spin-button {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
}
</style>
<i18n>
en:
params:

View File

@ -1,5 +1,5 @@
<script setup>
jsegarra marked this conversation as resolved
Review

Cuando pulsas en un detalle de un travel, se muestra el dialogo.
Si pulsas sobre el primer icono:

  1. No tiene tooltip
  2. Redirige a salix y no a lilium. @alexm es correcto?
  3. Redirige mal, pues le falta el summary después.
Cuando pulsas en un detalle de un travel, se muestra el dialogo. Si pulsas sobre el primer icono: 1. No tiene tooltip 2. Redirige a salix y no a lilium. @alexm es correcto? 3. Redirige mal, pues le falta el summary después.
Review

Corregido.

Commit: 4dc31bfdce

Corregido. Commit: https://gitea.verdnatura.es/verdnatura/salix-front/commit/4dc31bfdcec15f752a40ef88df4fd70282230a29
Review

1 y 3 OK

1 y 3 OK
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
@ -7,6 +7,8 @@ import CardList from 'src/components/ui/CardList.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import TravelSummary from './Card/TravelSummary.vue';
import TravelFilter from './TravelFilter.vue';
import FetchData from 'components/FetchData.vue';
import { useStateStore } from 'stores/useStateStore';
import { toDate } from 'src/filters/index';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
@ -16,6 +18,8 @@ const { t } = useI18n();
const stateStore = useStateStore();
const { viewSummary } = useSummaryDialog();
const warehouses = ref([]);
const navigateToTravelId = (id) => {
router.push({ path: `/travel/${id}` });
};
@ -33,12 +37,35 @@ const redirectCreateEntryView = (travelData) => {
router.push({ name: 'EntryCreate', query: { travelFk: travelData.id } });
};
const getWarehouseName = (id) => {
return warehouses.value.find((warehouse) => warehouse.id === id).name;
};
const getDateQBadgeColor = (date) => {
let today = Date.vnNew();
today.setHours(0, 0, 0, 0);
date = new Date(date);
date.setHours(0, 0, 0, 0);
const timeDifference = today - date;
if (timeDifference == 0) return 'warning';
if (timeDifference < 0) return 'success';
};
onMounted(async () => {
stateStore.rightDrawer = true;
});
</script>
<template>
<FetchData
url="Warehouses"
:filter="{ fields: ['id', 'name'] }"
order="name"
@on-fetch="(data) => (warehouses = data)"
auto-load
/>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<TravelFilter data-key="TravelList" />
@ -66,22 +93,43 @@ onMounted(async () => {
:value="row.agencyModeName"
/>
<VnLv
v-if="warehouses.length > 0"
:label="t('globals.wareHouseOut')"
:value="row.warehouseOutFk"
/>
<VnLv
:label="t('globals.shipped')"
:value="toDate(row.shipped)"
/>
<VnLv
:label="t('globals.landed')"
:value="toDate(row.landed)"
:value="getWarehouseName(row.warehouseOutFk)"
/>
<VnLv :label="t('globals.shipped')">
<template #value>
<QBadge
v-if="getDateQBadgeColor(row.shipped)"
:color="getDateQBadgeColor(row.shipped)"
class="q-ma-none"
dense
style="font-size: 14px"
>
{{ toDate(row.shipped) }}
</QBadge>
<span v-else>{{ toDate(row.shipped) }}</span>
</template>
</VnLv>
<VnLv :label="t('globals.landed')">
<template #value>
<QBadge
v-if="getDateQBadgeColor(row.landed)"
:color="getDateQBadgeColor(row.landed)"
class="q-ma-none"
dense
style="font-size: 14px"
>
{{ toDate(row.landed) }}
</QBadge>
<span v-else>{{ toDate(row.landed) }}</span>
</template>
</VnLv>
<VnLv
v-if="warehouses.length > 0"
:label="t('globals.wareHouseIn')"
:value="row.warehouseInFk"
:value="getWarehouseName(row.warehouseInFk)"
/>
<VnLv
:label="t('globals.totalEntries')"
:value="row.totalEntries"
@ -125,9 +173,6 @@ onMounted(async () => {
en:
addEntry: Add entry
es:
addEntry: Añadir entrada
</i18n>

View File

@ -43,7 +43,6 @@ export default {
name: 'TravelCreate',
meta: {
title: 'travelCreate',
icon: '',
},
component: () => import('src/pages/Travel/TravelCreate.vue'),
},
@ -69,9 +68,8 @@ export default {
meta: {
title: 'basicData',
icon: 'vn:settings',
// roles: [],
},
// component: () => import(),
component: () => import('src/pages/Travel/Card/TravelBasicData.vue'),
},
{
name: 'TravelHistory',
@ -79,19 +77,45 @@ export default {
meta: {
title: 'history',
icon: 'history',
// roles: [],
},
// component: () => import(),
component: () => import('src/pages/Travel/Card/TravelLog.vue'),
},
{
name: 'TravelThermographs',
path: 'thermographs',
path: 'thermographs/:thermographId?',
meta: {
title: 'thermographs',
icon: 'vn:thermometer',
// roles: [],
},
// component: () => import(),
redirect: {
name: 'TravelThermographsIndex',
},
children: [
{
name: 'TravelThermographsIndex',
path: 'index',
component: () =>
import('src/pages/Travel/Card/TravelThermographs.vue'),
},
{
name: 'TravelThermographsCreate',
path: 'create',
props: { viewAction: 'create' },
component: () =>
import(
'src/pages/Travel/Card/TravelThermographsForm.vue'
),
},
{
name: 'TravelThermographsEdit',
path: 'edit',
props: { viewAction: 'edit' },
component: () =>
import(
'src/pages/Travel/Card/TravelThermographsForm.vue'
),
},
],
},
],
},

View File

@ -1,23 +0,0 @@
import axios from 'axios';
const travelService = {
getTravelEntries: async (param) => {
try {
return await axios.get(`Travels/${param}/getEntries`);
} catch (err) {
console.error(`Error fetching travel entries`, err);
return err.response;
}
},
updateTravel: async (id, params) => {
try {
return await axios.patch(`Travels/${id}`, params);
} catch (err) {
console.error(`Error updating travel`, err);
return err.response;
}
},
};
export default travelService;