0
0
Fork 0

Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 6898-supplierMigration

This commit is contained in:
Carlos Satorres 2024-06-21 10:32:44 +02:00
commit f88fffa89c
30 changed files with 628 additions and 419 deletions

View File

@ -16,10 +16,11 @@ function stopEventPropagation(event) {
} }
</script> </script>
<template> <template>
<slot name="beforeChip" :row="row"></slot>
<span <span
v-for="col of columns" v-for="col of columns"
:key="col.name" :key="col.name"
@click="stopEventPropagation($event)" @click="stopEventPropagation"
class="cursor-text" class="cursor-text"
> >
<QChip <QChip

View File

@ -9,7 +9,7 @@ import VnInput from 'components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnComponent from 'components/common/VnComponent.vue'; import VnComponent from 'components/common/VnComponent.vue';
const model = defineModel(); const model = defineModel(undefined, { required: true });
const $props = defineProps({ const $props = defineProps({
column: { column: {
type: Object, type: Object,
@ -17,7 +17,7 @@ const $props = defineProps({
}, },
row: { row: {
type: Object, type: Object,
required: true, default: () => {},
}, },
default: { default: {
type: [Object, String], type: [Object, String],

View File

@ -27,7 +27,7 @@ const $props = defineProps({
default: 'params', default: 'params',
}, },
}); });
const model = defineModel(); const model = defineModel(undefined, { required: true });
const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl }); const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl });
const columnFilter = computed(() => $props.column?.columnFilter); const columnFilter = computed(() => $props.column?.columnFilter);
@ -36,45 +36,44 @@ const enterEvent = {
'keyup.enter': () => addFilter(model.value), 'keyup.enter': () => addFilter(model.value),
remove: () => addFilter(null), remove: () => addFilter(null),
}; };
const defaultAttrs = {
filled: !$props.showTitle,
class: 'q-px-sm q-pb-xs q-pt-none',
dense: true,
};
const forceAttrs = {
label: $props.showTitle ? '' : $props.column.label,
};
const components = { const components = {
input: { input: {
component: markRaw(VnInput), component: markRaw(VnInput),
event: enterEvent, event: enterEvent,
attrs: { attrs: {
class: 'q-px-sm q-pb-xs q-pt-none', ...defaultAttrs,
dense: true,
filled: !$props.showTitle,
clearable: true, clearable: true,
}, },
forceAttrs: { forceAttrs,
label: $props.showTitle ? '' : $props.column.label,
},
}, },
number: { number: {
component: markRaw(VnInput), component: markRaw(VnInput),
event: enterEvent, event: enterEvent,
attrs: { attrs: {
dense: true, ...defaultAttrs,
class: 'q-px-sm q-pb-xs q-pt-none',
clearable: true, clearable: true,
filled: !$props.showTitle,
},
forceAttrs: {
label: $props.showTitle ? '' : $props.column.label,
}, },
forceAttrs,
}, },
date: { date: {
component: markRaw(VnInputDate), component: markRaw(VnInputDate),
event: updateEvent, event: updateEvent,
attrs: { attrs: {
dense: true, ...defaultAttrs,
class: 'q-px-sm q-pb-xs q-pt-none',
filled: !$props.showTitle,
style: 'min-width: 150px', style: 'min-width: 150px',
}, },
forceAttrs: { forceAttrs,
label: $props.showTitle ? '' : $props.column.label,
},
}, },
checkbox: { checkbox: {
component: markRaw(QCheckbox), component: markRaw(QCheckbox),
@ -84,9 +83,7 @@ const components = {
class: $props.showTitle ? 'q-py-sm q-mt-md' : 'q-px-md q-py-xs', class: $props.showTitle ? 'q-py-sm q-mt-md' : 'q-px-md q-py-xs',
'toggle-indeterminate': true, 'toggle-indeterminate': true,
}, },
forceAttrs: { forceAttrs,
label: $props.showTitle ? '' : $props.column.label,
},
}, },
select: { select: {
component: markRaw(VnSelect), component: markRaw(VnSelect),
@ -96,9 +93,7 @@ const components = {
dense: true, dense: true,
filled: !$props.showTitle, filled: !$props.showTitle,
}, },
forceAttrs: { forceAttrs,
label: $props.showTitle ? '' : $props.column.label,
},
}, },
}; };
@ -116,32 +111,36 @@ async function addFilter(value) {
} }
function alignRow() { function alignRow() {
if ($props.column.align == 'left') return 'justify-start items-start'; switch ($props.column.align) {
if ($props.column.align == 'right') return 'justify-end items-end'; case 'left':
return 'flex-center'; return 'justify-start items-start';
case 'right':
return 'justify-end items-end';
default:
return 'flex-center';
}
} }
const showFilter = computed(
() => $props.column?.columnFilter !== false && $props.column.name != 'tableActions'
);
</script> </script>
<template> <template>
<div <div
v-if="showTitle && column" v-if="showTitle"
class="q-pt-sm q-px-sm ellipsis" class="q-pt-sm q-px-sm ellipsis"
:class="`text-${column.align ?? 'left'}`" :class="`text-${column?.align ?? 'left'}`"
:style="!showFilter ? { 'min-height': 72 + 'px' } : ''"
> >
{{ column.label }} {{ column?.label }}
</div> </div>
<div <div v-if="showFilter" class="full-width" :class="alignRow()">
v-if="columnFilter !== false && column.name != 'tableActions'"
class="full-width"
:class="alignRow()"
>
<VnTableColumn <VnTableColumn
:column="$props.column" :column="$props.column"
:row="{}"
default="input" default="input"
v-model="model" v-model="model"
:components="components" :components="components"
component-prop="columnFilter" component-prop="columnFilter"
/> />
</div> </div>
<div v-else-if="showTitle" style="height: 45px"><!-- fixme! --></div>
</template> </template>

View File

@ -66,7 +66,9 @@ const route = useRoute();
const router = useRouter(); const router = useRouter();
const quasar = useQuasar(); const quasar = useQuasar();
const mode = ref('card'); const DEFAULT_MODE = 'card';
const TABLE_MODE = 'table';
const mode = ref(DEFAULT_MODE);
const selected = ref([]); const selected = ref([]);
const routeQuery = JSON.parse(route?.query[$props.searchUrl] ?? '{}'); const routeQuery = JSON.parse(route?.query[$props.searchUrl] ?? '{}');
const params = ref({ ...routeQuery, ...routeQuery.filter?.where }); const params = ref({ ...routeQuery, ...routeQuery.filter?.where });
@ -77,17 +79,17 @@ const tableModes = [
{ {
icon: 'view_column', icon: 'view_column',
title: t('table view'), title: t('table view'),
value: 'table', value: TABLE_MODE,
}, },
{ {
icon: 'grid_view', icon: 'grid_view',
title: t('grid view'), title: t('grid view'),
value: 'card', value: DEFAULT_MODE,
}, },
]; ];
onMounted(() => { onMounted(() => {
mode.value = quasar.platform.is.mobile ? 'card' : $props.defaultMode; mode.value = quasar.platform.is.mobile ? DEFAULT_MODE : $props.defaultMode;
stateStore.rightDrawer = true; stateStore.rightDrawer = true;
setUserParams(route.query[$props.searchUrl]); setUserParams(route.query[$props.searchUrl]);
}); });
@ -172,6 +174,9 @@ function columnName(col) {
return name; return name;
} }
function getColAlign(col) {
return 'text-' + (col.align ?? 'left')
}
defineExpose({ defineExpose({
reload, reload,
redirect: redirectFn, redirect: redirectFn,
@ -218,7 +223,7 @@ defineExpose({
:limit="20" :limit="20"
ref="CrudModelRef" ref="CrudModelRef"
:search-url="searchUrl" :search-url="searchUrl"
:disable-infinite-scroll="mode == 'table'" :disable-infinite-scroll="mode == TABLE_MODE"
@save-changes="reload" @save-changes="reload"
:has-subtoolbar="isEditable" :has-subtoolbar="isEditable"
> >
@ -229,11 +234,11 @@ defineExpose({
:columns="splittedColumns.columns" :columns="splittedColumns.columns"
:rows="rows" :rows="rows"
v-model:selected="selected" v-model:selected="selected"
:grid="mode != 'table'" :grid="mode != TABLE_MODE"
table-header-class="bg-header" table-header-class="bg-header"
card-container-class="grid-three" card-container-class="grid-three"
flat flat
:style="mode == 'table' && 'max-height: 90vh'" :style="mode == TABLE_MODE && 'max-height: 90vh'"
virtual-scroll virtual-scroll
@virtual-scroll=" @virtual-scroll="
(event) => (event) =>
@ -287,7 +292,7 @@ defineExpose({
<QTh auto-width class="sticky" /> <QTh auto-width class="sticky" />
</template> </template>
<template #body-cell-tableStatus="{ col, row }"> <template #body-cell-tableStatus="{ col, row }">
<QTd auto-width :class="`text-${col.align ?? 'left'}`"> <QTd auto-width :class="getColAlign(col)">
<VnTableChip <VnTableChip
:columns="splittedColumns.columnChips" :columns="splittedColumns.columnChips"
:row="row" :row="row"
@ -303,7 +308,7 @@ defineExpose({
<QTd <QTd
auto-width auto-width
class="no-margin q-px-xs" class="no-margin q-px-xs"
:class="`text-${col.align ?? 'left'}`" :class="getColAlign(col)"
> >
<VnTableColumn <VnTableColumn
:column="col" :column="col"
@ -317,7 +322,7 @@ defineExpose({
<template #body-cell-tableActions="{ col, row }"> <template #body-cell-tableActions="{ col, row }">
<QTd <QTd
auto-width auto-width
:class="`text-${col.align ?? 'left'}`" :class="getColAlign(col)"
class="sticky no-padding" class="sticky no-padding"
@click="stopEventPropagation($event)" @click="stopEventPropagation($event)"
> >

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { computed } from 'vue'; import { onBeforeMount, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
@ -32,10 +32,15 @@ const url = computed(() => {
return props.customUrl; return props.customUrl;
}); });
useArrayData(props.dataKey, { const arrayData = useArrayData(props.dataKey, {
url: url.value, url: url.value,
filter: props.filter, filter: props.filter,
}); });
onBeforeMount(async () => {
if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id };
await arrayData.fetch({ append: false });
});
</script> </script>
<template> <template>
<QDrawer <QDrawer

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { computed, defineModel } from 'vue'; import { computed, defineModel } from 'vue';
const model = defineModel(); const model = defineModel(undefined, { required: true });
const $props = defineProps({ const $props = defineProps({
prop: { prop: {
type: Object, type: Object,

View File

@ -0,0 +1,60 @@
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useSession } from 'src/composables/useSession';
const $props = defineProps({
collection: {
type: [String, Number],
default: 'Images',
},
size: {
type: String,
default: '200x200',
},
zoomSize: {
type: String,
required: true,
default: 'lg',
},
id: {
type: Boolean,
default: false,
},
});
const show = ref(false);
const token = useSession().getTokenMultimedia();
const timeStamp = ref(`timestamp=${Date.now()}`);
const url = computed(
() =>
`/api/${$props.collection}/catalog/${$props.size}/${$props.id}/download?access_token=${token}&${timeStamp.value}`
);
const emits = defineEmits(['refresh']);
const reload = (emit = false) => {
timeStamp.value = `timestamp=${Date.now()}`;
};
defineExpose({
reload,
});
onMounted(() => {});
</script>
<template>
<QImg :src="url" v-bind="$attrs" @click="show = !show" spinner-color="primary" />
<QDialog v-model="show" v-if="$props.zoomSize">
<QImg :src="url" class="img_zoom" v-bind="$attrs" spinner-color="primary" />
</QDialog>
</template>
<style lang="scss" scoped>
.q-img {
cursor: zoom-in;
}
.rounded {
border-radius: 50%;
}
.img_zoom {
width: 100%;
height: auto;
border-radius: 0%;
}
</style>

View File

@ -992,6 +992,18 @@ route:
shipped: Preparation date shipped: Preparation date
viewCmr: View CMR viewCmr: View CMR
downloadCmrs: Download CMRs downloadCmrs: Download CMRs
columnLabels:
Id: Id
vehicle: Vehicle
description: Description
isServed: Served
worker: Worker
date: Date
started: Started
actions: Actions
agency: Agency
volume: Volume
finished: Finished
supplier: supplier:
pageTitles: pageTitles:
suppliers: Suppliers suppliers: Suppliers

View File

@ -107,6 +107,7 @@ globals:
aliasUsers: Usuarios aliasUsers: Usuarios
subRoles: Subroles subRoles: Subroles
inheritedRoles: Roles heredados inheritedRoles: Roles heredados
workers: Trabajadores
created: Fecha creación created: Fecha creación
worker: Trabajador worker: Trabajador
now: Ahora now: Ahora
@ -977,6 +978,18 @@ route:
shipped: Fecha preparación shipped: Fecha preparación
viewCmr: Ver CMR viewCmr: Ver CMR
downloadCmrs: Descargar CMRs downloadCmrs: Descargar CMRs
columnLabels:
Id: Id
vehicle: Vehículo
description: Descripción
isServed: Servida
worker: Trabajador
date: Fecha
started: Iniciada
actions: Acciones
agency: Agencia
volume: Volumen
finished: Finalizada
supplier: supplier:
pageTitles: pageTitles:
suppliers: Proveedores suppliers: Proveedores

View File

@ -33,7 +33,6 @@ function exprBuilder(param, value) {
url="Agencies" url="Agencies"
order="name" order="name"
:expr-builder="exprBuilder" :expr-builder="exprBuilder"
auto-load
> >
<template #body="{ rows }"> <template #body="{ rows }">
<CardList <CardList

View File

@ -10,7 +10,7 @@ import CustomerFilter from '../CustomerFilter.vue';
:descriptor="CustomerDescriptor" :descriptor="CustomerDescriptor"
:filter-panel="CustomerFilter" :filter-panel="CustomerFilter"
search-data-key="CustomerList" search-data-key="CustomerList"
search-url="Clients/filter" search-url="Clients/extendedListFilter"
searchbar-label="Search customer" searchbar-label="Search customer"
searchbar-info="You can search by customer id or name" searchbar-info="You can search by customer id or name"
/> />

View File

@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import VnLocation from 'src/components/common/VnLocation.vue'; import VnLocation from 'src/components/common/VnLocation.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import CustomerSummary from './Card/CustomerSummary.vue'; import CustomerSummary from './Card/CustomerSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
@ -382,9 +383,14 @@ function handleLocation(data, location) {
</script> </script>
<template> <template>
<VnSearchbar
:info="t('You can search by customer id or name')"
:label="t('Search customer')"
data-key="Customer"
/>
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="CustomerExtendedList" data-key="Customer"
url="Clients/extendedListFilter" url="Clients/extendedListFilter"
:create="{ :create="{
urlCreate: 'Clients/createWithUser', urlCreate: 'Clients/createWithUser',

View File

@ -11,9 +11,9 @@ import VnInput from 'src/components/common/VnInput.vue';
import FetchedTags from 'components/ui/FetchedTags.vue'; import FetchedTags from 'components/ui/FetchedTags.vue';
import VnConfirm from 'components/ui/VnConfirm.vue'; import VnConfirm from 'components/ui/VnConfirm.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useStateStore } from 'stores/useStateStore';
import { toCurrency } from 'src/filters'; import { toCurrency } from 'src/filters';
import axios from 'axios'; import axios from 'axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
@ -22,7 +22,6 @@ const quasar = useQuasar();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore();
const { notify } = useNotify(); const { notify } = useNotify();
const rowsSelected = ref([]); const rowsSelected = ref([]);
@ -312,20 +311,22 @@ const lockIconType = (groupingMode, mode) => {
auto-load auto-load
@on-fetch="(data) => (packagingsOptions = data)" @on-fetch="(data) => (packagingsOptions = data)"
/> />
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()"> <VnSubToolbar>
<QBtnGroup push style="column-gap: 10px"> <template #st-actions>
<slot name="moreBeforeActions" /> <QBtnGroup push style="column-gap: 10px">
<QBtn <slot name="moreBeforeActions" />
:label="t('globals.remove')" <QBtn
color="primary" :label="t('globals.remove')"
icon="delete" color="primary"
flat icon="delete"
@click="openRemoveDialog()" flat
:disable="!rowsSelected?.length" @click="openRemoveDialog()"
:title="t('globals.remove')" :disable="!rowsSelected?.length"
/> :title="t('globals.remove')"
</QBtnGroup> />
</Teleport> </QBtnGroup>
</template>
</VnSubToolbar>
<VnPaginate <VnPaginate
ref="entryBuysPaginateRef" ref="entryBuysPaginateRef"
data-key="EntryBuys" data-key="EntryBuys"

View File

@ -10,8 +10,10 @@ import FetchData from 'src/components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnCurrency from 'src/components/common/VnCurrency.vue'; import VnCurrency from 'src/components/common/VnCurrency.vue';
import { toCurrency } from 'src/filters'; import { toCurrency } from 'src/filters';
import useNotify from 'src/composables/useNotify.js';
const route = useRoute(); const route = useRoute();
const { notify } = useNotify();
const { t } = useI18n(); const { t } = useI18n();
const arrayData = useArrayData(); const arrayData = useArrayData();
const invoiceIn = computed(() => arrayData.store.data); const invoiceIn = computed(() => arrayData.store.data);
@ -69,6 +71,7 @@ const isNotEuro = (code) => code != 'EUR';
async function insert() { async function insert() {
await axios.post('/InvoiceInDueDays/new', { id: +invoiceId }); await axios.post('/InvoiceInDueDays/new', { id: +invoiceId });
await invoiceInFormRef.value.reload(); await invoiceInFormRef.value.reload();
notify(t('globals.dataSaved'), 'positive');
} }
const getTotalAmount = (rows) => rows.reduce((acc, { amount }) => acc + +amount, 0); const getTotalAmount = (rows) => rows.reduce((acc, { amount }) => acc + +amount, 0);
</script> </script>

View File

@ -3,8 +3,7 @@ import { ref, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import EditPictureForm from 'components/EditPictureForm.vue'; import EditPictureForm from 'components/EditPictureForm.vue';
import VnImg from 'src/components/ui/VnImg.vue';
import { useSession } from 'src/composables/useSession';
import axios from 'axios'; import axios from 'axios';
const $props = defineProps({ const $props = defineProps({
@ -27,19 +26,12 @@ const $props = defineProps({
}); });
const { t } = useI18n(); const { t } = useI18n();
const { getTokenMultimedia } = useSession();
const image = ref(null); const image = ref(null);
const editPhotoFormDialog = ref(null); const editPhotoFormDialog = ref(null);
const showEditPhotoForm = ref(false); const showEditPhotoForm = ref(false);
const warehouseName = ref(null); const warehouseName = ref(null);
const getItemAvatar = async () => {
const token = getTokenMultimedia();
const timeStamp = `timestamp=${Date.now()}`;
image.value = `/api/Images/catalog/200x200/${$props.entityId}/download?access_token=${token}&${timeStamp}`;
};
const toggleEditPictureForm = () => { const toggleEditPictureForm = () => {
showEditPhotoForm.value = !showEditPhotoForm.value; showEditPhotoForm.value = !showEditPhotoForm.value;
}; };
@ -62,14 +54,17 @@ const getWarehouseName = async (warehouseFk) => {
}; };
onMounted(async () => { onMounted(async () => {
getItemAvatar();
getItemConfigs(); getItemConfigs();
}); });
const handlePhotoUpdated = (evt = false) => {
image.value.reload(evt);
};
</script> </script>
<template> <template>
<div class="relative-position"> <div class="relative-position">
<QImg :src="image" spinner-color="primary" style="min-height: 256px"> <VnImg ref="image" :id="$props.entityId" @refresh="handlePhotoUpdated(true)">
<template #error> <template #error>
<div class="absolute-full picture text-center q-pa-md flex flex-center"> <div class="absolute-full picture text-center q-pa-md flex flex-center">
<div> <div>
@ -82,7 +77,7 @@ onMounted(async () => {
</div> </div>
</div> </div>
</template> </template>
</QImg> </VnImg>
<QBtn <QBtn
v-if="showEditButton" v-if="showEditButton"
color="primary" color="primary"
@ -97,7 +92,7 @@ onMounted(async () => {
collection="catalog" collection="catalog"
:id="entityId" :id="entityId"
@close-form="toggleEditPictureForm()" @close-form="toggleEditPictureForm()"
@on-photo-uploaded="getItemAvatar()" @on-photo-uploaded="handlePhotoUpdated"
/> />
</QDialog> </QDialog>
</QBtn> </QBtn>

View File

@ -3,14 +3,15 @@ import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import axios from 'axios'; import axios from 'axios';
import VnInput from 'components/common/VnInput.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue';
import { useValidator } from 'src/composables/useValidator';
import VnInput from 'src/components/common/VnInput.vue';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const props = defineProps({ const props = defineProps({
dataKey: { dataKey: {
@ -21,32 +22,34 @@ const props = defineProps({
type: Array, type: Array,
required: true, required: true,
}, },
tagValue: {
type: Array,
required: true,
},
}); });
const categoryList = ref(null); const categoryList = ref(null);
const selectedCategoryFk = ref(null); const selectedCategoryFk = ref(null);
const typeList = ref(null); const typeList = ref(null);
const selectedTypeFk = ref(null); const selectedTypeFk = ref(null);
const validationsStore = useValidator();
const selectedOrder = ref(null);
const selectedOrderField = ref(null);
const moreFields = ref([]);
const moreFieldsOrder = ref([]);
const createValue = (val, done) => {
if (val.length > 2) {
if (!tagOptions.value.includes(val)) {
done(tagOptions.value, 'add-unique');
}
tagValues.value.push({ value: val });
}
};
const resetCategory = () => { const resetCategory = () => {
selectedCategoryFk.value = null; selectedCategoryFk.value = null;
typeList.value = null; typeList.value = null;
}; };
const selectedOrder = ref(null);
const orderList = [
{ way: 'ASC', name: 'Ascendant' },
{ way: 'DESC', name: 'Descendant' },
];
const selectedOrderField = ref(null);
const OrderFields = [
{ field: 'relevancy DESC, name', name: 'Relevancy', priority: 999 },
{ field: 'showOrder, price', name: 'Color and price', priority: 999 },
{ field: 'name', name: 'Name', priority: 999 },
{ field: 'price', name: 'Price', priority: 999 },
];
const clearFilter = (key) => { const clearFilter = (key) => {
if (key === 'categoryFk') { if (key === 'categoryFk') {
resetCategory(); resetCategory();
@ -72,21 +75,6 @@ const loadTypes = async (categoryFk) => {
typeList.value = data; typeList.value = data;
}; };
const onFilterInit = async ({ params }) => {
if (params.typeFk) {
selectedTypeFk.value = params.typeFk;
}
if (params.categoryFk) {
await loadTypes(params.categoryFk);
selectedCategoryFk.value = params.categoryFk;
}
if (params.orderBy) {
orderByParam.value = JSON.parse(params.orderBy);
selectedOrder.value = orderByParam.value?.way;
selectedOrderField.value = orderByParam.value?.field;
}
};
const selectedCategory = computed(() => const selectedCategory = computed(() =>
(categoryList.value || []).find( (categoryList.value || []).find(
(category) => category?.id === selectedCategoryFk.value (category) => category?.id === selectedCategoryFk.value
@ -109,10 +97,7 @@ function exprBuilder(param, value) {
const selectedTag = ref(null); const selectedTag = ref(null);
const tagValues = ref([{}]); const tagValues = ref([{}]);
const tagOptions = ref(null); const tagOptions = ref([]);
const isButtonDisabled = computed(
() => !selectedTag.value || tagValues.value.some((item) => !item.value)
);
const applyTagFilter = (params, search) => { const applyTagFilter = (params, search) => {
if (!tagValues.value?.length) { if (!tagValues.value?.length) {
@ -125,12 +110,12 @@ const applyTagFilter = (params, search) => {
} }
params.tagGroups.push( params.tagGroups.push(
JSON.stringify({ JSON.stringify({
values: tagValues.value, values: tagValues.value.filter((obj) => Object.keys(obj).length > 0),
tagSelection: { tagSelection: {
...selectedTag.value, ...selectedTag.value,
orgShowField: selectedTag.value.name, orgShowField: selectedTag?.value?.name,
}, },
tagFk: selectedTag.value.tagFk, tagFk: selectedTag?.value?.tagFk,
}) })
); );
search(); search();
@ -147,20 +132,52 @@ const removeTagChip = (selection, params, search) => {
search(); search();
}; };
const orderByParam = ref(null); const onOrderChange = (value, params) => {
const tagObj = JSON.parse(params.orderBy);
const onOrderFieldChange = (value, params, search) => { tagObj.way = value.name;
const orderBy = Object.assign({}, orderByParam.value, { field: value.field }); params.orderBy = JSON.stringify(tagObj);
params.orderBy = JSON.stringify(orderBy);
search();
}; };
const onOrderChange = (value, params, search) => { const onOrderFieldChange = (value, params) => {
const orderBy = Object.assign({}, orderByParam.value, { way: value.way }); const tagObj = JSON.parse(params.orderBy); // esto donde va
params.orderBy = JSON.stringify(orderBy); const fields = {
search(); Relevancy: (value) => value + ' DESC, name',
ColorAndPrice: 'showOrder, price',
Name: 'name',
Price: 'price',
};
let tagField = fields[value];
if (!tagField) return;
if (typeof tagField === 'function') tagField = tagField(value);
tagObj.field = tagField;
params.orderBy = JSON.stringify(tagObj);
switch (value) {
case 'Relevancy':
tagObj.field = value + ' DESC, name';
params.orderBy = JSON.stringify(tagObj);
console.log('params: ', params);
break;
case 'ColorAndPrice':
tagObj.field = 'showOrder, price';
params.orderBy = JSON.stringify(tagObj);
console.log('params: ', params);
break;
case 'Name':
tagObj.field = 'name';
params.orderBy = JSON.stringify(tagObj);
console.log('params: ', params);
break;
case 'Price':
tagObj.field = 'price';
params.orderBy = JSON.stringify(tagObj);
console.log('params: ', params);
break;
}
}; };
const _moreFields = ['ASC', 'DESC'];
const _moreFieldsTypes = ['Relevancy', 'ColorAndPrice', 'Name', 'Price'];
const setCategoryList = (data) => { const setCategoryList = (data) => {
categoryList.value = (data || []) categoryList.value = (data || [])
.filter((category) => category.display) .filter((category) => category.display)
@ -168,6 +185,8 @@ const setCategoryList = (data) => {
...category, ...category,
icon: `vn:${(category.icon || '').split('-')[1]}`, icon: `vn:${(category.icon || '').split('-')[1]}`,
})); }));
moreFields.value = useLang(_moreFields);
moreFieldsOrder.value = useLang(_moreFieldsTypes);
}; };
const getCategoryClass = (category, params) => { const getCategoryClass = (category, params) => {
@ -175,6 +194,20 @@ const getCategoryClass = (category, params) => {
return 'active'; return 'active';
} }
}; };
const useLang = (values) => {
const { models } = validationsStore;
const properties = models.Item?.properties || {};
return values.map((name) => {
let prop = properties[name];
const label = t(`params.${name}`);
return {
name,
label,
type: prop ? prop.type : null,
};
});
};
</script> </script>
<template> <template>
@ -182,9 +215,9 @@ const getCategoryClass = (category, params) => {
<VnFilterPanel <VnFilterPanel
:data-key="props.dataKey" :data-key="props.dataKey"
:hidden-tags="['orderFk', 'orderBy']" :hidden-tags="['orderFk', 'orderBy']"
:unremovable-params="['orderFk', 'orderBy']"
:expr-builder="exprBuilder" :expr-builder="exprBuilder"
:custom-tags="['tagGroups']" :custom-tags="['tagGroups']"
@init="onFilterInit"
@remove="clearFilter" @remove="clearFilter"
> >
<template #tags="{ tag, formatFn }"> <template #tags="{ tag, formatFn }">
@ -274,40 +307,29 @@ const getCategoryClass = (category, params) => {
<QItem class="q-my-md"> <QItem class="q-my-md">
<QItemSection> <QItemSection>
<VnSelect <VnSelect
:label="t('params.order')" :label="t('Order')"
v-model="selectedOrder" v-model="selectedOrder"
:options="orderList || []" :options="moreFields"
option-value="way" option-label="label"
option-label="name"
dense dense
outlined outlined
rounded rounded
:emit-value="false" @update:model-value="(value) => onOrderChange(value, params)"
use-input
:is-clearable="false"
@update:model-value="
(value) => onOrderChange(value, params, searchFn)
"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-md"> <QItem class="q-mb-md">
<QItemSection> <QItemSection>
<VnSelect <VnSelect
:label="t('params.order')" :label="t('Order by')"
v-model="selectedOrderField" v-model="selectedOrderField"
:options="OrderFields || []" :options="moreFieldsOrder"
option-value="field" option-label="label"
option-label="name" option-value="name"
dense dense
outlined outlined
rounded rounded
:emit-value="false" @update:model-value="(value) => onOrderFieldChange(value, params)"
use-input
:is-clearable="false"
@update:model-value="
(value) => onOrderFieldChange(value, params, searchFn)
"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -333,15 +355,30 @@ const getCategoryClass = (category, params) => {
:key="value" :key="value"
class="q-mt-md filter-value" class="q-mt-md filter-value"
> >
<VnInput <FetchData
v-if="selectedTag?.isFree" v-if="selectedTag"
v-model="value.value" :url="`Tags/${selectedTag}/filterValue`"
:label="t('params.value')" limit="30"
is-outlined auto-load
class="filter-input" @on-fetch="(data) => (tagOptions = data)"
/> />
<VnSelect <VnSelect
v-else v-if="!selectedTag"
:label="t('params.value')"
v-model="value.value"
:options="tagValue || []"
option-value="value"
option-label="value"
dense
outlined
rounded
emit-value
use-input
class="filter-input"
@new-value="createValue"
/>
<VnSelect
v-else-if="selectedTag === 1"
:label="t('params.value')" :label="t('params.value')"
v-model="value.value" v-model="value.value"
:options="tagOptions || []" :options="tagOptions || []"
@ -352,18 +389,18 @@ const getCategoryClass = (category, params) => {
rounded rounded
emit-value emit-value
use-input use-input
:disable="!selectedTag" class="filter-input"
@new-value="createValue"
/>
<VnInput
v-else
:label="t('params.value')"
v-model="value.value"
dense
outlined
rounded
class="filter-input" class="filter-input"
/> />
<FetchData
v-if="selectedTag && !selectedTag.isFree"
:url="`Tags/${selectedTag?.id}/filterValue`"
limit="30"
auto-load
@on-fetch="(data) => (tagOptions = data)"
/>
<QIcon <QIcon
name="delete" name="delete"
class="filter-icon" class="filter-icon"
@ -388,7 +425,6 @@ const getCategoryClass = (category, params) => {
rounded rounded
type="button" type="button"
unelevated unelevated
:disable="isButtonDisabled"
@click.stop="applyTagFilter(params, searchFn)" @click.stop="applyTagFilter(params, searchFn)"
/> />
</QItemSection> </QItemSection>
@ -453,6 +489,12 @@ en:
tag: Tag tag: Tag
value: Value value: Value
order: Order order: Order
ASC: Ascendant
DESC: Descendant
Relevancy: Relevancy
ColorAndPrice: Color and price
Name: Name
Price: Price
es: es:
params: params:
type: Tipo type: Tipo
@ -460,6 +502,14 @@ es:
tag: Etiqueta tag: Etiqueta
value: Valor value: Valor
order: Orden order: Orden
ASC: Ascendiente
DESC: Descendiente
Relevancy: Relevancia
ColorAndPrice: Color y precio
Name: Nombre
Price: Precio
Order: Orden
Order by: Ordenar por
Plant: Planta Plant: Planta
Flower: Flor Flower: Flor
Handmade: Confección Handmade: Confección

View File

@ -3,16 +3,14 @@ import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
import VnImg from 'src/components/ui/VnImg.vue';
import OrderCatalogItemDialog from 'pages/Order/Card/OrderCatalogItemDialog.vue'; import OrderCatalogItemDialog from 'pages/Order/Card/OrderCatalogItemDialog.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import { useSession } from 'composables/useSession';
import toCurrency from '../../../filters/toCurrency'; import toCurrency from '../../../filters/toCurrency';
const DEFAULT_PRICE_KG = 0; const DEFAULT_PRICE_KG = 0;
const { getTokenMultimedia } = useSession();
const token = getTokenMultimedia();
const { t } = useI18n(); const { t } = useI18n();
defineProps({ defineProps({
@ -29,14 +27,7 @@ const dialog = ref(null);
<div class="container order-catalog-item overflow-hidden"> <div class="container order-catalog-item overflow-hidden">
<QCard class="card shadow-6"> <QCard class="card shadow-6">
<div class="img-wrapper"> <div class="img-wrapper">
<QImg <VnImg :id="item.id" class="image" />
:src="`/api/Images/catalog/200x200/${item.id}/download?access_token=${token}`"
spinner-color="primary"
:ratio="1"
height="192"
width="192"
class="image"
/>
<div v-if="item.hex" class="item-color-container"> <div v-if="item.hex" class="item-color-container">
<div <div
class="item-color" class="item-color"
@ -59,7 +50,10 @@ const dialog = ref(null);
</template> </template>
<div class="footer"> <div class="footer">
<div class="price"> <div class="price">
<p>{{ item.available }} {{ t('to') }} {{ item.price }}</p> <p>
{{ item.available }} {{ t('to') }}
{{ toCurrency(item.price) }}
</p>
<QIcon name="add_circle" class="icon"> <QIcon name="add_circle" class="icon">
<QTooltip>{{ t('globals.add') }}</QTooltip> <QTooltip>{{ t('globals.add') }}</QTooltip>
<QPopupProxy ref="dialog"> <QPopupProxy ref="dialog">

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { useRoute } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import axios from 'axios'; import axios from 'axios';
@ -16,6 +16,7 @@ const route = useRoute();
const state = useState(); const state = useState();
const ORDER_MODEL = 'order'; const ORDER_MODEL = 'order';
const router = useRouter();
const isNew = Boolean(!route.params.id); const isNew = Boolean(!route.params.id);
const initialFormState = reactive({ const initialFormState = reactive({
clientFk: null, clientFk: null,
@ -26,22 +27,19 @@ const initialFormState = reactive({
const clientList = ref([]); const clientList = ref([]);
const agencyList = ref([]); const agencyList = ref([]);
const addressList = ref([]); const addressList = ref([]);
const clientId = ref(null);
const onClientsFetched = async (data) => { const onClientsFetched = (data) => {
try { clientList.value = data;
clientList.value = data; initialFormState.clientFk = Number(route.query?.clientFk) || null;
initialFormState.clientFk = Number(route.query?.clientFk) || null; clientId.value = initialFormState.clientFk;
if (initialFormState.clientFk) { const client = clientList.value.find(
const { defaultAddressFk } = clientList.value.find( (client) => client.id === initialFormState.clientFk
(client) => client.id === initialFormState.clientFk );
); if (!client?.defaultAddressFk)
throw new Error(t(`No default address found for the client`));
if (defaultAddressFk) await fetchAddressList(defaultAddressFk); fetchAddressList(client.defaultAddressFk);
}
} catch (err) {
console.error('Error fetching clients', err);
}
}; };
const fetchAddressList = async (addressId) => { const fetchAddressList = async (addressId) => {
@ -55,7 +53,6 @@ const fetchAddressList = async (addressId) => {
}, },
}); });
addressList.value = data; addressList.value = data;
// Set address by default
if (addressList.value?.length === 1) { if (addressList.value?.length === 1) {
state.get(ORDER_MODEL).addressFk = addressList.value[0].id; state.get(ORDER_MODEL).addressFk = addressList.value[0].id;
} }
@ -121,6 +118,21 @@ const orderFilter = {
}, },
], ],
}; };
const onClientChange = async (clientId) => {
try {
const { data } = await axios.get(`Clients/${clientId}`);
console.log('info cliente: ', data);
await fetchAddressList(data.defaultAddressFk);
} catch (error) {
console.error('Error al cambiar el cliente:', error);
}
};
async function onDataSaved(data) {
await router.push({ path: `/order/${data}/catalog` });
}
</script> </script>
<template> <template>
@ -134,13 +146,15 @@ const orderFilter = {
<div class="q-pa-md"> <div class="q-pa-md">
<FormModel <FormModel
:url="!isNew ? `Orders/${route.params.id}` : null" :url="!isNew ? `Orders/${route.params.id}` : null"
:url-create="isNew ? 'Orders/new' : null" url-create="Orders/new"
@on-data-saved="onDataSaved"
:model="ORDER_MODEL" :model="ORDER_MODEL"
:form-initial-data="isNew ? initialFormState : null" :form-initial-data="isNew ? initialFormState : null"
:observe-form-changes="!isNew" :observe-form-changes="!isNew"
:mapper="isNew ? orderMapper : null" :mapper="isNew ? orderMapper : null"
:filter="orderFilter" :filter="orderFilter"
@on-fetch="fetchOrderDetails" @on-fetch="fetchOrderDetails"
auto-load
> >
<template #form="{ data }"> <template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow class="row q-gutter-md q-mb-md">
@ -151,9 +165,7 @@ const orderFilter = {
option-value="id" option-value="id"
option-label="name" option-label="name"
hide-selected hide-selected
@update:model-value=" @update:model-value="onClientChange"
(client) => fetchAddressList(client.defaultAddressFk)
"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -170,12 +182,10 @@ const orderFilter = {
v-model="data.addressFk" v-model="data.addressFk"
:options="addressList" :options="addressList"
option-value="id" option-value="id"
option-label="nickname" option-label="street"
hide-selected hide-selected
:disable="!addressList?.length" :disable="!addressList?.length"
@update:model-value=" @update:model-value="onAddressChange"
() => fetchAgencyList(data.landed, data.addressFk)
"
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -216,3 +226,8 @@ const orderFilter = {
</FormModel> </FormModel>
</div> </div>
</template> </template>
<i18n>
es:
No default address found for the client: No hay ninguna dirección asociada a este cliente.
</i18n>

View File

@ -4,7 +4,6 @@ import { useRoute } from 'vue-router';
import { onMounted, onUnmounted, ref } from 'vue'; import { onMounted, onUnmounted, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnPaginate from 'components/ui/VnPaginate.vue'; import VnPaginate from 'components/ui/VnPaginate.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import OrderCatalogItem from 'pages/Order/Card/OrderCatalogItem.vue'; import OrderCatalogItem from 'pages/Order/Card/OrderCatalogItem.vue';
import OrderCatalogFilter from 'pages/Order/Card/OrderCatalogFilter.vue'; import OrderCatalogFilter from 'pages/Order/Card/OrderCatalogFilter.vue';
@ -35,38 +34,31 @@ function extractTags(items) {
}); });
}); });
tags.value = resultTags; tags.value = resultTags;
extractValueTags(items);
}
const tagValue = ref([]);
function extractValueTags(items) {
const resultValueTags = items.flatMap((x) =>
Object.keys(x)
.filter((k) => /^value\d+$/.test(k))
.map((v) => x[v])
.filter((v) => v)
.sort()
);
tagValue.value = resultValueTags;
} }
</script> </script>
<template> <template>
<Teleport to="#searchbar" v-if="stateStore.isHeaderMounted()">
<VnSearchbar
data-key="OrderCatalogList"
url="Orders/CatalogFilter"
:limit="50"
:user-params="catalogParams"
:static-params="['orderFk', 'orderBy']"
:redirect="false"
/>
</Teleport>
<Teleport v-if="stateStore.isHeaderMounted()" to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click.stop="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8"> <QScrollArea class="fit text-grey-8">
<OrderCatalogFilter data-key="OrderCatalogList" :tags="tags" /> <OrderCatalogFilter
data-key="OrderCatalogList"
:tag-value="tagValue"
:tags="tags"
/>
</QScrollArea> </QScrollArea>
</QDrawer> </QDrawer>
<QPage class="column items-center q-pa-md"> <QPage class="column items-center q-pa-md">

View File

@ -7,19 +7,17 @@ import { useQuasar } from 'quasar';
import VnPaginate from 'components/ui/VnPaginate.vue'; import VnPaginate from 'components/ui/VnPaginate.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
import CardList from 'components/ui/CardList.vue';
import FetchedTags from 'components/ui/FetchedTags.vue'; import FetchedTags from 'components/ui/FetchedTags.vue';
import VnConfirm from 'components/ui/VnConfirm.vue'; import VnConfirm from 'components/ui/VnConfirm.vue';
import VnImg from 'components/ui/VnImg.vue';
import { toCurrency, toDate } from 'src/filters'; import { toCurrency, toDate } from 'src/filters';
import { useSession } from 'composables/useSession';
import axios from 'axios'; import axios from 'axios';
import ItemDescriptorProxy from '../Item/Card/ItemDescriptorProxy.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const { getTokenMultimedia } = useSession();
const quasar = useQuasar(); const quasar = useQuasar();
const token = getTokenMultimedia();
const orderSummary = ref({ const orderSummary = ref({
total: null, total: null,
vat: null, vat: null,
@ -61,6 +59,56 @@ async function confirmOrder() {
type: 'positive', type: 'positive',
}); });
} }
const detailsColumns = ref([
{
name: 'img',
label: '',
field: (row) => row?.item?.id,
},
{
name: 'item',
label: t('order.summary.item'),
field: (row) => row?.item?.id,
sortable: true,
},
{
name: 'description',
label: t('globals.description'),
field: (row) => row?.item?.name,
},
{
name: 'warehouse',
label: t('warehouse'),
field: (row) => row?.warehouse?.name,
sortable: true,
},
{
name: 'shipped',
label: t('shipped'),
field: (row) => toDate(row?.shipped),
},
{
name: 'quantity',
label: t('order.summary.quantity'),
field: (row) => row?.quantity,
},
{
name: 'price',
label: t('order.summary.price'),
field: (row) => toCurrency(row?.price),
},
{
name: 'amount',
label: t('order.summary.amount'),
field: (row) => toCurrency(row?.quantity * row?.price),
},
{
name: 'actions',
label: '',
field: (row) => row?.id,
},
]);
</script> </script>
<template> <template>
@ -83,30 +131,33 @@ async function confirmOrder() {
auto-load auto-load
/> />
<QPage :key="componentKey" class="column items-center q-pa-md"> <QPage :key="componentKey" class="column items-center q-pa-md">
<div class="vn-card-list"> <div class="order-list full-width">
<div v-if="!orderSummary.total" class="no-result"> <div v-if="!orderSummary.total" class="no-result">
{{ t('globals.noResults') }} {{ t('globals.noResults') }}
</div> </div>
<QCard v-else class="order-lines-summary q-pa-lg">
<p class="header text-right block"> <QDrawer side="right" :width="270" show-if-above>
{{ t('summary') }} <QCard class="order-lines-summary q-pa-lg">
</p> <p class="header text-right block">
<VnLv {{ t('summary') }}
v-if="orderSummary.vat && orderSummary.total" </p>
:label="t('subtotal')" <VnLv
:value="toCurrency(orderSummary.total - orderSummary.vat)" v-if="orderSummary.vat && orderSummary.total"
/> :label="t('subtotal')"
<VnLv :value="toCurrency(orderSummary.total - orderSummary.vat)"
v-if="orderSummary.vat" />
:label="t('VAT')" <VnLv
:value="toCurrency(orderSummary?.vat)" v-if="orderSummary.vat"
/> :label="t('VAT')"
<VnLv :value="toCurrency(orderSummary?.vat)"
v-if="orderSummary.total" />
:label="t('total')" <VnLv
:value="toCurrency(orderSummary?.total)" v-if="orderSummary.total"
/> :label="t('total')"
</QCard> :value="toCurrency(orderSummary?.total)"
/>
</QCard>
</QDrawer>
<VnPaginate <VnPaginate
data-key="OrderLines" data-key="OrderLines"
url="OrderRows" url="OrderRows"
@ -125,74 +176,71 @@ async function confirmOrder() {
}" }"
> >
<template #body="{ rows }"> <template #body="{ rows }">
<div class="catalog-list q-mt-xl"> <div class="q-pa-md">
<CardList <QTable
v-for="row in rows" :columns="detailsColumns"
:key="row.id" :rows="rows"
:id="row.id" flat
:title="row?.item?.name" class="full-width"
class="cursor-inherit" style="text-align: center"
> >
<template #title> <template #header="props">
<div class="flex items-center"> <QTr class="tr-header" :props="props">
<div class="image-wrapper q-mr-md"> <QTh
<QImg v-for="col in props.cols"
:src="`/api/Images/catalog/50x50/${row?.item?.id}/download?access_token=${token}`" :key="col.name"
spinner-color="primary" :props="props"
:ratio="1" style="text-align: center"
height="50"
width="50"
class="image"
/>
</div>
<div
class="title text-primary text-weight-bold text-h5"
> >
{{ row?.item?.name }} {{ t(col.label) }}
</QTh>
</QTr>
</template>
<template #body-cell-img="{ value }">
<QTd>
<div class="image-wrapper">
<VnImg :id="value" class="rounded" />
</div> </div>
<QChip class="q-chip-color" outline size="sm"> </QTd>
{{ t('ID') }}: {{ row.id }}
</QChip>
</div>
</template> </template>
<template #list-items> <template #body-cell-item="{ value }">
<div class="q-mb-sm"> <QTd class="item">
<span class="text-uppercase subname"> <span class="link">
{{ row.item.subName }} <QBtn flat>
{{ value }}
</QBtn>
<ItemDescriptorProxy :id="value" />
</span> </span>
<FetchedTags :item="row.item" :max-length="5" /> </QTd>
</div>
<VnLv :label="t('item')" :value="String(row.item.id)" />
<VnLv
:label="t('warehouse')"
:value="row.warehouse.name"
/>
<VnLv
:label="t('shipped')"
:value="toDate(row.shipped)"
/>
<VnLv
:label="t('quantity')"
:value="String(row.quantity)"
/>
<VnLv
:label="t('price')"
:value="toCurrency(row.price)"
/>
<VnLv
:label="t('amount')"
:value="toCurrency(row.price * row.quantity)"
/>
</template> </template>
<template #actions v-if="!order?.isConfirmed"> <template #body-cell-description="{ row, value }">
<QBtn <QTd>
:label="t('remove')" <div
@click.stop="confirmRemove(row)" class="row column full-width justify-between items-start"
color="primary" >
style="margin-top: 15px" {{ value }}
/> <div v-if="value" class="subName">
{{ value.toUpperCase() }}
</div>
</div>
<FetchedTags :item="row.item" :max-length="6" />
</QTd>
</template> </template>
</CardList>
<template #body-cell-actions="{ value }">
<QTd>
<QIcon
name="delete"
color="primary"
size="sm"
class="cursor-pointer"
@click.stop="confirmRemove(value)"
>
<QTooltip>{{ t('Remove thermograph') }}</QTooltip>
</QIcon>
</QTd>
</template>
</QTable>
</div> </div>
</template> </template>
</VnPaginate> </VnPaginate>
@ -239,14 +287,7 @@ async function confirmOrder() {
.image-wrapper { .image-wrapper {
height: 50px; height: 50px;
width: 50px; width: 50px;
margin-left: 30%;
.image {
border-radius: 50%;
}
}
.subname {
color: var(--vn-label-color);
} }
.no-result { .no-result {
@ -255,6 +296,11 @@ async function confirmOrder() {
color: var(--vn-label-color); color: var(--vn-label-color);
text-align: center; text-align: center;
} }
.subName {
text-transform: uppercase;
color: var(--vn-label-color);
}
</style> </style>
<i18n> <i18n>
en: en:

View File

@ -240,4 +240,5 @@ es:
From: Desde From: Desde
To: Hasta To: Hasta
Served: Servida Served: Servida
Days Onward: Días en adelante
</i18n> </i18n>

View File

@ -3,14 +3,15 @@ import { computed, onMounted, onUnmounted, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'components/ui/VnLv.vue';
import { QIcon } from 'quasar'; import { QIcon } from 'quasar';
import { dashIfEmpty, toCurrency, toDate, toHour } from 'src/filters'; import { dashIfEmpty, toCurrency, toDate, toHour } from 'src/filters';
import { openBuscaman } from 'src/utils/buscaman';
import CardSummary from 'components/ui/CardSummary.vue';
import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue'; import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
import { openBuscaman } from 'src/utils/buscaman'; import VnLv from 'components/ui/VnLv.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -127,8 +128,14 @@ const ticketColumns = ref([
<span>{{ `${entity?.route.id} - ${entity?.route?.description}` }}</span> <span>{{ `${entity?.route.id} - ${entity?.route?.description}` }}</span>
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<QCard class="vn-max">
<VnTitle
:url="`#/route/${entityId}/basic-data`"
:text="t('globals.pageTitles.basicData')"
/>
</QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<VnLv :label="t('ID')" :value="entity?.route.id" />
<VnLv <VnLv
:label="t('route.summary.date')" :label="t('route.summary.date')"
:value="toDate(entity?.route.created)" :value="toDate(entity?.route.created)"
@ -153,24 +160,6 @@ const ticketColumns = ref([
:label="t('route.summary.cost')" :label="t('route.summary.cost')"
:value="toCurrency(entity.route?.cost)" :value="toCurrency(entity.route?.cost)"
/> />
</QCard>
<QCard class="vn-one">
<VnLv
:label="t('route.summary.started')"
:value="toHour(entity?.route.started)"
/>
<VnLv
:label="t('route.summary.finished')"
:value="toHour(entity?.route.finished)"
/>
<VnLv
:label="t('route.summary.kmStart')"
:value="dashIfEmpty(entity?.route?.kmStart)"
/>
<VnLv
:label="t('route.summary.kmEnd')"
:value="dashIfEmpty(entity?.route?.kmEnd)"
/>
<VnLv <VnLv
:label="t('route.summary.volume')" :label="t('route.summary.volume')"
:value="`${dashIfEmpty(entity?.route?.m3)} / ${dashIfEmpty( :value="`${dashIfEmpty(entity?.route?.m3)} / ${dashIfEmpty(
@ -192,19 +181,32 @@ const ticketColumns = ref([
/> />
</QCard> </QCard>
<QCard class="vn-one"> <QCard class="vn-one">
<div class="header"> <VnLv
{{ t('globals.description') }} :label="t('route.summary.started')"
</div> :value="toHour(entity?.route.started)"
<p> />
{{ dashIfEmpty(entity?.route?.description) }} <VnLv
</p> :label="t('route.summary.finished')"
:value="toHour(entity?.route.finished)"
/>
<VnLv
:label="t('route.summary.kmStart')"
:value="dashIfEmpty(entity?.route?.kmStart)"
/>
<VnLv
:label="t('route.summary.kmEnd')"
:value="dashIfEmpty(entity?.route?.kmEnd)"
/>
<VnLv
:label="t('globals.description')"
:value="dashIfEmpty(entity?.route?.description)"
/>
</QCard> </QCard>
<QCard class="vn-max"> <QCard class="vn-max">
<a class="header" :href="`#/route/${entityId}/tickets`"> <VnTitle
{{ t('route.summary.tickets') }} :url="`#/route/${entityId}/tickets`"
<QIcon name="open_in_new" color="primary" /> :text="t('route.summary.tickets')"
</a> />
<QTable <QTable
:columns="ticketColumns" :columns="ticketColumns"
:rows="entity?.tickets" :rows="entity?.tickets"

View File

@ -109,13 +109,7 @@ function downloadPdfs() {
</RightMenu> </RightMenu>
<div class="column items-center"> <div class="column items-center">
<div class="list"> <div class="list">
<VnPaginate <VnPaginate data-key="CmrList" :url="`Routes/cmrs`" order="cmrFk DESC">
data-key="CmrList"
:url="`Routes/cmrs`"
order="cmrFk DESC"
limit="null"
auto-load
>
<template #body="{ rows }"> <template #body="{ rows }">
<QTable <QTable
:columns="columns" :columns="columns"

View File

@ -67,6 +67,7 @@ const filter = {
}, },
}, },
], ],
where: { id: entityId },
}; };
const openAddStopDialog = () => { const openAddStopDialog = () => {
@ -84,7 +85,7 @@ const openAddStopDialog = () => {
<CardSummary <CardSummary
data-key="RoadmapSummary" data-key="RoadmapSummary"
ref="summary" ref="summary"
:url="`Roadmaps/${entityId}`" :url="`Roadmaps`"
:filter="filter" :filter="filter"
> >
<template #header-left> <template #header-left>

View File

@ -39,7 +39,7 @@ const selectedRows = ref([]);
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'ID', name: 'ID',
label: t('ID'), label: 'Id',
field: (row) => row.routeFk, field: (row) => row.routeFk,
sortable: true, sortable: true,
align: 'left', align: 'left',
@ -117,7 +117,9 @@ const columns = computed(() => [
const refreshKey = ref(0); const refreshKey = ref(0);
const total = computed(() => selectedRows.value.reduce((item) => item?.price || 0, 0)); const total = computed(() => {
return selectedRows.value.reduce((sum, item) => sum + item.price, 0);
});
const openDmsUploadDialog = async () => { const openDmsUploadDialog = async () => {
dmsDialog.value.rowsToCreateInvoiceIn = selectedRows.value dmsDialog.value.rowsToCreateInvoiceIn = selectedRows.value
@ -212,7 +214,6 @@ function navigateToRouteSummary(event, row) {
data-key="RouteAutonomousList" data-key="RouteAutonomousList"
url="AgencyTerms/filter" url="AgencyTerms/filter"
:limit="20" :limit="20"
auto-load
> >
<template #body="{ rows }"> <template #body="{ rows }">
<div class="q-pa-md"> <div class="q-pa-md">
@ -306,6 +307,13 @@ function navigateToRouteSummary(event, row) {
cursor: pointer; cursor: pointer;
} }
} }
th:last-child,
td:last-child {
background-color: var(--vn-section-color);
position: sticky;
right: 0;
}
</style> </style>
<i18n> <i18n>
es: es:

View File

@ -1,40 +1,51 @@
<script setup> <script setup>
import VnPaginate from 'components/ui/VnPaginate.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { computed, onMounted, onUnmounted, ref } from 'vue'; import { computed, onMounted, ref } from 'vue';
import { dashIfEmpty, toHour } from 'src/filters'; import { dashIfEmpty, toHour } from 'src/filters';
import VnSelect from 'components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import { useValidator } from 'composables/useValidator'; import { useValidator } from 'composables/useValidator';
import { useSession } from 'composables/useSession';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useArrayData } from 'composables/useArrayData';
import { useQuasar } from 'quasar';
import axios from 'axios';
import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue';
import FetchData from 'components/FetchData.vue';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
import RouteFilter from 'pages/Route/Card/RouteFilter.vue';
import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import VnPaginate from 'components/ui/VnPaginate.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue'; import VnInput from 'components/common/VnInput.vue';
import VnInputTime from 'components/common/VnInputTime.vue'; import VnInputTime from 'components/common/VnInputTime.vue';
import axios from 'axios'; import VnLv from 'src/components/ui/VnLv.vue';
import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue';
import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue';
import RouteFilter from 'pages/Route/Card/RouteFilter.vue';
import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useSession } from 'composables/useSession';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue';
import { useQuasar } from 'quasar';
import { useArrayData } from 'composables/useArrayData';
import RightMenu from 'src/components/common/RightMenu.vue';
const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const { validate } = useValidator(); const { validate } = useValidator();
const { viewSummary } = useSummaryDialog();
const quasar = useQuasar(); const quasar = useQuasar();
const session = useSession(); const session = useSession();
const { viewSummary } = useSummaryDialog(); const paginate = ref();
const visibleColumns = ref([]); const visibleColumns = ref([]);
const selectedRows = ref([]); const selectedRows = ref([]);
const workers = ref([]);
const agencyList = ref([]);
const vehicleList = ref([]);
const allColumnNames = ref([]);
const confirmationDialog = ref(false);
const startingDate = ref(null);
const refreshKey = ref(0);
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'ID', name: 'Id',
label: t('ID'), label: t('Id'),
field: (row) => row.id, field: (row) => row.id,
sortable: true, sortable: true,
align: 'center', align: 'center',
@ -109,14 +120,12 @@ const columns = computed(() => [
align: 'right', align: 'right',
}, },
]); ]);
const arrayData = useArrayData('EntryLatestBuys', { const arrayData = useArrayData('EntryLatestBuys', {
url: 'Buys/latestBuysFilter', url: 'Buys/latestBuysFilter',
order: ['itemFk DESC'], order: ['itemFk DESC'],
}); });
const refreshKey = ref(0);
const workers = ref([]);
const agencyList = ref([]);
const vehicleList = ref([]);
const updateRoute = async (route) => { const updateRoute = async (route) => {
try { try {
return await axios.patch(`Routes/${route.id}`, route); return await axios.patch(`Routes/${route.id}`, route);
@ -124,9 +133,6 @@ const updateRoute = async (route) => {
return err; return err;
} }
}; };
const allColumnNames = ref([]);
const confirmationDialog = ref(false);
const startingDate = ref(null);
const cloneRoutes = () => { const cloneRoutes = () => {
axios.post('Routes/clone', { axios.post('Routes/clone', {
@ -135,6 +141,7 @@ const cloneRoutes = () => {
}); });
refreshKey.value++; refreshKey.value++;
startingDate.value = null; startingDate.value = null;
paginate.value.fetch();
}; };
const showRouteReport = () => { const showRouteReport = () => {
@ -154,15 +161,13 @@ const showRouteReport = () => {
window.open(url, '_blank'); window.open(url, '_blank');
}; };
const markAsServed = () => { function markAsServed() {
selectedRows.value.forEach((row) => { selectedRows.value.forEach(async (row) => {
if (row?.id) { if (row?.id) await axios.patch(`Routes/${row?.id}`, { isOk: true });
axios.patch(`Routes/${row?.id}`, { isOk: true });
}
}); });
refreshKey.value++; refreshKey.value++;
startingDate.value = null; startingDate.value = null;
}; }
const openTicketsDialog = (id) => { const openTicketsDialog = (id) => {
if (!id) { if (!id) {
@ -179,11 +184,9 @@ const openTicketsDialog = (id) => {
}; };
onMounted(async () => { onMounted(async () => {
stateStore.rightDrawer = true;
allColumnNames.value = columns.value.map((col) => col.name); allColumnNames.value = columns.value.map((col) => col.name);
await arrayData.fetch({ append: false }); await arrayData.fetch({ append: false });
}); });
onUnmounted(() => (stateStore.rightDrawer = false));
</script> </script>
<template> <template>
@ -210,6 +213,10 @@ onUnmounted(() => (stateStore.rightDrawer = false));
<QBtn flat :label="t('Cancel')" v-close-popup class="text-primary" /> <QBtn flat :label="t('Cancel')" v-close-popup class="text-primary" />
<QBtn color="primary" v-close-popup @click="cloneRoutes"> <QBtn color="primary" v-close-popup @click="cloneRoutes">
{{ t('globals.clone') }} {{ t('globals.clone') }}
<VnLv
:label="t('route.summary.packages')"
:value="getTotalPackages(entity.tickets)"
/>
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</QCard> </QCard>
@ -228,7 +235,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
class="LeftIcon" class="LeftIcon"
:all-columns="allColumnNames" :all-columns="allColumnNames"
table-code="routesList" table-code="routesList"
labels-traductions-path="globals" labels-traductions-path="route.columnLabels"
@on-config-saved="visibleColumns = [...$event]" @on-config-saved="visibleColumns = [...$event]"
/> />
</template> </template>
@ -256,7 +263,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
color="primary" color="primary"
class="q-mr-sm" class="q-mr-sm"
:disable="!selectedRows?.length" :disable="!selectedRows?.length"
@click="markAsServed" @click="markAsServed()"
> >
<QTooltip>{{ t('Mark as served') }}</QTooltip> <QTooltip>{{ t('Mark as served') }}</QTooltip>
</QBtn> </QBtn>
@ -269,7 +276,6 @@ onUnmounted(() => (stateStore.rightDrawer = false));
url="Routes/filter" url="Routes/filter"
:order="['created ASC', 'started ASC', 'id ASC']" :order="['created ASC', 'started ASC', 'id ASC']"
:limit="20" :limit="20"
auto-load
> >
<template #body="{ rows }"> <template #body="{ rows }">
<div class="q-pa-md route-table"> <div class="q-pa-md route-table">
@ -500,7 +506,6 @@ en:
hourStarted: Started hour hourStarted: Started hour
hourFinished: Finished hour hourFinished: Finished hour
es: es:
ID: ID
Worker: Trabajador Worker: Trabajador
Agency: Agencia Agency: Agencia
Vehicle: Vehículo Vehicle: Vehículo
@ -521,4 +526,6 @@ es:
Summary: Resumen Summary: Resumen
Route is closed: La ruta está cerrada Route is closed: La ruta está cerrada
Route is not served: La ruta no está servida Route is not served: La ruta no está servida
hourStarted: Hora de inicio
hourFinished: Hora de fin
</i18n> </i18n>

View File

@ -129,7 +129,7 @@ function confirmRemove() {
} }
function navigateToRoadmapSummary(event, row) { function navigateToRoadmapSummary(event, row) {
router.push({ name: 'RoadmapSummary', params: { id: row.id } }); router.push({ name: 'RoadmapSummary', params: { id: 1 } });
} }
</script> </script>
@ -193,7 +193,6 @@ function navigateToRoadmapSummary(event, row) {
url="Roadmaps" url="Roadmaps"
:limit="20" :limit="20"
:filter="filter" :filter="filter"
auto-load
> >
<template #body="{ rows }"> <template #body="{ rows }">
<div class="q-pa-md"> <div class="q-pa-md">

View File

@ -141,7 +141,7 @@ const setOrderedPriority = async () => {
}; };
const sortRoutes = async () => { const sortRoutes = async () => {
await axios.get(`Routes/${route.params?.id}/guessPriority/`); await axios.patch(`Routes/${route.params?.id}/guessPriority/`);
refreshKey.value++; refreshKey.value++;
}; };

View File

@ -20,8 +20,8 @@ const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const entityId = computed(() => $props.id || route.params.id); const entityId = computed(() => $props.id || route.params.id);
const URL_KEY = 'NotificationSubscriptions'; const URL_KEY = 'NotificationSubscriptions';
const active = ref(); const active = ref(new Map());
const available = ref(); const available = ref(new Map());
async function toggleNotification(notification) { async function toggleNotification(notification) {
try { try {
@ -56,6 +56,7 @@ const swapEntry = (from, to, key) => {
}; };
function setNotifications(data) { function setNotifications(data) {
console.log('data: ', data);
active.value = new Map(data.active); active.value = new Map(data.active);
available.value = new Map(data.available); available.value = new Map(data.available);
} }

View File

@ -22,7 +22,7 @@ describe('InvoiceInDueDay', () => {
cy.waitForElement('thead'); cy.waitForElement('thead');
cy.get(addBtn).click(); cy.get(addBtn).click();
cy.saveCard(); cy.get('tbody > :nth-child(1)').should('exist');
cy.get('.q-notification__message').should('have.text', 'Data saved'); cy.get('.q-notification__message').should('have.text', 'Data saved');
}); });
}); });