Merge branch 'dev' into 8185-DuplicateLeftMenu
gitea/salix-front/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Jon Elias 2024-11-18 06:21:42 +00:00
commit 3d8aa3cff9
10 changed files with 111 additions and 77 deletions

View File

@ -34,18 +34,26 @@ const search = ref(null);
const filteredItems = computed(() => { const filteredItems = computed(() => {
if (!search.value) return items.value; if (!search.value) return items.value;
const normalizedSearch = normalize(search.value);
return items.value.filter((item) => { return items.value.filter((item) => {
const locale = t(item.title).toLowerCase(); const locale = normalize(t(item.title));
return locale.includes(search.value.toLowerCase()); return locale.includes(normalizedSearch);
}); });
}); });
const filteredPinnedModules = computed(() => { const filteredPinnedModules = computed(() => {
if (!search.value) return pinnedModules.value; if (!search.value) return pinnedModules.value;
const normalizedSearch = search.value
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
const map = new Map(); const map = new Map();
for (const [key, pinnedModule] of pinnedModules.value) { for (const [key, pinnedModule] of pinnedModules.value) {
const locale = t(pinnedModule.title).toLowerCase(); const locale = t(pinnedModule.title)
if (locale.includes(search.value.toLowerCase())) map.set(key, pinnedModule); .normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
if (locale.includes(normalizedSearch)) map.set(key, pinnedModule);
} }
return map; return map;
}); });
@ -149,6 +157,13 @@ async function togglePinned(item, event) {
const handleItemExpansion = (itemName) => { const handleItemExpansion = (itemName) => {
expansionItemElements[itemName].scrollToLastElement(); expansionItemElements[itemName].scrollToLastElement();
}; };
function normalize(text) {
return text
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase();
}
</script> </script>
<template> <template>

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { ref, toRefs, computed, watch, onMounted, useAttrs } from 'vue'; import { ref, toRefs, computed, watch, onMounted, useAttrs } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import FetchData from 'src/components/FetchData.vue'; import { useArrayData } from 'src/composables/useArrayData';
import { useRequired } from 'src/composables/useRequired'; import { useRequired } from 'src/composables/useRequired';
import dataByOrder from 'src/utils/dataByOrder'; import dataByOrder from 'src/utils/dataByOrder';
@ -90,6 +90,10 @@ const $props = defineProps({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
dataKey: {
type: String,
default: null,
},
}); });
const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])]; const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
@ -98,14 +102,14 @@ const { optionLabel, optionValue, optionFilter, optionFilterValue, options, mode
const myOptions = ref([]); const myOptions = ref([]);
const myOptionsOriginal = ref([]); const myOptionsOriginal = ref([]);
const vnSelectRef = ref(); const vnSelectRef = ref();
const dataRef = ref();
const lastVal = ref(); const lastVal = ref();
const noOneText = t('globals.noOne'); const noOneText = t('globals.noOne');
const noOneOpt = ref({ const noOneOpt = ref({
[optionValue.value]: false, [optionValue.value]: false,
[optionLabel.value]: noOneText, [optionLabel.value]: noOneText,
}); });
const isLoading = ref(false);
const useURL = computed(() => $props.url);
const value = computed({ const value = computed({
get() { get() {
return $props.modelValue; return $props.modelValue;
@ -129,11 +133,18 @@ watch(modelValue, async (newValue) => {
onMounted(() => { onMounted(() => {
setOptions(options.value); setOptions(options.value);
if ($props.url && $props.modelValue && !findKeyInOptions()) if (useURL.value && $props.modelValue && !findKeyInOptions())
fetchFilter($props.modelValue); fetchFilter($props.modelValue);
if ($props.focusOnMount) setTimeout(() => vnSelectRef.value.showPopup(), 300); if ($props.focusOnMount) setTimeout(() => vnSelectRef.value.showPopup(), 300);
}); });
defineExpose({ opts: myOptions });
const arrayDataKey =
$props.dataKey ?? ($props.url?.length > 0 ? $props.url : $attrs.name ?? $attrs.label);
const arrayData = useArrayData(arrayDataKey, { url: $props.url, searchUrl: false });
function findKeyInOptions() { function findKeyInOptions() {
if (!$props.options) return; if (!$props.options) return;
return filter($props.modelValue, $props.options)?.length; return filter($props.modelValue, $props.options)?.length;
@ -168,7 +179,7 @@ function filter(val, options) {
} }
async function fetchFilter(val) { async function fetchFilter(val) {
if (!$props.url || !dataRef.value) return; if (!$props.url) return;
const { fields, include, sortBy, limit } = $props; const { fields, include, sortBy, limit } = $props;
const key = const key =
@ -190,8 +201,8 @@ async function fetchFilter(val) {
const fetchOptions = { where, include, limit }; const fetchOptions = { where, include, limit };
if (fields) fetchOptions.fields = fields; if (fields) fetchOptions.fields = fields;
if (sortBy) fetchOptions.order = sortBy; if (sortBy) fetchOptions.order = sortBy;
arrayData.reset(['skip', 'filter.skip', 'page']);
return dataRef.value.fetch(fetchOptions); return (await arrayData.applyFilter({ filter: fetchOptions }))?.data;
} }
async function filterHandler(val, update) { async function filterHandler(val, update) {
@ -231,20 +242,23 @@ function nullishToTrue(value) {
const getVal = (val) => ($props.useLike ? { like: `%${val}%` } : val); const getVal = (val) => ($props.useLike ? { like: `%${val}%` } : val);
defineExpose({ opts: myOptions }); async function onScroll({ to, direction, from, index }) {
const lastIndex = myOptions.value.length - 1;
if (from === 0 && index === 0) return;
if (!useURL.value && !$props.fetchRef) return;
if (direction === 'decrease') return;
if (to === lastIndex && arrayData.store.hasMoreData && !isLoading.value) {
isLoading.value = true;
await arrayData.loadMore();
setOptions(arrayData.store.data);
vnSelectRef.value.scrollTo(lastIndex);
isLoading.value = false;
}
}
</script> </script>
<template> <template>
<FetchData
ref="dataRef"
:url="$props.url"
@on-fetch="(data) => setOptions(data)"
:where="where || { [optionValue]: value }"
:limit="limit"
:sort-by="sortBy"
:fields="fields"
:params="params"
/>
<QSelect <QSelect
v-model="value" v-model="value"
:options="myOptions" :options="myOptions"
@ -263,6 +277,9 @@ defineExpose({ opts: myOptions });
:rules="mixinRules" :rules="mixinRules"
virtual-scroll-slice-size="options.length" virtual-scroll-slice-size="options.length"
hide-bottom-space hide-bottom-space
:input-debounce="useURL ? '300' : '0'"
:loading="isLoading"
@virtual-scroll="onScroll"
> >
<template v-if="isClearable" #append> <template v-if="isClearable" #append>
<QIcon <QIcon

View File

@ -65,13 +65,9 @@ onBeforeRouteLeave((to, from, next) => {
auto-load auto-load
@on-fetch="(data) => (observationTypes = data)" @on-fetch="(data) => (observationTypes = data)"
/> />
<QCard class="q-pa-xs q-mb-xl full-width" v-if="$props.addNote"> <QCard class="q-pa-xs q-mb-lg full-width" v-if="$props.addNote">
<QCardSection horizontal> <QCardSection horizontal>
<VnAvatar :worker-id="currentUser.id" size="md" /> {{ t('New note') }}
<div class="full-width row justify-between q-pa-xs">
<VnUserLink :name="t('New note')" :worker-id="currentUser.id" />
{{ t('globals.now') }}
</div>
</QCardSection> </QCardSection>
<QCardSection class="q-px-xs q-my-none q-py-none"> <QCardSection class="q-px-xs q-my-none q-py-none">
<VnRow class="full-width"> <VnRow class="full-width">
@ -144,7 +140,7 @@ onBeforeRouteLeave((to, from, next) => {
<div class="full-width row justify-between q-pa-xs"> <div class="full-width row justify-between q-pa-xs">
<div> <div>
<VnUserLink <VnUserLink
:name="`${note.worker.user.nickname}`" :name="`${note.worker.user.name}`"
:worker-id="note.worker.id" :worker-id="note.worker.id"
/> />
<QBadge <QBadge

View File

@ -0,0 +1,8 @@
import { openURL } from 'quasar';
const defaultWindowFeatures = {
noopener: true,
noreferrer: true,
};
export default function (url, windowFeatures = defaultWindowFeatures, fn = undefined) {
openURL(url, fn, windowFeatures);
}

View File

@ -174,23 +174,6 @@ const setData = (entity) => (data.value = useCardDescription(entity?.name, entit
> >
<QTooltip>{{ t('Customer ticket list') }}</QTooltip> <QTooltip>{{ t('Customer ticket list') }}</QTooltip>
</QBtn> </QBtn>
<QBtn
:to="{
name: 'TicketList',
query: {
table: JSON.stringify({
clientFk: entity.id,
}),
createForm: JSON.stringify({ clientId: entity.id }),
},
}"
size="md"
color="primary"
target="_blank"
icon="vn:ticketAdd"
>
<QTooltip>{{ t('New ticket') }}</QTooltip>
</QBtn>
<QBtn <QBtn
:to="{ :to="{
name: 'InvoiceOutList', name: 'InvoiceOutList',
@ -202,23 +185,6 @@ const setData = (entity) => (data.value = useCardDescription(entity?.name, entit
> >
<QTooltip>{{ t('Customer invoice out list') }}</QTooltip> <QTooltip>{{ t('Customer invoice out list') }}</QTooltip>
</QBtn> </QBtn>
<QBtn
:to="{
name: 'OrderList',
query: {
table: JSON.stringify({
clientFk: entity.id,
}),
createForm: JSON.stringify({ clientFk: entity.id }),
},
}"
size="md"
target="_blank"
icon="vn:basketadd"
color="primary"
>
<QTooltip>{{ t('New order') }}</QTooltip>
</QBtn>
<QBtn <QBtn
:to="{ :to="{
name: 'AccountSummary', name: 'AccountSummary',

View File

@ -6,8 +6,8 @@ import axios from 'axios';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import useNotify from 'src/composables/useNotify'; import useNotify from 'src/composables/useNotify';
import VnSmsDialog from 'src/components/common/VnSmsDialog.vue'; import VnSmsDialog from 'src/components/common/VnSmsDialog.vue';
import useOpenURL from 'src/composables/useOpenURL';
const $props = defineProps({ const $props = defineProps({
customer: { customer: {
@ -15,7 +15,6 @@ const $props = defineProps({
required: true, required: true,
}, },
}); });
const { notify } = useNotify(); const { notify } = useNotify();
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
@ -40,9 +39,42 @@ const sendSms = async (payload) => {
notify(error.message, 'positive'); notify(error.message, 'positive');
} }
}; };
const openCreateForm = (type) => {
const query = {
table: {
clientFk: $props.customer.id,
},
createForm: {
addressId: $props.customer.defaultAddressFk,
},
};
const clientFk = {
ticket: 'clientId',
order: 'clientFk',
};
const key = clientFk[type];
if (!key) return;
query.createForm[key] = $props.customer.id;
const params = Object.entries(query)
.map(([key, value]) => `${key}=${JSON.stringify(value)}`)
.join('&');
useOpenURL(`/#/${type}/list?${params}`);
};
</script> </script>
<template> <template>
<QItem v-ripple clickable @click="openCreateForm('ticket')">
<QItemSection>
{{ t('globals.pageTitles.createTicket') }}
</QItemSection>
</QItem>
<QItem v-ripple clickable @click="openCreateForm('order')">
<QItemSection>
{{ t('globals.pageTitles.createOrder') }}
</QItemSection>
</QItem>
<QItem v-ripple clickable> <QItem v-ripple clickable>
<QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection> <QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection>
</QItem> </QItem>

View File

@ -53,7 +53,7 @@ defineProps({
<QItemSection> <QItemSection>
<VnSelect <VnSelect
url="Workers/activeWithInheritedRole" url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }" :where="{ role: 'salesPerson' }"
auto-load auto-load
:label="t('Salesperson')" :label="t('Salesperson')"
v-model="params.salesPersonFk" v-model="params.salesPersonFk"
@ -67,7 +67,6 @@ defineProps({
dense dense
outlined outlined
rounded rounded
:input-debounce="0"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>

View File

@ -116,7 +116,7 @@ function getApiUrl() {
return new URL(window.location).origin; return new URL(window.location).origin;
} }
function getCmrUrl(value) { function getCmrUrl(value) {
return `${getApiUrl()}/api/Routes/${value}/cmr?access_token=${token}`; return `${getApiUrl()}/api/Cmrs/${value}/print?access_token=${token}`;
} }
function downloadPdfs() { function downloadPdfs() {
if (!selectedRows.value.length) { if (!selectedRows.value.length) {
@ -129,7 +129,7 @@ function downloadPdfs() {
let cmrs = []; let cmrs = [];
for (let value of selectedRows.value) cmrs.push(value.cmrFk); for (let value of selectedRows.value) cmrs.push(value.cmrFk);
// prettier-ignore // prettier-ignore
return window.open(`${getApiUrl()}/api/Routes/downloadCmrsZip?ids=${cmrs.join(',')}&access_token=${token}`); return window.open(`${getApiUrl()}/api/Cmrs/downloadZip?ids=${cmrs.join(',')}&access_token=${token}`);
} }
</script> </script>
<template> <template>
@ -149,7 +149,7 @@ function downloadPdfs() {
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="CmrList" data-key="CmrList"
url="Routes/cmrs" url="Cmrs/filter"
:columns="columns" :columns="columns"
:right-search="true" :right-search="true"
default-mode="table" default-mode="table"

View File

@ -45,6 +45,13 @@ const userParams = {
from: null, from: null,
to: null, to: null,
}; };
onMounted(() => {
initializeFromQuery();
stateStore.rightDrawer = true;
if (!route.query.createForm) return;
onClientSelected(JSON.parse(route.query.createForm));
});
// Método para inicializar las variables desde la query string // Método para inicializar las variables desde la query string
const initializeFromQuery = () => { const initializeFromQuery = () => {
const query = route.query.table ? JSON.parse(route.query.table) : {}; const query = route.query.table ? JSON.parse(route.query.table) : {};
@ -301,11 +308,6 @@ const getDateColor = (date) => {
if (comparation < 0) return 'bg-success'; if (comparation < 0) return 'bg-success';
}; };
onMounted(() => {
initializeFromQuery();
stateStore.rightDrawer = true;
});
async function makeInvoice(ticket) { async function makeInvoice(ticket) {
const ticketsIds = ticket.map((item) => item.id); const ticketsIds = ticket.map((item) => item.id);
const { data } = await axios.post(`Tickets/invoiceTicketsAndPdf`, { ticketsIds }); const { data } = await axios.post(`Tickets/invoiceTicketsAndPdf`, { ticketsIds });

View File

@ -19,7 +19,7 @@ const router = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
const thermographPaginateRef = ref(null); const thermographPaginateRef = ref();
const warehouses = ref([]); const warehouses = ref([]);
const thermographFilter = { const thermographFilter = {
@ -145,7 +145,6 @@ const removeThermograph = async (id) => {
data-key="TravelThermographs" data-key="TravelThermographs"
url="TravelThermographs" url="TravelThermographs"
:filter="thermographFilter" :filter="thermographFilter"
:params="{ travelFk: id }"
auto-load auto-load
> >
<template #body="{ rows }"> <template #body="{ rows }">