diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 100% rename from .eslintrc.js rename to .eslintrc.cjs diff --git a/package.json b/package.json index 4668d2d56..71d80d401 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "24.28.1", + "version": "24.30.1", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", diff --git a/public/no-image-dark.png b/public/no-image-dark.png new file mode 100644 index 000000000..2a20d7eb4 Binary files /dev/null and b/public/no-image-dark.png differ diff --git a/public/no-image.png b/public/no-image.png new file mode 100644 index 000000000..11d5317a9 Binary files /dev/null and b/public/no-image.png differ diff --git a/public/no-user.png b/public/no-user.png new file mode 100644 index 000000000..e090bc2eb Binary files /dev/null and b/public/no-user.png differ diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index d5d8e2a91..fa7761c5f 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -10,6 +10,7 @@ import VnPaginate from 'components/ui/VnPaginate.vue'; import VnConfirm from 'components/ui/VnConfirm.vue'; import SkeletonTable from 'components/ui/SkeletonTable.vue'; import { tMobile } from 'src/composables/tMobile'; +import VnSubToolbar from './ui/VnSubToolbar.vue'; const { push } = useRouter(); const quasar = useQuasar(); @@ -67,7 +68,7 @@ const $props = defineProps({ default: '', description: 'It is used for redirect on click "save and continue"', }, - hasSubtoolbar: { + hasSubToolbar: { type: Boolean, default: true, }, @@ -313,8 +314,11 @@ watch(formUrl, async () => { ></slot> </template> </VnPaginate> - <SkeletonTable v-if="!formData" :columns="$attrs.columns?.length" /> - <Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown() && hasSubtoolbar"> + <SkeletonTable + v-if="!formData && $attrs.autoLoad" + :columns="$attrs.columns?.length" + /> + <Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown() && hasSubToolbar"> <QBtnGroup push style="column-gap: 10px"> <slot name="moreBeforeActions" /> <QBtn diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue index f10dee61a..becc1c174 100644 --- a/src/components/FormModel.vue +++ b/src/components/FormModel.vue @@ -90,7 +90,7 @@ const $props = defineProps({ }); const emit = defineEmits(['onFetch', 'onDataSaved']); const modelValue = computed( - () => $props.model ?? `formModel_${route?.meta?.title ?? route.name}` + () => $props.model ?? `formModel_${route?.meta?.title ?? route.name}`, ).value; const componentIsRendered = ref(false); const arrayData = useArrayData(modelValue); @@ -137,7 +137,7 @@ onMounted(async () => { JSON.stringify(newVal) !== JSON.stringify(originalData.value); isResetting.value = false; }, - { deep: true } + { deep: true }, ); } }); @@ -145,7 +145,7 @@ onMounted(async () => { if (!$props.url) watch( () => arrayData.store.data, - (val) => updateAndEmit('onFetch', val) + (val) => updateAndEmit('onFetch', val), ); watch(formUrl, async () => { @@ -206,11 +206,11 @@ async function save() { updateAndEmit('onDataSaved', formData.value, response?.data); if ($props.reload) await arrayData.fetch({}); + hasChanges.value = false; } catch (err) { console.error(err); notify('errors.writeRequest', 'negative'); } finally { - hasChanges.value = false; isLoading.value = false; } } @@ -239,7 +239,7 @@ function filter(value, update, filterOptions) { (ref) => { ref.setOptionIndex(-1); ref.moveOptionSelection(1, true); - } + }, ); } @@ -262,7 +262,7 @@ defineExpose({ <template> <div class="column items-center full-width"> <QForm - v-if="formData" + @submit="save" @reset="reset" class="q-pa-md" @@ -270,11 +270,13 @@ defineExpose({ > <QCard> <slot + v-if="formData" name="form" :data="formData" :validate="validate" :filter="filter" /> + <SkeletonForm v-else/> </QCard> </QForm> </div> @@ -337,7 +339,7 @@ defineExpose({ </QBtnGroup> </div> </Teleport> - <SkeletonForm v-if="!formData" /> + <QInnerLoading :showing="isLoading" :label="t('globals.pleaseWait')" diff --git a/src/components/LeftMenu.vue b/src/components/LeftMenu.vue index 3cccd0d2f..213c08d7e 100644 --- a/src/components/LeftMenu.vue +++ b/src/components/LeftMenu.vue @@ -58,6 +58,7 @@ function addChildren(module, route, parent) { } const items = ref([]); + function getRoutes() { if (props.source === 'main') { const modules = Object.assign([], navigation.getModules().value); @@ -66,9 +67,8 @@ function getRoutes() { const moduleDef = routes.find( (route) => toLowerCamel(route.name) === item.module ); - item.children = []; - if (!moduleDef) continue; + item.children = []; addChildren(item.module, moduleDef, item.children); } diff --git a/src/components/LeftMenuItem.vue b/src/components/LeftMenuItem.vue index d12fb8428..f3f2315a3 100644 --- a/src/components/LeftMenuItem.vue +++ b/src/components/LeftMenuItem.vue @@ -21,7 +21,7 @@ const itemComputed = computed(() => { </script> <template> <QItem - active-class="bg-hover" + active-class="bg-vn-hover" class="min-height" :to="{ name: itemComputed.name }" clickable diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index c6722d875..3051e8b99 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -1,21 +1,19 @@ <script setup> import { onMounted, ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useSession } from 'src/composables/useSession'; import { useState } from 'src/composables/useState'; import { useStateStore } from 'stores/useStateStore'; import { useQuasar } from 'quasar'; import PinnedModules from './PinnedModules.vue'; import UserPanel from 'components/UserPanel.vue'; import VnBreadcrumbs from './common/VnBreadcrumbs.vue'; +import VnImg from 'src/components/ui/VnImg.vue'; const { t } = useI18n(); const stateStore = useStateStore(); const quasar = useQuasar(); const state = useState(); const user = state.getUser(); -const { getTokenMultimedia } = useSession(); -const token = getTokenMultimedia(); const appName = 'Lilium'; onMounted(() => stateStore.setMounted()); @@ -83,11 +81,12 @@ const pinnedModulesRef = ref(); id="user" > <QAvatar size="lg"> - <QImg - :src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`" - spinner-color="primary" - > - </QImg> + <VnImg + :id="user.id" + collection="user" + size="160x160" + :zoom-size="null" + /> </QAvatar> <QTooltip bottom> {{ t('globals.userPanel') }} diff --git a/src/components/TransferInvoiceForm.vue b/src/components/TransferInvoiceForm.vue index 1af23583d..0a8c1272e 100644 --- a/src/components/TransferInvoiceForm.vue +++ b/src/components/TransferInvoiceForm.vue @@ -70,14 +70,11 @@ const makeInvoice = async () => { }); }); if (!response) { - console.log('entra cuando no checkbox'); return; } } - console.log('params: ', params); const { data } = await axios.post('InvoiceOuts/transferInvoice', params); - console.log('data: ', data); notify(t('Transferred invoice'), 'positive'); const id = data?.[0]; if (id) router.push({ name: 'InvoiceOutSummary', params: { id } }); diff --git a/src/components/UserPanel.vue b/src/components/UserPanel.vue index 9c3b456b1..589524258 100644 --- a/src/components/UserPanel.vue +++ b/src/components/UserPanel.vue @@ -12,12 +12,14 @@ import VnRow from 'components/ui/VnRow.vue'; import FetchData from 'components/FetchData.vue'; import { useClipboard } from 'src/composables/useClipboard'; import VnImg from 'src/components/ui/VnImg.vue'; +import { useRole } from 'src/composables/useRole'; const state = useState(); const session = useSession(); const router = useRouter(); const { t, locale } = useI18n(); const { copyText } = useClipboard(); + const userLocale = computed({ get() { return locale.value; @@ -99,6 +101,7 @@ function saveUserData(param, value) { axios.post('UserConfigs/setUserConfig', { [param]: value }); localUserData(); } +const isEmployee = computed(() => useRole().isEmployee()); </script> <template> @@ -109,12 +112,14 @@ function saveUserData(param, value) { auto-load /> <FetchData + v-if="isEmployee" url="Companies" order="name" @on-fetch="(data) => (companiesData = data)" auto-load /> <FetchData + v-if="isEmployee" url="Accountings" order="name" @on-fetch="(data) => (accountBankData = data)" diff --git a/src/components/VnTable/VnColumn.vue b/src/components/VnTable/VnColumn.vue index 6cd62d83e..aeccdb2d6 100644 --- a/src/components/VnTable/VnColumn.vue +++ b/src/components/VnTable/VnColumn.vue @@ -46,6 +46,7 @@ const defaultComponents = { component: markRaw(VnInput), attrs: { disable: !$props.isEditable, + class: 'fit', }, forceAttrs: { label: $props.showLabel && $props.column.label, @@ -55,6 +56,7 @@ const defaultComponents = { component: markRaw(VnInput), attrs: { disable: !$props.isEditable, + class: 'fit', }, forceAttrs: { label: $props.showLabel && $props.column.label, @@ -66,6 +68,7 @@ const defaultComponents = { readonly: true, disable: !$props.isEditable, style: 'min-width: 125px', + class: 'fit', }, forceAttrs: { label: $props.showLabel && $props.column.label, @@ -77,7 +80,7 @@ const defaultComponents = { const defaultAttrs = { disable: !$props.isEditable, 'model-value': Boolean(prop), - class: 'no-padding', + class: 'no-padding fit', }; if (typeof prop == 'number') { @@ -94,6 +97,7 @@ const defaultComponents = { component: markRaw(VnSelect), attrs: { disable: !$props.isEditable, + class: 'fit', }, forceAttrs: { label: $props.showLabel && $props.column.label, @@ -134,7 +138,7 @@ const col = computed(() => { const components = computed(() => $props.components ?? defaultComponents); </script> <template> - <div class="row no-wrap fit"> + <div class="row no-wrap"> <VnComponent v-if="col.before" :prop="col.before" diff --git a/src/components/VnTable/VnFilter.vue b/src/components/VnTable/VnFilter.vue index 3d489cf73..b3386899f 100644 --- a/src/components/VnTable/VnFilter.vue +++ b/src/components/VnTable/VnFilter.vue @@ -7,6 +7,7 @@ import { useArrayData } from 'composables/useArrayData'; import VnSelect from 'components/common/VnSelect.vue'; import VnInput from 'components/common/VnInput.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; +import VnInputTime from 'components/common/VnInputTime.vue'; import VnTableColumn from 'components/VnTable/VnColumn.vue'; const $props = defineProps({ @@ -39,7 +40,7 @@ const enterEvent = { const defaultAttrs = { filled: !$props.showTitle, - class: 'q-px-sm q-pb-xs q-pt-none', + class: 'q-px-sm q-pb-xs q-pt-none fit', dense: true, }; @@ -75,12 +76,23 @@ const components = { }, forceAttrs, }, + time: { + component: markRaw(VnInputTime), + event: updateEvent, + attrs: { + ...defaultAttrs, + disable: !$props.isEditable, + }, + forceAttrs: { + label: $props.showLabel && $props.column.label, + }, + }, checkbox: { component: markRaw(QCheckbox), event: updateEvent, attrs: { dense: true, - 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 fit', 'toggle-indeterminate': true, }, forceAttrs, @@ -89,7 +101,7 @@ const components = { component: markRaw(VnSelect), event: updateEvent, attrs: { - class: 'q-px-md q-pb-xs q-pt-none', + class: 'q-px-md q-pb-xs q-pt-none fit', dense: true, filled: !$props.showTitle, }, diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index cb691de69..13b12a05b 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -59,6 +59,14 @@ const $props = defineProps({ type: Boolean, default: false, }, + hasSubToolbar: { + type: Boolean, + default: true, + }, + disableOption: { + type: Object, + default: () => ({ card: false, table: false }), + }, }); const { t } = useI18n(); const stateStore = useStateStore(); @@ -81,11 +89,13 @@ const tableModes = [ icon: 'view_column', title: t('table view'), value: TABLE_MODE, + disable: $props.disableOption?.table, }, { icon: 'grid_view', title: t('grid view'), value: DEFAULT_MODE, + disable: $props.disableOption?.card, }, ]; @@ -176,11 +186,14 @@ function columnName(col) { } function getColAlign(col) { - return 'text-' + (col.align ?? 'left') + return 'text-' + (col.align ?? 'left'); } + +const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']); defineExpose({ reload, redirect: redirectFn, + selected, }); </script> <template> @@ -235,11 +248,18 @@ defineExpose({ :search-url="searchUrl" :disable-infinite-scroll="mode == TABLE_MODE" @save-changes="reload" - :has-subtoolbar="isEditable" + :has-sub-toolbar="$attrs['hasSubToolbar'] ?? isEditable" > + <template + v-for="(_, slotName) in $slots" + #[slotName]="slotData" + :key="slotName" + > + <slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" /> + </template> <template #body="{ rows }"> <QTable - v-bind="$attrs['QTable']" + v-bind="$attrs['q-table']" class="vnTable" :columns="splittedColumns.columns" :rows="rows" @@ -256,6 +276,7 @@ defineExpose({ CrudModelRef.vnPaginateRef.paginate() " @row-click="(_, row) => rowClickFunction(row)" + @update:selected="emit('update:selected', $event)" > <template #top-left> <slot name="top-left"></slot> @@ -320,13 +341,15 @@ defineExpose({ class="no-margin q-px-xs" :class="getColAlign(col)" > - <VnTableColumn - :column="col" - :row="row" - :is-editable="false" - v-model="row[col.name]" - component-prop="columnField" - /> + <slot :name="`column-${col.name}`" :col="col" :row="row"> + <VnTableColumn + :column="col" + :row="row" + :is-editable="false" + v-model="row[col.name]" + component-prop="columnField" + /> + </slot> </QTd> </template> <template #body-cell-tableActions="{ col, row }"> @@ -411,9 +434,9 @@ defineExpose({ > <VnLv :label=" - !col.component && - col.label && - `${col.label}:` + !col.component && col.label + ? `${col.label}:` + : '' " > <template #value> @@ -422,14 +445,20 @@ defineExpose({ stopEventPropagation($event) " > - <VnTableColumn - :column="col" + <slot + :name="`column-${col.name}`" + :col="col" :row="row" - :is-editable="false" - v-model="row[col.name]" - component-prop="columnField" - :show-label="true" - /> + > + <VnTableColumn + :column="col" + :row="row" + :is-editable="false" + v-model="row[col.name]" + component-prop="columnField" + :show-label="true" + /> + </slot> </span> </template> </VnLv> @@ -487,6 +516,7 @@ defineExpose({ default="input" v-model="data[column.name]" :show-label="true" + component-prop="columnCreate" /> <slot name="more-create-dialog" :data="data" /> </div> diff --git a/src/components/common/VnCard.vue b/src/components/common/VnCard.vue index 8517525df..17fa74317 100644 --- a/src/components/common/VnCard.vue +++ b/src/components/common/VnCard.vue @@ -1,6 +1,6 @@ <script setup> import { onBeforeMount, computed } from 'vue'; -import { useRoute } from 'vue-router'; +import { useRoute, onBeforeRouteUpdate } from 'vue-router'; import { useArrayData } from 'src/composables/useArrayData'; import { useStateStore } from 'stores/useStateStore'; import useCardSize from 'src/composables/useCardSize'; @@ -39,8 +39,17 @@ const arrayData = useArrayData(props.dataKey, { onBeforeMount(async () => { if (!props.baseUrl) arrayData.store.filter.where = { id: route.params.id }; - await arrayData.fetch({ append: false }); + await arrayData.fetch({ append: false, updateRouter: false }); }); + +if (props.baseUrl) { + onBeforeRouteUpdate(async (to, from) => { + if (to.params.id !== from.params.id) { + arrayData.store.url = `${props.baseUrl}/${to.params.id}`; + await arrayData.fetch({ append: false, updateRouter: false }); + } + }); +} </script> <template> <QDrawer diff --git a/src/components/common/VnComponent.vue b/src/components/common/VnComponent.vue index 318b5ee5f..d7719034a 100644 --- a/src/components/common/VnComponent.vue +++ b/src/components/common/VnComponent.vue @@ -12,7 +12,7 @@ const $props = defineProps({ default: () => {}, }, value: { - type: [Object, Number, String], + type: [Object, Number, String, Boolean], default: () => {}, }, }); @@ -54,7 +54,6 @@ function toValueAttrs(attrs) { v-bind="mix(toComponent).attrs" v-on="mix(toComponent).event ?? {}" v-model="model" - class="fit" /> </span> </template> diff --git a/src/components/common/VnInput.vue b/src/components/common/VnInput.vue index 4cd964012..33b97e29d 100644 --- a/src/components/common/VnInput.vue +++ b/src/components/common/VnInput.vue @@ -93,7 +93,12 @@ const inputRules = [ name="close" size="xs" v-if="hover && value && !$attrs.disabled && $props.clearable" - @click="value = null" + @click=" + () => { + value = null; + emit('remove'); + } + " ></QIcon> <QIcon v-if="info" name="info"> <QTooltip max-width="350px"> diff --git a/src/components/common/VnInputDate.vue b/src/components/common/VnInputDate.vue index 77ab2692d..184f8a158 100644 --- a/src/components/common/VnInputDate.vue +++ b/src/components/common/VnInputDate.vue @@ -1,84 +1,31 @@ <script setup> -import { computed, ref } from 'vue'; +import { onMounted, watch, computed, ref } from 'vue'; +import { date } from 'quasar'; import { useI18n } from 'vue-i18n'; -import isValidDate from 'filters/isValidDate'; -const props = defineProps({ - modelValue: { - type: String, - default: null, - }, - readonly: { - type: Boolean, - default: false, - }, +const model = defineModel({ type: String }); +const $props = defineProps({ isOutlined: { type: Boolean, default: false, }, - emitDateFormat: { - type: Boolean, - default: false, - }, }); -const hover = ref(false); - -const emit = defineEmits(['update:modelValue']); const { t } = useI18n(); const requiredFieldRule = (val) => !!val || t('globals.fieldRequired'); -const joinDateAndTime = (date, time) => { - if (!date) { - return null; - } - if (!time) { - return new Date(date).toISOString(); - } - const [year, month, day] = date.split('/'); - return new Date(`${year}-${month}-${day}T${time}`).toISOString(); -}; +const dateFormat = 'DD/MM/YYYY'; +const isPopupOpen = ref(); +const hover = ref(); +const mask = ref(); -const time = computed(() => (props.modelValue ? props.modelValue.split('T')?.[1] : null)); -const value = computed({ - get() { - return props.modelValue; - }, - set(value) { - emit( - 'update:modelValue', - props.emitDateFormat ? new Date(value) : joinDateAndTime(value, time.value) - ); - }, +onMounted(() => { + // fix quasar bug + mask.value = '##/##/####'; }); -const isPopupOpen = ref(false); - -const onDateUpdate = (date) => { - value.value = date; - isPopupOpen.value = false; -}; - -const padDate = (value) => value.toString().padStart(2, '0'); -const formatDate = (dateString) => { - const date = new Date(dateString || ''); - return `${date.getFullYear()}/${padDate(date.getMonth() + 1)}/${padDate( - date.getDate() - )}`; -}; -const displayDate = (dateString) => { - if (!dateString || !isValidDate(dateString)) { - return ''; - } - return new Date(dateString).toLocaleDateString([], { - year: 'numeric', - month: '2-digit', - day: '2-digit', - }); -}; - const styleAttrs = computed(() => { - return props.isOutlined + return $props.isOutlined ? { dense: true, outlined: true, @@ -86,42 +33,101 @@ const styleAttrs = computed(() => { } : {}; }); + +const formattedDate = computed({ + get() { + if (!model.value) return model.value; + return date.formatDate(new Date(model.value), dateFormat); + }, + set(value) { + if (value == model.value) return; + let newDate; + if (value) { + // parse input + if (value.includes('/') && value.length >= 10) { + if (value.at(2) == '/') value = value.split('/').reverse().join('/'); + value = date.formatDate( + new Date(value).toISOString(), + 'YYYY-MM-DDTHH:mm:ss.SSSZ' + ); + } + let ymd = value.split('-').map((e) => parseInt(e)); + newDate = new Date(ymd[0], ymd[1] - 1, ymd[2]); + if (model.value) { + const orgDate = + model.value instanceof Date ? model.value : new Date(model.value); + + newDate.setHours( + orgDate.getHours(), + orgDate.getMinutes(), + orgDate.getSeconds(), + orgDate.getMilliseconds() + ); + } + } + if (!isNaN(newDate)) model.value = newDate.toISOString(); + }, +}); + +const popupDate = computed(() => + model.value ? date.formatDate(new Date(model.value), 'YYYY/MM/DD') : model.value +); + +watch( + () => model.value, + (val) => (formattedDate.value = val), + { immediate: true } +); </script> <template> <div @mouseover="hover = true" @mouseleave="hover = false"> <QInput + v-model="formattedDate" class="vn-input-date" - readonly - :model-value="displayDate(value)" + :mask="mask" + placeholder="dd/mm/aaaa" v-bind="{ ...$attrs, ...styleAttrs }" :class="{ required: $attrs.required }" :rules="$attrs.required ? [requiredFieldRule] : null" - @click="isPopupOpen = true" + :clearable="false" > <template #append> <QIcon name="close" size="xs" - v-if="hover && value && !readonly" - @click="onDateUpdate(null)" - ></QIcon> - <QIcon name="event" class="cursor-pointer"> - <QPopupProxy - v-model="isPopupOpen" - cover - transition-show="scale" - transition-hide="scale" - :no-parent-event="props.readonly" - > - <QDate - :today-btn="true" - :model-value="formatDate(value)" - @update:model-value="onDateUpdate" - /> - </QPopupProxy> - </QIcon> + v-if=" + ($attrs.clearable == undefined || $attrs.clearable) && + hover && + model && + !$attrs.disable + " + @click=" + model = null; + isPopupOpen = false; + " + /> + <QIcon name="event" class="cursor-pointer" /> </template> + <QMenu + transition-show="scale" + transition-hide="scale" + v-model="isPopupOpen" + anchor="bottom left" + self="top start" + :no-focus="true" + > + <QDate + v-model="popupDate" + :today-btn="true" + @update:model-value=" + (date) => { + formattedDate = date; + isPopupOpen = false; + } + " + /> + </QMenu> </QInput> </div> </template> diff --git a/src/components/common/VnInputTime.vue b/src/components/common/VnInputTime.vue index 5c84951cb..9344ff869 100644 --- a/src/components/common/VnInputTime.vue +++ b/src/components/common/VnInputTime.vue @@ -1,14 +1,11 @@ <script setup> -import { computed, ref } from 'vue'; +import { watch, computed, ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import isValidDate from 'filters/isValidDate'; +import { date } from 'quasar'; +const model = defineModel({ type: String }); const props = defineProps({ - modelValue: { - type: String, - default: null, - }, - readonly: { + timeOnly: { type: Boolean, default: false, }, @@ -17,43 +14,12 @@ const props = defineProps({ default: false, }, }); -const emit = defineEmits(['update:modelValue']); const { t } = useI18n(); const requiredFieldRule = (val) => !!val || t('globals.fieldRequired'); -const value = computed({ - get() { - return props.modelValue; - }, - set(value) { - const [hours, minutes] = value.split(':'); - const date = new Date(props.modelValue); - date.setHours(Number.parseInt(hours) || 0, Number.parseInt(minutes) || 0, 0, 0); - emit('update:modelValue', value ? date.toISOString() : null); - }, -}); - -const isPopupOpen = ref(false); -const onDateUpdate = (date) => { - internalValue.value = date; -}; - -const save = () => { - value.value = internalValue.value; -}; -const formatTime = (dateString) => { - if (!dateString || !isValidDate(dateString)) { - return ''; - } - - const date = new Date(dateString || ''); - return date.toLocaleTimeString([], { - hour: '2-digit', - minute: '2-digit', - }); -}; - -const internalValue = ref(formatTime(value)); +const dateFormat = 'HH:mm'; +const isPopupOpen = ref(); +const hover = ref(); const styleAttrs = computed(() => { return props.isOutlined @@ -64,54 +30,87 @@ const styleAttrs = computed(() => { } : {}; }); + +const formattedTime = computed({ + get() { + if (!model.value || model.value?.length <= 5) return model.value; + return dateToTime(model.value); + }, + set(value) { + if (value == model.value) return; + let time = value; + if (time) { + if (time?.length > 5) time = dateToTime(time); + if (!props.timeOnly) { + const hours = time.split(':'); + const date = new Date(); + date.setHours(hours[0], hours[1], 0); + time = date.toISOString(); + } + } + model.value = time; + }, +}); + +function dateToTime(newDate) { + return date.formatDate(new Date(newDate), dateFormat); +} + +watch( + () => model.value, + (val) => (formattedTime.value = val), + { immediate: true } +); </script> <template> - <QInput - class="vn-input-time" - readonly - :model-value="formatTime(value)" - v-bind="{ ...$attrs, ...styleAttrs }" - :class="{ required: $attrs.required }" - :rules="$attrs.required ? [requiredFieldRule] : null" - @click="isPopupOpen = true" - > - <template #append> - <QIcon name="Schedule" class="cursor-pointer"> - <QPopupProxy - v-model="isPopupOpen" - cover - transition-show="scale" - transition-hide="scale" - :no-parent-event="props.readonly" - > - <QTime - :format24h="false" - :model-value="formatTime(value)" - @update:model-value="onDateUpdate" - > - <div class="row items-center justify-end q-gutter-sm"> - <QBtn - :label="t('Cancel')" - color="primary" - flat - v-close-popup - /> - <QBtn - label="Ok" - color="primary" - flat - @click="save" - v-close-popup - /> - </div> - </QTime> - </QPopupProxy> - </QIcon> - </template> - </QInput> + <div @mouseover="hover = true" @mouseleave="hover = false"> + <QInput + class="vn-input-time" + mask="##:##" + placeholder="--:--" + v-model="formattedTime" + v-bind="{ ...$attrs, ...styleAttrs }" + :class="{ required: $attrs.required }" + style="min-width: 100px" + :rules="$attrs.required ? [requiredFieldRule] : null" + > + <template #append> + <QIcon + name="close" + size="xs" + v-if=" + ($attrs.clearable == undefined || $attrs.clearable) && + hover && + model && + !$attrs.disable + " + @click=" + model = null; + isPopupOpen = false; + " + /> + <QIcon name="Schedule" class="cursor-pointer" /> + </template> + <QMenu + transition-show="scale" + transition-hide="scale" + v-model="isPopupOpen" + anchor="bottom left" + self="top start" + :no-focus="true" + > + <QTime + :format24h="false" + v-model="formattedTime" + mask="HH:mm" + landscape + now-btn + /> + </QMenu> + </QInput> + </div> </template> - <style lang="scss"> .vn-input-time.q-field--standard.q-field--readonly .q-field__control:before { border-bottom-style: solid; @@ -121,8 +120,3 @@ const styleAttrs = computed(() => { border-style: solid; } </style> - -<i18n> -es: - Cancel: Cancelar -</i18n> diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 61436b7e8..19765b1f7 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -49,6 +49,7 @@ const filter = { 'changedModelId', 'changedModelValue', 'description', + 'summaryId', ], include: [ { @@ -459,12 +460,12 @@ onUnmounted(() => { :style="{ backgroundColor: useColor(modelLog.model), }" - :title="modelLog.model" + :title="`${modelLog.model} #${modelLog.id}`" > {{ t(modelLog.modelI18n) }} </QChip> - <span class="model-id" v-if="modelLog.id" - >#{{ modelLog.id }}</span + <span class="model-id" v-if="modelLog.summaryId" + >#{{ modelLog.summaryId }}</span > <span class="model-value" :title="modelLog.showValue"> {{ modelLog.showValue }} diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 52cb68438..3e5cd4216 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -61,6 +61,10 @@ const $props = defineProps({ type: Boolean, default: false, }, + useLike: { + type: Boolean, + default: true, + }, }); const { t } = useI18n(); @@ -114,11 +118,14 @@ async function fetchFilter(val) { if (!$props.url || !dataRef.value) return; const { fields, sortBy, limit } = $props; - let key = optionLabel.value; + let key = optionFilter.value ?? optionLabel.value; - if (new RegExp(/\d/g).test(val)) key = optionFilter.value ?? optionValue.value; + if (new RegExp(/\d/g).test(val)) key = optionValue.value; - const where = { ...{ [key]: { like: `%${val}%` } }, ...$props.where }; + const defaultWhere = $props.useLike + ? { [key]: { like: `%${val}%` } } + : { [key]: val }; + const where = { ...defaultWhere, ...$props.where }; const fetchOptions = { where, order: sortBy, limit }; if (fields) fetchOptions.fields = fields; return dataRef.value.fetch(fetchOptions); diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index 8bb2a603e..2bb5234ad 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -46,7 +46,7 @@ let arrayData; let store; let entity; const isLoading = ref(false); - +const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName); defineExpose({ getData }); onBeforeMount(async () => { @@ -58,10 +58,12 @@ onBeforeMount(async () => { store = arrayData.store; entity = computed(() => (Array.isArray(store.data) ? store.data[0] : store.data)); // It enables to load data only once if the module is the same as the dataKey - if ($props.dataKey !== route.meta.moduleName || !route.params.id) await getData(); + if (!isSameDataKey.value || !route.params.id) await getData(); watch( () => [$props.url, $props.filter], - async () => await getData() + async () => { + if (!isSameDataKey.value) await getData(); + } ); }); @@ -77,14 +79,50 @@ async function getData() { isLoading.value = false; } } + +function getValueFromPath(path) { + if (!path) return; + const keys = path.toString().split('.'); + let current = entity.value; + + for (let i = 0; i < keys.length; i++) { + if (current[keys[i]] === undefined) return undefined; + else current = current[keys[i]]; + } + return current; +} + const emit = defineEmits(['onFetch']); + +const iconModule = computed(() => route.matched[1].meta.icon); +const toModule = computed(() => + route.matched[1].path.split('/').length > 2 + ? route.matched[1].redirect + : route.matched[1].children[0].redirect +); </script> <template> <div class="descriptor"> <template v-if="entity && !isLoading"> <div class="header bg-primary q-pa-sm justify-between"> - <slot name="header-extra-action" /> + <slot name="header-extra-action" + ><QBtn + round + flat + dense + size="md" + :icon="iconModule" + color="white" + class="link" + :to="toModule" + > + <QTooltip> + {{ t('globals.goToModuleIndex') }} + </QTooltip> + </QBtn></slot + > + <QBtn @click.stop="viewSummary(entity.id, $props.summary)" round @@ -139,8 +177,8 @@ const emit = defineEmits(['onFetch']); <QList dense> <QItemLabel header class="ellipsis text-h5" :lines="1"> <div class="title"> - <span v-if="$props.title" :title="$props.title"> - {{ entity[title] ?? $props.title }} + <span v-if="$props.title" :title="getValueFromPath(title)"> + {{ getValueFromPath(title) ?? $props.title }} </span> <slot v-else name="description" :entity="entity"> <span :title="entity.name"> @@ -151,7 +189,7 @@ const emit = defineEmits(['onFetch']); </QItemLabel> <QItem dense> <QItemLabel class="subtitle" caption> - #{{ $props.subtitle ?? entity.id }} + #{{ getValueFromPath(subtitle) ?? entity.id }} </QItemLabel> </QItem> </QList> diff --git a/src/components/ui/CardSummary.vue b/src/components/ui/CardSummary.vue index 253683889..11dcbee3b 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -187,15 +187,10 @@ function existSummary(routes) { color: lighten($primary, 20%); } .q-checkbox { - display: flex; - margin-bottom: 9px; & .q-checkbox__label { - margin-left: 31px; color: var(--vn-text-color); } & .q-checkbox__inner { - position: absolute; - left: 0; color: var(--vn-label-color); } } diff --git a/src/components/ui/SkeletonForm.vue b/src/components/ui/SkeletonForm.vue index 4ae4fb5bb..8d3a4b5bf 100644 --- a/src/components/ui/SkeletonForm.vue +++ b/src/components/ui/SkeletonForm.vue @@ -1,20 +1,14 @@ <template> - <div class="q-pa-md"> - <div class="row q-gutter-md q-mb-md"> - <QSkeleton type="QInput" square /> - <QSkeleton type="QInput" square /> - </div> - <div class="row q-gutter-md q-mb-md"> - <QSkeleton type="QInput" square /> - <QSkeleton type="QInput" square /> - </div> - <div class="row q-gutter-md q-mb-md"> - <QSkeleton type="QInput" square /> - <QSkeleton type="QInput" square /> - </div> - <div class="row q-gutter-md"> - <QSkeleton type="QBtn" /> - <QSkeleton type="QBtn" /> - </div> + <div class="row q-gutter-md q-mb-md"> + <QSkeleton type="QInput" class="col" square /> + <QSkeleton type="QInput" class="col" square /> </div> -</template> + <div class="row q-gutter-md q-mb-md"> + <QSkeleton type="QInput" class="col" square /> + <QSkeleton type="QInput" class="col" square /> + </div> + <div class="row q-gutter-md q-mb-md"> + <QSkeleton type="QInput" class="col" square /> + <QSkeleton type="QInput" class="col" square /> + </div> +</template> \ No newline at end of file diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index f6241ffe8..9b2345825 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -7,8 +7,11 @@ import toDate from 'filters/toDate'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue'; const { t } = useI18n(); -const params = defineModel({ default: {}, required: true, type: Object }); const $props = defineProps({ + modelValue: { + type: Object, + default: () => {}, + }, dataKey: { type: String, required: true, @@ -56,7 +59,14 @@ const $props = defineProps({ }); defineExpose({ search }); -const emit = defineEmits(['refresh', 'clear', 'search', 'init', 'remove']); +const emit = defineEmits([ + 'update:modelValue', + 'refresh', + 'clear', + 'search', + 'init', + 'remove', +]); const arrayData = useArrayData($props.dataKey, { exprBuilder: $props.exprBuilder, @@ -65,9 +75,10 @@ const arrayData = useArrayData($props.dataKey, { }); const route = useRoute(); const store = arrayData.store; - +const userParams = ref({}); onMounted(() => { - emit('init', { params: params.value }); + userParams.value = $props.modelValue ?? {}; + emit('init', { params: userParams.value }); }); function setUserParams(watchedParams) { @@ -76,7 +87,7 @@ function setUserParams(watchedParams) { if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams); watchedParams = { ...watchedParams, ...watchedParams.filter?.where }; delete watchedParams.filter; - params.value = { ...params.value, ...watchedParams }; + userParams.value = { ...userParams.value, ...watchedParams }; } watch( @@ -89,18 +100,22 @@ watch( (val) => setUserParams(val) ); +watch( + () => $props.modelValue, + (val) => (userParams.value = val ?? {}) +); + const isLoading = ref(false); async function search(evt) { if (evt && $props.disableSubmitEvent) return; store.filter.where = {}; isLoading.value = true; - const filter = { ...params.value }; + const filter = { ...userParams.value }; store.userParamsChanged = true; - store.filter.skip = 0; - store.skip = 0; - const { params: newParams } = await arrayData.addFilter({ params: params.value }); - params.value = newParams; + arrayData.reset(['skip', 'filter.skip', 'page']); + const { params: newParams } = await arrayData.addFilter({ params: userParams.value }); + userParams.value = newParams; if (!$props.showAll && !Object.values(filter).length) store.data = []; @@ -110,8 +125,9 @@ async function search(evt) { async function reload() { isLoading.value = true; - const params = Object.values(params.value).filter((param) => param); - + const params = Object.values(userParams.value).filter((param) => param); + store.skip = 0; + store.page = 1; await arrayData.fetch({ append: false }); if (!$props.showAll && !params.length) store.data = []; isLoading.value = false; @@ -121,20 +137,19 @@ async function reload() { async function clearFilters() { isLoading.value = true; store.userParamsChanged = true; - store.filter.skip = 0; - store.skip = 0; + arrayData.reset(['skip', 'filter.skip', 'page']); // Filtrar los params no removibles - const removableFilters = Object.keys(params.value).filter((param) => + const removableFilters = Object.keys(userParams.value).filter((param) => $props.unremovableParams.includes(param) ); const newParams = {}; // Conservar solo los params que no son removibles for (const key of removableFilters) { - newParams[key] = params.value[key]; + newParams[key] = userParams.value[key]; } - params.value = {}; - params.value = { ...newParams }; // Actualizar los params con los removibles - await arrayData.applyFilter({ params: params.value }); + userParams.value = {}; + userParams.value = { ...newParams }; // Actualizar los params con los removibles + await arrayData.applyFilter({ params: userParams.value }); if (!$props.showAll) { store.data = []; @@ -142,12 +157,13 @@ async function clearFilters() { isLoading.value = false; emit('clear'); + emit('update:modelValue', userParams.value); } const tagsList = computed(() => { const tagList = []; - for (const key of Object.keys(params.value)) { - const value = params.value[key]; + for (const key of Object.keys(userParams.value)) { + const value = userParams.value[key]; if (value == null || ($props.hiddenTags || []).includes(key)) continue; tagList.push({ label: key, value }); } @@ -162,9 +178,10 @@ const customTags = computed(() => ); async function remove(key) { - params.value[key] = undefined; + userParams.value[key] = undefined; search(); emit('remove', key); + emit('update:modelValue', userParams.value); } function formatValue(value) { @@ -237,7 +254,7 @@ function formatValue(value) { <slot v-if="$slots.customTags" name="customTags" - :params="params" + :params="userParams" :tags="customTags" :format-fn="formatValue" :search-fn="search" @@ -247,7 +264,7 @@ function formatValue(value) { <QSeparator /> </QList> <QList dense class="list q-gutter-y-sm q-mt-sm"> - <slot name="body" :params="params" :search-fn="search"></slot> + <slot name="body" :params="userParams" :search-fn="search"></slot> </QList> </QForm> <QInnerLoading diff --git a/src/components/ui/VnImg.vue b/src/components/ui/VnImg.vue index 37c1edefc..8d747963b 100644 --- a/src/components/ui/VnImg.vue +++ b/src/components/ui/VnImg.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed, onMounted } from 'vue'; +import { ref, computed } from 'vue'; import { useSession } from 'src/composables/useSession'; const $props = defineProps({ @@ -28,21 +28,29 @@ const $props = defineProps({ const show = ref(false); const token = useSession().getTokenMultimedia(); const timeStamp = ref(`timestamp=${Date.now()}`); -const url = computed( - () => - `/api/${$props.storage}/${$props.collection}/${$props.size}/${$props.id}/download?access_token=${token}&${timeStamp.value}` -); +import noImage from '/public/no-user.png'; +import { useRole } from 'src/composables/useRole'; +const url = computed(() => { + const isEmployee = useRole().isEmployee(); + return isEmployee + ? `/api/${$props.storage}/${$props.collection}/${$props.size}/${$props.id}/download?access_token=${token}&${timeStamp.value}` + : noImage; +}); const reload = () => { timeStamp.value = `timestamp=${Date.now()}`; }; defineExpose({ reload, }); - -onMounted(() => {}); </script> <template> - <QImg :src="url" v-bind="$attrs" @click="show = !show" spinner-color="primary" /> + <QImg + :class="{ zoomIn: $props.zoomSize }" + :src="url" + v-bind="$attrs" + @click="show = !show" + spinner-color="primary" + /> <QDialog v-model="show" v-if="$props.zoomSize"> <QImg :src="url" @@ -56,7 +64,9 @@ onMounted(() => {}); <style lang="scss" scoped> .q-img { - cursor: zoom-in; + &.zoomIn { + cursor: zoom-in; + } min-width: 50px; } .rounded { diff --git a/src/components/ui/VnLv.vue b/src/components/ui/VnLv.vue index 3220bce6a..ff65f759b 100644 --- a/src/components/ui/VnLv.vue +++ b/src/components/ui/VnLv.vue @@ -2,6 +2,7 @@ import { dashIfEmpty } from 'src/filters'; import { useI18n } from 'vue-i18n'; import { useClipboard } from 'src/composables/useClipboard'; +import { computed } from 'vue'; const $props = defineProps({ label: { type: String, default: null }, @@ -24,52 +25,67 @@ function copyValueText() { }, }); } +const val = computed(() => $props.value); </script> -<style scoped> -.label, -.value { - white-space: pre-line; - word-wrap: break-word; -} -</style> <template> <div class="vn-label-value"> - <div v-if="$props.label || $slots.label" class="label"> - <slot name="label"> - <span>{{ $props.label }}</span> - </slot> - </div> - <div class="value"> - <slot name="value"> - <span :title="$props.value"> - {{ $props.dash ? dashIfEmpty($props.value) : $props.value }} - </span> - </slot> - </div> - <div class="info" v-if="$props.info"> - <QIcon name="info" class="cursor-pointer" size="xs" color="grey"> - <QTooltip class="bg-dark text-white shadow-4" :offset="[10, 10]"> - {{ $props.info }} - </QTooltip> - </QIcon> - </div> - <div class="copy" v-if="$props.copy && $props.value" @click="copyValueText()"> - <QIcon name="Content_Copy" color="primary"> - <QTooltip>{{ t('globals.copyClipboard') }}</QTooltip> - </QIcon> - </div> + <QCheckbox + v-if="typeof value === 'boolean'" + v-model="val" + :label="label" + disable + dense + /> + <template v-else> + <div v-if="label || $slots.label" class="label"> + <slot name="label"> + <span>{{ label }}</span> + </slot> + </div> + <div class="value"> + <slot name="value"> + <span :title="value"> + {{ dash ? dashIfEmpty(value) : value }} + </span> + </slot> + </div> + <div class="info" v-if="info"> + <QIcon name="info" class="cursor-pointer" size="xs" color="grey"> + <QTooltip class="bg-dark text-white shadow-4" :offset="[10, 10]"> + {{ info }} + </QTooltip> + </QIcon> + </div> + <div class="copy" v-if="copy && value" @click="copyValueText()"> + <QIcon name="Content_Copy" color="primary"> + <QTooltip>{{ t('globals.copyClipboard') }}</QTooltip> + </QIcon> + </div> + </template> </div> </template> - <style lang="scss" scoped> -.vn-label-value:hover .copy { - visibility: visible; - cursor: pointer; +.vn-label-value { + &:hover .copy { + visibility: visible; + cursor: pointer; + } + + .label, + .value { + white-space: pre-line; + word-wrap: break-word; + } + .copy { + visibility: hidden; + } + + .info { + margin-left: 5px; + } } -.copy { - visibility: hidden; -} -.info { - margin-left: 5px; + +:deep(.q-checkbox.disabled) { + opacity: 1 !important; } </style> diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue index 9a2c06b0c..8e426b471 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -1,5 +1,5 @@ <script setup> -import { onMounted, ref, watch } from 'vue'; +import { onBeforeUnmount, onMounted, ref, watch } from 'vue'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'composables/useArrayData'; @@ -95,6 +95,8 @@ onMounted(async () => { mounted.value = true; }); +onBeforeUnmount(() => arrayData.reset()); + watch( () => props.data, () => { @@ -118,8 +120,7 @@ const addFilter = async (filter, params) => { async function fetch(params) { useArrayData(props.dataKey, params); - store.filter.skip = 0; - store.skip = 0; + arrayData.reset(['filter.skip', 'skip']); await arrayData.fetch({ append: false }); if (!store.hasMoreData) { isLoading.value = false; diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index 4e048e238..0c7a8a3f6 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -103,7 +103,7 @@ async function search() { const staticParams = Object.entries(store.userParams).filter( ([key, value]) => value && (props.staticParams || []).includes(key) ); - store.skip = 0; + arrayData.reset(['skip', 'page']); if (props.makeFetch) await arrayData.applyFilter({ diff --git a/src/components/ui/VnSubToolbar.vue b/src/components/ui/VnSubToolbar.vue index 8c86c056a..df6fcbcb7 100644 --- a/src/components/ui/VnSubToolbar.vue +++ b/src/components/ui/VnSubToolbar.vue @@ -1,6 +1,7 @@ <script setup> -import { onMounted, onUnmounted, ref } from 'vue'; +import { onMounted, onBeforeUnmount, ref, nextTick } from 'vue'; import { useStateStore } from 'stores/useStateStore'; + const stateStore = useStateStore(); const actions = ref(null); const data = ref(null); @@ -24,9 +25,7 @@ onMounted(() => { if (data.value) observer.observe(data.value, opts); }); -onUnmounted(() => { - stateStore.toggleSubToolbar(); -}); +onBeforeUnmount(() => stateStore.toggleSubToolbar()); </script> <template> diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index ff87842e8..0fcbbbb48 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -1,4 +1,4 @@ -import { onMounted, ref, computed } from 'vue'; +import { onMounted, computed } from 'vue'; import { useRouter, useRoute } from 'vue-router'; import axios from 'axios'; import { useArrayDataStore } from 'stores/useArrayDataStore'; @@ -16,11 +16,9 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { const router = useRouter(); let canceller = null; - const page = ref(1); - onMounted(() => { setOptions(); - store.skip = 0; + arrayDataStore.reset(['skip']); const query = route.query; const searchUrl = store.searchUrl; @@ -29,7 +27,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { const filter = params?.filter; delete params.filter; store.userParams = { ...params, ...store.userParams }; - store.userFilter = { ...JSON.parse(filter), ...store.userFilter }; + store.userFilter = { ...JSON.parse(filter ?? '{}'), ...store.userFilter }; } }); @@ -87,13 +85,17 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { } Object.assign(filter, store.userFilter, exprFilter); - Object.assign(store.filter, { ...filter, skip: store.skip }); - const params = { - filter: JSON.stringify(store.filter), - }; + let where; + if (filter?.where || store.filter?.where) + where = Object.assign(filter?.where ?? {}, store.filter?.where ?? {}); + Object.assign(filter, store.filter); + filter.where = where; + const params = { filter }; Object.assign(params, userParams); + params.filter.skip = store.skip; + params.filter = JSON.stringify(params.filter); store.currentFilter = params; store.isLoading = true; const response = await axios.get(store.url, { @@ -129,6 +131,10 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { delete store[option]; } + function reset(opts = []) { + if (arrayDataStore.get(key)) arrayDataStore.reset(key, opts); + } + function cancelRequest() { if (canceller) { canceller.abort(); @@ -146,22 +152,20 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { } async function addFilter({ filter, params }) { - if (filter) store.userFilter = Object.assign(store.userFilter, filter); + if (filter) store.filter = filter; let userParams = { ...store.userParams, ...params }; userParams = sanitizerParams(userParams, store?.exprBuilder); store.userParams = userParams; - store.skip = 0; - store.filter.skip = 0; - page.value = 1; + arrayDataStore.reset(['skip', 'filter.skip', 'page']); await fetch({ append: false }); return { filter, params }; } async function addFilterWhere(where) { - const storedFilter = { ...store.userFilter }; + const storedFilter = { ...store.filter }; if (!storedFilter?.where) storedFilter.where = {}; where = { ...storedFilter.where, ...where }; await addFilter({ filter: { where } }); @@ -187,8 +191,8 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { async function loadMore() { if (!store.hasMoreData) return; - store.skip = store.limit * page.value; - page.value += 1; + store.skip = store.limit * store.page; + store.page += 1; await fetch({ append: true }); } @@ -244,5 +248,6 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { updateStateParams, isLoading, deleteOption, + reset, }; } diff --git a/src/composables/useRole.js b/src/composables/useRole.js index 95b585283..d1a6d6ef3 100644 --- a/src/composables/useRole.js +++ b/src/composables/useRole.js @@ -27,8 +27,12 @@ export function useRole() { return false; } + function isEmployee() { + return hasAny(['employee']); + } return { + isEmployee, fetch, hasAny, state, diff --git a/src/css/app.scss b/src/css/app.scss index f6c873e90..27ed65534 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -5,6 +5,7 @@ body.body--light { --font-color: black; --vn-section-color: #e0e0e0; + --vn-section-hover-color: #b9b9b9; --vn-page-color: #ffffff; --vn-text-color: var(--font-color); --vn-label-color: #5f5f5f; @@ -19,6 +20,7 @@ body.body--light { body.body--dark { --vn-page-color: #222; --vn-section-color: #3d3d3d; + --vn-section-hover-color: #747474; --vn-text-color: white; --vn-label-color: #a8a8a8; --vn-accent-color: #424242; @@ -71,8 +73,9 @@ select:-webkit-autofill { .bg-vn-section-color { background-color: var(--vn-section-color); } -.bg-hover { - background-color: #666666; + +.bg-vn-hover { + background-color: var(--vn-section-hover-color); } .color-vn-label { @@ -152,6 +155,15 @@ select:-webkit-autofill { color: var(--vn-label-color); } +.disabled { + & .q-checkbox__label { + color: var(--vn-text-color); + } + & .q-checkbox__inner { + color: var(--vn-label-color); + } +} + .q-chip, .q-notification__message, .q-notification__icon { @@ -174,8 +186,6 @@ select:-webkit-autofill { justify-content: center; } -/* q-notification row items-stretch q-notification--standard bg-negative text-white */ - .q-card, .q-table, .q-table__bottom, diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index fbab06966..75b07801a 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -90,6 +90,7 @@ globals: send: Send code: Code pageTitles: + logIn: Login summary: Summary basicData: Basic data log: Logs @@ -100,13 +101,143 @@ globals: modes: Modes zones: Zones zonesList: Zones - deliveryList: Delivery days - upcomingList: Upcoming deliveries + deliveryDays: Delivery days + upcomingDeliveries: Upcoming deliveries role: Role alias: Alias aliasUsers: Users subRoles: Subroles inheritedRoles: Inherited Roles + customers: Customers + list: List + webPayments: Web Payments + extendedList: Extended list + notifications: Notifications + defaulter: Defaulter + customerCreate: New customer + fiscalData: Fiscal data + billingData: Billing data + consignees: Consignees + notes: Notes + credits: Credits + greuges: Greuges + balance: Balance + recoveries: Recoveries + webAccess: Web access + sms: Sms + creditManagement: Credit management + creditContracts: Credit contracts + creditOpinion: Credit opinion + others: Others + samples: Samples + consumption: Consumption + mandates: Mandates + contacts: Contacts + webPayment: Web payment + fileManagement: File management + unpaid: Unpaid + entries: Entries + buys: Buys + dms: File management + entryCreate: New entry + latestBuys: Latest buys + tickets: Tickets + ticketCreate: New Tickets + boxing: Boxing + sale: Sale + claims: Claims + claimCreate: New claim + lines: Lines + photos: Photos + development: Development + action: Action + invoiceOuts: Invoice out + negativeBases: Negative Bases + globalInvoicing: Global invoicing + invoiceOutCreate: Create invoice out + shelving: Shelving + shelvingList: Shelving List + shelvingCreate: New shelving + invoiceIns: Invoices In + invoiceInCreate: Create invoice in + vat: VAT + dueDay: Due day + intrastat: Intrastat + corrective: Corrective + order: Orders + orderList: List + orderCreate: New order + catalog: Catalog + volume: Volume + workers: Workers + workerCreate: New worker + department: Department + pda: PDA + pbx: Private Branch Exchange + calendar: Calendar + timeControl: Time control + locker: Locker + wagons: Wagons + wagonsList: Wagons List + wagonCreate: Create wagon + wagonEdit: Edit wagon + typesList: Types List + typeCreate: Create type + typeEdit: Edit type + wagonCounter: Trolley counter + roadmap: Roadmap + stops: Stops + routes: Routes + cmrsList: CMRs list + RouteList: List + routeCreate: New route + RouteRoadmap: Roadmaps + RouteRoadmapCreate: Create roadmap + autonomous: Autonomous + suppliers: Suppliers + supplier: Supplier + labeler: Labeler + supplierCreate: New supplier + accounts: Accounts + addresses: Addresses + agencyTerm: Agency agreement + travel: Travels + extraCommunity: Extra community + travelCreate: New travel + history: Log + thermographs: Thermograph + items: Items + diary: Diary + tags: Tags + create: Create + buyRequest: Buy requests + fixedPrice: Fixed prices + wasteBreakdown: Waste breakdown + itemCreate: New item + barcode: Barcodes + tax: Tax + botanical: Botanical + itemTypeCreate: New item type + family: Item Type + lastEntries: Last entries + itemType: Item type + monitors: Monitors + dashboard: Dashboard + users: Users + createTicket: Create ticket + ticketAdvance: Advance tickets + futureTickets: Future tickets + purchaseRequest: Purchase request + weeklyTickets: Weekly tickets + formation: Formation + locations: Locations + warehouses: Warehouses + roles: Roles + connections: Connections + acls: ACLs + mailForwarding: Mail forwarding + mailAlias: Mail alias + privileges: Privileges created: Created worker: Worker now: Now @@ -114,6 +245,7 @@ globals: new: New comment: Comment observations: Observations + goToModuleIndex: Go to module index errors: statusUnauthorized: Access denied statusInternalServerError: An internal server error has ocurred @@ -132,8 +264,6 @@ login: loginError: Invalid username or password fieldRequired: This field is required twoFactorRequired: Two-factor verification required - pageTitles: - logIn: Login twoFactor: code: Code validate: Validate @@ -148,40 +278,8 @@ verifyEmail: verifyEmail: Email verification dashboard: pageTitles: - dashboard: Dashboard + customer: - pageTitles: - customers: Customers - list: List - webPayments: Web Payments - extendedList: Extended list - notifications: Notifications - defaulter: Defaulter - customerCreate: New customer - summary: Summary - basicData: Basic data - fiscalData: Fiscal data - billingData: Billing data - consignees: Consignees - notes: Notes - credits: Credits - greuges: Greuges - balance: Balance - recoveries: Recoveries - webAccess: Web access - log: Log - sms: Sms - creditManagement: Credit management - creditContracts: Credit contracts - creditOpinion: Credit opinion - others: Others - samples: Samples - consumption: Consumption - mandates: Mandates - contacts: Contacts - webPayment: Web payment - fileManagement: File management - unpaid: Unpaid list: phone: Phone email: Email @@ -311,17 +409,6 @@ customer: hasCoreVnl: VNL core received hasSepaVnl: VNL B2B received entry: - pageTitles: - entries: Entries - list: List - summary: Summary - basicData: Basic data - buys: Buys - notes: Notes - dms: File management - log: Log - entryCreate: New entry - latestBuys: Latest buys list: newEntry: New entry landed: Landed @@ -330,6 +417,18 @@ entry: booked: Booked confirmed: Confirmed ordered: Ordered + tableVisibleColumns: + id: Id + reference: Reference + created: Creation + supplierFk: Supplier + isBooked: Booked + isConfirmed: Confirmed + isOrdered: Ordered + companyFk: Company + travelFk: Travel + isExcludedFromAvailable: Inventory + isRaid: Raid summary: commission: Commission currency: Currency @@ -405,34 +504,37 @@ entry: landed: Landed warehouseOut: Warehouse Out latestBuys: - picture: Picture - itemFk: Item ID - packing: Packing - grouping: Grouping - quantity: Quantity - size: Size - tags: Tags - type: Type - intrastat: Intrastat - origin: Origin - weightByPiece: Weight/Piece - isActive: Active - family: Family - entryFk: Entry - buyingValue: Buying value - freightValue: Freight value - comissionValue: Commission value - description: Description - packageValue: Package value - isIgnored: Is ignored - price2: Grouping - price3: Packing - minPrice: Min - ektFk: Ekt - weight: Weight - packagingFk: Package - packingOut: Package out - landing: Landing + tableVisibleColumns: + image: Picture + itemFk: Item ID + packing: Packing + grouping: Grouping + quantity: Quantity + size: Size + tags: Tags + type: Type + intrastat: Intrastat + origin: Origin + weightByPiece: Weight/Piece + isActive: Active + family: Family + entryFk: Entry + buyingValue: Buying value + freightValue: Freight value + comissionValue: Commission value + description: Description + packageValue: Package value + isIgnored: Is ignored + price2: Grouping + price3: Packing + minPrice: Min + ektFk: Ekt + weight: Weight + packagingFk: Package + packingOut: Package out + landing: Landing + isExcludedFromAvailable: Es inventory + isRaid: Raid ticket: pageTitles: tickets: Tickets @@ -444,10 +546,14 @@ ticket: sms: Sms notes: Notes sale: Sale + volume: Volume + observation: Notes ticketAdvance: Advance tickets futureTickets: Future tickets purchaseRequest: Purchase request weeklyTickets: Weekly tickets + services: Service + tracking: Tracking list: nickname: Nickname state: State @@ -522,18 +628,6 @@ ticket: warehouse: Warehouse agency: Agency claim: - pageTitles: - claims: Claims - list: List - claimCreate: New claim - summary: Summary - basicData: Basic Data - lines: Lines - photos: Photos - development: Development - log: Audit logs - notes: Notes - action: Action list: customer: Customer assignedTo: Assigned @@ -597,14 +691,6 @@ claim: noData: 'There are no images/videos, click here or drag and drop the file' dragDrop: Drag and drop it here invoiceOut: - pageTitles: - invoiceOuts: Invoice out - list: List - negativeBases: Negative Bases - globalInvoicing: Global invoicing - invoiceOutCreate: Create invoice out - summary: Summary - basicData: Basic Data list: ref: Reference issued: Issued @@ -672,13 +758,6 @@ invoiceOut: errors: downloadCsvFailed: CSV download failed shelving: - pageTitles: - shelving: Shelving - shelvingList: Shelving List - shelvingCreate: New shelving - summary: Summary - basicData: Basic Data - log: Logs list: parking: Parking priority: Priority @@ -705,17 +784,6 @@ parking: info: You can search by parking code label: Search parking... invoiceIn: - pageTitles: - invoiceIns: Invoices In - list: List - invoiceInCreate: Create invoice in - summary: Summary - basicData: Basic data - vat: VAT - dueDay: Due day - intrastat: Intrastat - corrective: Corrective - log: Logs list: ref: Reference supplier: Supplier @@ -766,15 +834,6 @@ invoiceIn: stems: Stems country: Country order: - pageTitles: - order: Orders - orderList: List - orderCreate: New order - summary: Summary - basicData: Basic Data - catalog: Catalog - volume: Volume - lines: Lines field: salesPersonFk: Sales Person clientFk: Client @@ -850,7 +909,6 @@ worker: timeControl: Time control locker: Locker balance: Balance - formation: Formation list: name: Name email: Email @@ -939,15 +997,6 @@ worker: credit: Have concept: Concept wagon: - pageTitles: - wagons: Wagons - wagonsList: Wagons List - wagonCreate: Create wagon - wagonEdit: Edit wagon - typesList: Types List - typeCreate: Create type - typeEdit: Edit type - wagonCounter: Trolley counter type: name: Name submit: Submit @@ -976,20 +1025,9 @@ wagon: minHeightBetweenTrays: 'The minimum height between trays is ' maxWagonHeight: 'The maximum height of the wagon is ' uncompleteTrays: There are incomplete trays -route/roadmap: - pageTitles: - roadmap: Roadmap - summary: Summary - basicData: Basic Data - stops: Stops -roadmap: - pageTitles: - roadmap: Roadmap - summary: Summary - basicData: Basic Data - stops: Stops route: pageTitles: + agency: Agency List routes: Routes cmrsList: CMRs list RouteList: List @@ -1028,28 +1066,20 @@ route: volume: Volume finished: Finished supplier: - pageTitles: - suppliers: Suppliers - supplier: Supplier - list: List - supplierCreate: New supplier - summary: Summary - basicData: Basic data - fiscalData: Fiscal data - billingData: Billing data - log: Log - accounts: Accounts - contacts: Contacts - addresses: Addresses - consumption: Consumption - agencyTerm: Agency agreement - dms: File management list: payMethod: Pay method payDeadline: Pay deadline payDay: Pay day account: Account newSupplier: New supplier + tableVisibleColumns: + id: Id + name: Name + nif: NIF/CIF + nickname: Alias + account: Account + payMethod: Pay Method + payDay: Pay Day summary: responsible: Responsible notes: Notes @@ -1135,15 +1165,16 @@ supplier: date: Date reference: Reference travel: - pageTitles: - travel: Travels - list: List - summary: Summary - extraCommunity: Extra community - travelCreate: New travel - basicData: Basic data - history: Log - thermographs: Thermograph + travelList: + tableVisibleColumns: + id: Id + ref: Reference + agency: Agency + shipped: Shipped + landed: Landed + warehouseIn: Warehouse in + warehouseOut: Warehouse out + totalEntries: Total entries summary: confirmed: Confirmed entryId: Entry Id @@ -1190,24 +1221,6 @@ travel: travelFileDescription: 'Travel id { travelId }' file: File item: - pageTitles: - items: Items - list: List - diary: Diary - tags: Tags - create: Create - buyRequest: Buy requests - fixedPrice: Fixed prices - wasteBreakdown: Waste breakdown - itemCreate: New item - barcode: Barcodes - tax: Tax - log: Log - botanical: Botanical - shelving: Shelving - itemTypeCreate: New item type - family: Item Type - lastEntries: Last entries descriptor: item: Item buyer: Buyer @@ -1293,22 +1306,6 @@ item: minSalesQuantity: 'Cantidad mínima de venta' genus: 'Genus' specie: 'Specie' -item/itemType: - pageTitles: - itemType: Item type - basicData: Basic data - summary: Summary -monitor: - pageTitles: - monitors: Monitors - list: List -zone: - pageTitles: - zones: Zones - zonesList: Zones - deliveryList: Delivery days - upcomingList: Upcoming deliveries - components: topbar: {} itemsFilterPanel: diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index fec78d5e6..452e387c9 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -90,6 +90,7 @@ globals: send: Enviar code: Código pageTitles: + logIn: Inicio de sesión summary: Resumen basicData: Datos básicos log: Historial @@ -100,14 +101,144 @@ globals: modes: Modos zones: Zonas zonesList: Zonas - deliveryList: Días de entrega - upcomingList: Próximos repartos + deliveryDays: Días de entrega + upcomingDeliveries: Próximos repartos role: Role alias: Alias aliasUsers: Usuarios subRoles: Subroles inheritedRoles: Roles heredados + customers: Clientes + customerCreate: Nuevo cliente + list: Listado + webPayments: Pagos Web + extendedList: Listado extendido + notifications: Notificaciones + defaulter: Morosos + createCustomer: Crear cliente + fiscalData: Datos fiscales + billingData: Forma de pago + consignees: Consignatarios + notes: Notas + credits: Créditos + greuges: Greuges + balance: Balance + recoveries: Recobros + webAccess: Acceso web + sms: Sms + creditManagement: Gestión de crédito + creditContracts: Contratos de crédito + creditOpinion: Opinión de crédito + others: Otros + samples: Plantillas + consumption: Consumo + mandates: Mandatos + contacts: Contactos + webPayment: Pago web + fileManagement: Gestión documental + unpaid: Impago + entries: Entradas + buys: Compras + dms: Gestión documental + entryCreate: Nueva entrada + latestBuys: Últimas compras + tickets: Tickets + ticketCreate: Nuevo ticket + boxing: Encajado + sale: Lineas del pedido + claims: Reclamaciones + claimCreate: Crear reclamación + lines: Líneas + development: Trazabilidad + photos: Fotos + action: Acción + invoiceOuts: Fact. emitidas + negativeBases: Bases Negativas + globalInvoicing: Facturación global + invoiceOutCreate: Crear fact. emitida + order: Cesta + orderList: Listado + orderCreate: Nueva orden + catalog: Catálogo + volume: Volumen + shelving: Carros + shelvingList: Listado de carros + shelvingCreate: Nuevo carro + invoiceIns: Fact. recibidas + invoiceInCreate: Crear fact. recibida + vat: IVA + labeler: Etiquetas + dueDay: Vencimiento + intrastat: Intrastat + corrective: Rectificativa workers: Trabajadores + workerCreate: Nuevo trabajador + department: Departamentos + pda: PDA + pbx: Centralita + calendar: Calendario + timeControl: Control de horario + locker: Taquilla + wagons: Vagones + wagonsList: Listado vagones + wagonCreate: Crear tipo + wagonEdit: Editar tipo + typesList: Listado tipos + typeCreate: Crear tipo + typeEdit: Editar tipo + wagonCounter: Contador de carros + roadmap: Troncales + stops: Paradas + routes: Rutas + cmrsList: Listado de CMRs + RouteList: Listado + routeCreate: Nueva ruta + RouteRoadmap: Troncales + RouteRoadmapCreate: Crear troncal + autonomous: Autónomos + suppliers: Proveedores + supplier: Proveedor + supplierCreate: Nuevo proveedor + accounts: Cuentas + addresses: Direcciones + agencyTerm: Acuerdo agencia + travel: Envíos + create: Crear + extraCommunity: Extra comunitarios + travelCreate: Nuevo envío + history: Historial + thermographs: Termógrafos + items: Artículos + diary: Histórico + tags: Etiquetas + fixedPrice: Precios fijados + buyRequest: Peticiones de compra + wasteBreakdown: Deglose de mermas + itemCreate: Nuevo artículo + tax: 'IVA' + botanical: 'Botánico' + barcode: 'Código de barras' + itemTypeCreate: Nueva familia + family: Familia + lastEntries: Últimas entradas + itemType: Familia + monitors: Monitores + dashboard: Tablón + users: Usuarios + createTicket: Crear ticket + ticketAdvance: Adelantar tickets + futureTickets: Tickets a futuro + purchaseRequest: Petición de compra + weeklyTickets: Tickets programados + formation: Formación + locations: Ubicaciones + warehouses: Almacenes + roles: Roles + connections: Conexiones + acls: ACLs + mailForwarding: Reenvío de correo + mailAlias: Alias de correo + privileges: Privilegios created: Fecha creación worker: Trabajador now: Ahora @@ -115,6 +246,7 @@ globals: new: Nuevo comment: Comentario observations: Observaciones + goToModuleIndex: Ir al índice del módulo errors: statusUnauthorized: Acceso denegado statusInternalServerError: Ha ocurrido un error interno del servidor @@ -133,8 +265,6 @@ login: loginError: Nombre de usuario o contraseña incorrectos fieldRequired: Este campo es obligatorio twoFactorRequired: Verificación de doble factor requerida - pageTitles: - logIn: Inicio de sesión twoFactor: code: Código validate: Validar @@ -147,41 +277,8 @@ verifyEmail: verifyEmail: Verificación de correo dashboard: pageTitles: - dashboard: Tablón + customer: - pageTitles: - customers: Clientes - customerCreate: Nuevo cliente - list: Listado - webPayments: Pagos Web - extendedList: Listado extendido - notifications: Notificaciones - defaulter: Morosos - createCustomer: Crear cliente - summary: Resumen - basicData: Datos básicos - fiscalData: Datos fiscales - billingData: Forma de pago - consignees: Consignatarios - notes: Notas - credits: Créditos - greuges: Greuges - balance: Balance - recoveries: Recobros - webAccess: Acceso web - log: Historial - sms: Sms - creditManagement: Gestión de crédito - creditContracts: Contratos de crédito - creditOpinion: Opinión de crédito - others: Otros - samples: Plantillas - consumption: Consumo - mandates: Mandatos - contacts: Contactos - webPayment: Pago web - fileManagement: Gestión documental - unpaid: Impago list: phone: Teléfono email: Email @@ -310,17 +407,6 @@ customer: hasCoreVnl: Recibido core VNL hasSepaVnl: Recibido B2B VNL entry: - pageTitles: - entries: Entradas - list: Listado - summary: Resumen - basicData: Datos básicos - buys: Compras - notes: Notas - dms: Gestión documental - log: Historial - entryCreate: Nueva entrada - latestBuys: Últimas compras list: newEntry: Nueva entrada landed: F. entrega @@ -329,6 +415,18 @@ entry: booked: Asentado confirmed: Confirmado ordered: Pedida + tableVisibleColumns: + id: Id + reference: Referencia + created: Creación + supplierFk: Proveedor + isBooked: Asentado + isConfirmed: Confirmado + isOrdered: Pedida + companyFk: Empresa + travelFk: Envio + isExcludedFromAvailable: Inventario + isRaid: Redada summary: commission: Comisión currency: Moneda @@ -404,34 +502,37 @@ entry: landed: F. entrega warehouseOut: Alm. salida latestBuys: - picture: Foto - itemFk: ID Artículo - packing: Packing - grouping: Grouping - quantity: Cantidad - size: Medida - tags: Etiquetas - type: Tipo - intrastat: Intrastat - origin: Origen - weightByPiece: Peso (gramos)/tallo - isActive: Activo - family: Familia - entryFk: Entrada - buyingValue: Coste - freightValue: Porte - comissionValue: Comisión - description: Descripción - packageValue: Embalaje - isIgnored: Ignorado - price2: Grouping - price3: Packing - minPrice: Min - ektFk: Ekt - weight: Peso - packagingFk: Embalaje - packingOut: Embalaje envíos - landing: Llegada + tableVisibleColumns: + image: Foto + itemFk: Id Artículo + packing: packing + grouping: Grouping + quantity: Cantidad + size: Medida + tags: Etiquetas + type: Tipo + intrastat: Intrastat + origin: Origen + weightByPiece: Peso (gramos)/tallo + isActive: Activo + family: Familia + entryFk: Entrada + buyingValue: Coste + freightValue: Porte + comissionValue: Comisión + description: Descripción + packageValue: Embalaje + isIgnored: Ignorado + price2: Grouping + price3: Packing + minPrice: Min + ektFk: Ekt + weight: Peso + packagingFk: Embalaje + packingOut: Embalaje envíos + landing: Llegada + isExcludedFromAvailable: Es inventario + isRaid: Redada ticket: pageTitles: tickets: Tickets @@ -443,10 +544,14 @@ ticket: sms: Sms notes: Notas sale: Lineas del pedido + volume: Volumen + observation: Notas ticketAdvance: Adelantar tickets futureTickets: Tickets a futuro purchaseRequest: Petición de compra weeklyTickets: Tickets programados + services: Servicios + tracking: Estados list: nickname: Alias state: Estado @@ -521,18 +626,6 @@ ticket: warehouse: Almacén agency: Agencia claim: - pageTitles: - claims: Reclamaciones - list: Listado - claimCreate: Crear reclamación - summary: Resumen - basicData: Datos básicos - lines: Líneas - development: Trazabilidad - photos: Fotos - log: Historial - notes: Notas - action: Acción list: customer: Cliente assignedTo: Asignada a @@ -596,14 +689,6 @@ claim: noData: No hay imágenes/videos haz click aquí o arrastra y suelta el archivo dragDrop: Arrástralo y sueltalo aquí invoiceOut: - pageTitles: - invoiceOuts: Fact. emitidas - list: Listado - negativeBases: Bases Negativas - globalInvoicing: Facturación global - invoiceOutCreate: Crear fact. emitida - summary: Resumen - basicData: Datos básicos list: ref: Referencia issued: Fecha emisión @@ -671,15 +756,6 @@ invoiceOut: errors: downloadCsvFailed: Error al descargar CSV order: - pageTitles: - order: Cesta - orderList: Listado - orderCreate: Nueva orden - summary: Resumen - basicData: Datos básicos - catalog: Catálogo - volume: Volumen - lines: Líneas field: salesPersonFk: Comercial clientFk: Cliente @@ -721,13 +797,6 @@ order: price: Precio amount: Monto shelving: - pageTitles: - shelving: Carros - shelvingList: Listado de carros - shelvingCreate: Nuevo carro - summary: Resumen - basicData: Datos básicos - log: Historial list: parking: Parking priority: Prioridad @@ -753,17 +822,6 @@ parking: info: Puedes buscar por código de parking label: Buscar parking... invoiceIn: - pageTitles: - invoiceIns: Fact. recibidas - list: Listado - invoiceInCreate: Crear fact. recibida - summary: Resumen - basicData: Datos básicos - vat: IVA - dueDay: Vencimiento - intrastat: Intrastat - corrective: Rectificativa - log: Historial list: ref: Referencia supplier: Proveedor @@ -846,7 +904,6 @@ worker: timeControl: Control de horario locker: Taquilla balance: Balance - formation: Formación list: name: Nombre email: Email @@ -926,15 +983,6 @@ worker: credit: Haber concept: Concepto wagon: - pageTitles: - wagons: Vagones - wagonsList: Listado vagones - wagonCreate: Crear tipo - wagonEdit: Editar tipo - typesList: Listado tipos - typeCreate: Crear tipo - typeEdit: Editar tipo - wagonCounter: Contador de carros type: name: Nombre submit: Guardar @@ -963,36 +1011,12 @@ wagon: minHeightBetweenTrays: 'La distancia mínima entre bandejas es ' maxWagonHeight: 'La altura máxima del vagón es ' uncompleteTrays: Hay bandejas sin completar -route/roadmap: - pageTitles: - roadmap: Troncales - summary: Resumen - basicData: Datos básicos - stops: Paradas -roadmap: - pageTitles: - roadmap: Troncales - summary: Resumen - basicData: Datos básicos - stops: Paradas route: - pageTitles: - routes: Rutas - cmrsList: Listado de CMRs - RouteList: Listado - routeCreate: Nueva ruta - basicData: Datos básicos - summary: Resumen - RouteRoadmap: Troncales - RouteRoadmapCreate: Crear troncal - tickets: Tickets - log: Historial - autonomous: Autónomos cmr: list: results: resultados cmrFk: Id CMR - hasCmrDms: Adjuntado en gestdoc + hasCmrDms: Gestdoc 'true': Sí 'false': 'No' ticketFk: Id ticket @@ -1015,28 +1039,20 @@ route: volume: Volumen finished: Finalizada supplier: - pageTitles: - suppliers: Proveedores - supplier: Proveedor - list: Listado - supplierCreate: Nuevo proveedor - summary: Resumen - basicData: Datos básicos - fiscalData: Datos fiscales - billingData: Forma de pago - log: Historial - accounts: Cuentas - contacts: Contactos - addresses: Direcciones - consumption: Consumo - agencyTerm: Acuerdo agencia - dms: Gestión documental list: payMethod: Método de pago payDeadline: Plazo de pago payDay: Día de pago account: Cuenta newSupplier: Nuevo proveedor + tableVisibleColumns: + id: Id + name: Nombre + nif: NIF/CIF + nickname: Alias + account: Cuenta + payMethod: Método de pago + payDay: Dia de pago summary: responsible: Responsable notes: Notas @@ -1122,16 +1138,16 @@ supplier: date: Fecha reference: Referencia travel: - pageTitles: - travel: Envíos - list: Listado - create: Crear - summary: Resumen - extraCommunity: Extra comunitarios - travelCreate: Nuevo envío - basicData: Datos básicos - history: Historial - thermographs: Termógrafos + travelList: + tableVisibleColumns: + id: Id + ref: Referencia + agency: Agencia + shipped: Enviado + landed: Llegada + warehouseIn: Almacén de salida + warehouseOut: Almacén de entrada + totalEntries: Total de entradas summary: confirmed: Confirmado entryId: Id entrada @@ -1178,24 +1194,6 @@ travel: travelFileDescription: 'Id envío { travelId }' file: Fichero item: - pageTitles: - items: Artículos - list: Listado - diary: Histórico - tags: Etiquetas - fixedPrice: Precios fijados - buyRequest: Peticiones de compra - wasteBreakdown: Deglose de mermas - itemCreate: Nuevo artículo - basicData: 'Datos básicos' - tax: 'IVA' - botanical: 'Botánico' - barcode: 'Código de barras' - log: Historial - shelving: Carros - itemTypeCreate: Nueva familia - family: Familia - lastEntries: Últimas entradas descriptor: item: Artículo buyer: Comprador @@ -1281,27 +1279,6 @@ item: achieved: 'Conseguido' concept: 'Concepto' state: 'Estado' -item/itemType: - pageTitles: - itemType: Familia - basicData: Datos básicos - summary: Resumen -zone: - pageTitles: - zones: Zonas - list: Zonas - deliveryList: Días de entrega - upcomingList: Próximos repartos -role: - pageTitles: - zones: Zonas - list: Zonas - deliveryList: Días de entrega - upcomingList: Próximos repartos -monitor: - pageTitles: - monitors: Monitores - list: Listado components: topbar: {} itemsFilterPanel: diff --git a/src/pages/Account/Card/AccountDescriptor.vue b/src/pages/Account/Card/AccountDescriptor.vue index ddc7c077f..d43266172 100644 --- a/src/pages/Account/Card/AccountDescriptor.vue +++ b/src/pages/Account/Card/AccountDescriptor.vue @@ -49,20 +49,6 @@ const hasAccount = ref(false); :title="data.title" :subtitle="data.subtitle" > - <template #header-extra-action> - <QBtn - round - flat - size="md" - color="white" - icon="face" - :to="{ name: 'AccountList' }" - > - <QTooltip> - {{ t('Go to module index') }} - </QTooltip> - </QBtn> - </template> <template #menu> <AccountDescriptorMenu :has-account="hasAccount" /> </template> diff --git a/src/pages/Account/Role/Card/RoleDescriptor.vue b/src/pages/Account/Role/Card/RoleDescriptor.vue index 89712b0b9..91d733714 100644 --- a/src/pages/Account/Role/Card/RoleDescriptor.vue +++ b/src/pages/Account/Role/Card/RoleDescriptor.vue @@ -15,6 +15,10 @@ const $props = defineProps({ required: false, default: null, }, + summary: { + type: Object, + default: null, + }, }); const route = useRoute(); @@ -60,14 +64,14 @@ const removeRole = () => { <template> <CardDescriptor - ref="descriptor" - :url="`VnRoles`" + :url="`VnRoles/${entityId}`" :filter="filter" module="Role" @on-fetch="setData" data-key="accountData" :title="data.title" :subtitle="data.subtitle" + :summary="$props.summary" > <template #menu> <QItem v-ripple clickable @click="removeRole()"> diff --git a/src/pages/Account/Role/Card/RoleDescriptorProxy.vue b/src/pages/Account/Role/Card/RoleDescriptorProxy.vue new file mode 100644 index 000000000..9714a3ae6 --- /dev/null +++ b/src/pages/Account/Role/Card/RoleDescriptorProxy.vue @@ -0,0 +1,17 @@ +<script setup> +import RoleDescriptor from './RoleDescriptor.vue'; +import RoleSummary from './RoleSummary.vue'; + +const $props = defineProps({ + id: { + type: Number, + required: true, + }, +}); +</script> + +<template> + <QPopupProxy> + <RoleDescriptor v-if="$props.id" :id="$props.id" :summary="RoleSummary" /> + </QPopupProxy> +</template> diff --git a/src/pages/Account/Role/Card/RoleSummary.vue b/src/pages/Account/Role/Card/RoleSummary.vue index d404fa2f0..76c72e947 100644 --- a/src/pages/Account/Role/Card/RoleSummary.vue +++ b/src/pages/Account/Role/Card/RoleSummary.vue @@ -30,6 +30,7 @@ const filter = { :url="`VnRoles`" :filter="filter" @on-fetch="(data) => (role = data)" + data-key="RoleSummary" > <template #header> {{ role.id }} - {{ role.name }} </template> <template #body> diff --git a/src/pages/Agency/AgencyList.vue b/src/pages/Agency/AgencyList.vue deleted file mode 100644 index ec6506ba0..000000000 --- a/src/pages/Agency/AgencyList.vue +++ /dev/null @@ -1,72 +0,0 @@ -<script setup> -import VnPaginate from 'src/components/ui/VnPaginate.vue'; -import CardList from 'src/components/ui/CardList.vue'; -import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; -import { useRouter } from 'vue-router'; -import { useI18n } from 'vue-i18n'; - -const { t } = useI18n(); -const router = useRouter(); -function navigate(id) { - router.push({ path: `/agency/${id}` }); -} -function exprBuilder(param, value) { - if (!value) return; - if (param !== 'search') return; - - if (!isNaN(value)) return { id: value }; - - return { name: { like: `%${value}%` } }; -} -</script> -<template> - <VnSearchbar - :info="t('You can search by name')" - :label="t('Search agency')" - data-key="AgencyList" - url="Agencies" - /> - <QPage class="column items-center q-pa-md"> - <div class="vn-card-list"> - <VnPaginate - data-key="AgencyList" - url="Agencies" - order="name" - :expr-builder="exprBuilder" - > - <template #body="{ rows }"> - <CardList - :id="row.id" - :key="row.id" - :title="row.name" - @click="navigate(row.id)" - v-for="row of rows" - > - <template #list-items> - <QCheckbox - :label="t('isOwn')" - v-model="row.isOwn" - :disable="true" - /> - <QCheckbox - :label="t('isAnyVolumeAllowed')" - v-model="row.isAnyVolumeAllowed" - :disable="true" - /> - </template> - </CardList> - </template> - </VnPaginate> - </div> - </QPage> -</template> -<i18n> - es: - isOwn: Tiene propietario - isAnyVolumeAllowed: Permite cualquier volumen - Search agency: Buscar agencia - You can search by name: Puedes buscar por nombre - en: - isOwn: Has owner - isAnyVolumeAllowed: Allows any volume -</i18n> diff --git a/src/pages/Claim/Card/ClaimDescriptor.vue b/src/pages/Claim/Card/ClaimDescriptor.vue index 5336c4427..ecf16f4c6 100644 --- a/src/pages/Claim/Card/ClaimDescriptor.vue +++ b/src/pages/Claim/Card/ClaimDescriptor.vue @@ -56,8 +56,7 @@ onMounted(async () => { :url="`Claims/${entityId}`" :filter="filter" module="Claim" - :title="data.title" - :subtitle="data.subtitle" + title="client.name" @on-fetch="setData" data-key="Claim" > diff --git a/src/pages/Claim/Card/ClaimSummary.vue b/src/pages/Claim/Card/ClaimSummary.vue index 1a2d3c251..55d59822d 100644 --- a/src/pages/Claim/Card/ClaimSummary.vue +++ b/src/pages/Claim/Card/ClaimSummary.vue @@ -224,7 +224,7 @@ async function changeState(value) { <QCard class="vn-one"> <VnTitle :url="`#/claim/${entityId}/basic-data`" - :text="t('claim.pageTitles.basicData')" + :text="t('globals.pageTitles.basicData')" /> <VnLv :label="t('claim.summary.created')" diff --git a/src/pages/Customer/Card/CustomerBasicData.vue b/src/pages/Customer/Card/CustomerBasicData.vue index 805795522..6f37544e8 100644 --- a/src/pages/Customer/Card/CustomerBasicData.vue +++ b/src/pages/Customer/Card/CustomerBasicData.vue @@ -69,7 +69,7 @@ const filterOptions = { <template #form="{ data, validate, filter }"> <VnRow class="row q-gutter-md q-mb-md"> <VnInput - :label="t('Comercial name')" + :label="t('globals.name')" :rules="validate('client.socialName')" autofocus clearable diff --git a/src/pages/Customer/Card/CustomerDescriptor.vue b/src/pages/Customer/Card/CustomerDescriptor.vue index 2d3b17bc9..5e688076a 100644 --- a/src/pages/Customer/Card/CustomerDescriptor.vue +++ b/src/pages/Customer/Card/CustomerDescriptor.vue @@ -45,20 +45,6 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity :summary="$props.summary" data-key="customerData" > - <template #header-extra-action> - <QBtn - round - flat - size="sm" - icon="vn:Person" - color="white" - :to="{ name: 'CustomerList' }" - > - <QTooltip> - {{ t('Go to module index') }} - </QTooltip> - </QBtn> - </template> <template #menu="{ entity }"> <CustomerDescriptorMenu :customer="entity" /> </template> diff --git a/src/pages/Customer/Card/CustomerGreuges.vue b/src/pages/Customer/Card/CustomerGreuges.vue index 0dcb3a3a9..8cca2ef23 100644 --- a/src/pages/Customer/Card/CustomerGreuges.vue +++ b/src/pages/Customer/Card/CustomerGreuges.vue @@ -13,7 +13,7 @@ const { t } = useI18n(); const route = useRoute(); const stateStore = computed(() => useStateStore()); const rows = ref([]); -const totalAmount = ref(0); +const totalAmount = ref(); const filter = { include: [ @@ -75,7 +75,7 @@ const columns = computed(() => [ }, { align: 'left', - field: (value) => value.user.name, + field: (value) => value?.user?.name, label: t('Created by'), name: 'createdBy', }, @@ -87,7 +87,7 @@ const columns = computed(() => [ }, { align: 'left', - field: (value) => value.greugeType.name, + field: (value) => value?.greugeType?.name, label: t('Type'), name: 'type', }, @@ -108,26 +108,9 @@ const setRows = (data) => { <template> <FetchData :filter="filter" @on-fetch="setRows" auto-load url="greuges" /> - <template v-if="stateStore.isHeaderMounted()"> - <Teleport to="#actions-append"> - <div class="row q-gutter-x-sm"> - <QBtn - flat - @click="stateStore.toggleRightDrawer()" - round - dense - icon="menu" - > - <QTooltip bottom anchor="bottom right"> - {{ t('globals.collapseMenu') }} - </QTooltip> - </QBtn> - </div> - </Teleport> - </template> <QDrawer v-model="stateStore.rightDrawer" side="right" :width="300" show-if-above> <QCard class="full-width q-pa-sm"> - <h6 class="flex justify-end q-my-lg q-pr-lg" v-if="totalAmount"> + <h6 class="flex justify-end q-my-lg q-pr-lg" v-if="totalAmount !== undefined"> <span class="color-vn-label q-mr-md">{{ t('Total') }}:</span> {{ toCurrency(totalAmount) }} </h6> diff --git a/src/pages/Customer/Card/CustomerSummary.vue b/src/pages/Customer/Card/CustomerSummary.vue index 1190f22fe..86de5217e 100644 --- a/src/pages/Customer/Card/CustomerSummary.vue +++ b/src/pages/Customer/Card/CustomerSummary.vue @@ -73,7 +73,7 @@ const creditWarning = computed(() => { :text="t('customer.summary.basicData')" /> <VnLv :label="t('customer.summary.customerId')" :value="entity.id" /> - <VnLv :label="t('customer.summary.name')" :value="entity.name" /> + <VnLv :label="t('globals.name')" :value="entity.name" /> <VnLv :label="t('customer.summary.contact')" :value="entity.contact" /> <VnLv :value="entity.phone"> <template #label> diff --git a/src/pages/Customer/CustomerList.vue b/src/pages/Customer/CustomerList.vue index ccd53e8b2..58aaf2bf0 100644 --- a/src/pages/Customer/CustomerList.vue +++ b/src/pages/Customer/CustomerList.vue @@ -38,7 +38,7 @@ const columns = computed(() => [ }, { align: 'left', - label: t('customer.extendedList.tableVisibleColumns.name'), + label: t('globals.name'), name: 'name', isTitle: true, create: true, @@ -65,6 +65,8 @@ const columns = computed(() => [ url: 'Workers/activeWithInheritedRole', fields: ['id', 'name'], where: { role: 'salesPerson' }, + optionFilter: 'firstName', + useLike: false, }, create: true, columnField: { @@ -406,6 +408,7 @@ function handleLocation(data, location) { default-mode="table" redirect="customer" auto-load + :disable-option="{ card: true }" > <template #more-create-dialog="{ data }"> <VnLocation diff --git a/src/pages/Customer/Defaulter/CustomerDefaulter.vue b/src/pages/Customer/Defaulter/CustomerDefaulter.vue index 06732b944..693b016fb 100644 --- a/src/pages/Customer/Defaulter/CustomerDefaulter.vue +++ b/src/pages/Customer/Defaulter/CustomerDefaulter.vue @@ -91,7 +91,6 @@ const tableColumnComponents = { props: (prop) => ({ disable: true, 'model-value': prop.value, - class: 'disabled-checkbox', }), event: () => {}, }, diff --git a/src/pages/Entry/Card/EntryDescriptor.vue b/src/pages/Entry/Card/EntryDescriptor.vue index 3c925ead6..a55ad67cd 100644 --- a/src/pages/Entry/Card/EntryDescriptor.vue +++ b/src/pages/Entry/Card/EntryDescriptor.vue @@ -1,5 +1,5 @@ <script setup> -import { ref, computed, watch } from 'vue'; +import { ref, computed, watch, onMounted } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; @@ -10,6 +10,7 @@ import useCardDescription from 'src/composables/useCardDescription'; import { useState } from 'src/composables/useState'; import { toDate } from 'src/filters'; import { usePrintService } from 'composables/usePrintService'; +import { getUrl } from 'src/composables/getUrl'; const $props = defineProps({ id: { @@ -17,10 +18,6 @@ const $props = defineProps({ required: false, default: null, }, - summary: { - type: Object, - default: null, - }, }); const route = useRoute(); @@ -28,6 +25,7 @@ const { t } = useI18n(); const { openReport } = usePrintService(); const state = useState(); const entryDescriptorRef = ref(null); +const url = ref(); const entryFilter = { include: [ @@ -69,6 +67,9 @@ const entryFilter = { const entityId = computed(() => { return $props.id || route.params.id; }); +onMounted(async () => { + url.value = await getUrl(''); +}); const data = ref(useCardDescription()); const setData = (entity) => @@ -114,6 +115,7 @@ watch; :subtitle="data.subtitle" @on-fetch="setData" data-key="entry" + :summary="$attrs" > <template #menu="{ entity }"> <QItem v-ripple clickable @click="showEntryReport(entity)"> diff --git a/src/pages/Entry/Card/EntryDescriptorProxy.vue b/src/pages/Entry/Card/EntryDescriptorProxy.vue index e1c05f1ac..17b542ec1 100644 --- a/src/pages/Entry/Card/EntryDescriptorProxy.vue +++ b/src/pages/Entry/Card/EntryDescriptorProxy.vue @@ -1,5 +1,6 @@ <script setup> import EntryDescriptor from './EntryDescriptor.vue'; +import EntrySummary from './EntrySummary.vue'; const $props = defineProps({ id: { @@ -11,6 +12,6 @@ const $props = defineProps({ <template> <QPopupProxy> - <EntryDescriptor v-if="$props.id" :id="$props.id" /> + <EntryDescriptor v-if="$props.id" :id="$props.id" :summary="EntrySummary" /> </QPopupProxy> </template> diff --git a/src/pages/Entry/Card/EntrySummary.vue b/src/pages/Entry/Card/EntrySummary.vue index d08b7eed2..7b92b29d4 100644 --- a/src/pages/Entry/Card/EntrySummary.vue +++ b/src/pages/Entry/Card/EntrySummary.vue @@ -11,8 +11,6 @@ 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(); diff --git a/src/pages/Entry/EntryBuysTableDialog.vue b/src/pages/Entry/EntryBuysTableDialog.vue new file mode 100644 index 000000000..c2b04c291 --- /dev/null +++ b/src/pages/Entry/EntryBuysTableDialog.vue @@ -0,0 +1,120 @@ +<script setup> +import { computed } from 'vue'; +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; +import { QBtn } from 'quasar'; + +import VnPaginate from 'src/components/ui/VnPaginate.vue'; +import { usePrintService } from 'composables/usePrintService'; +const { openReport } = usePrintService(); + +const route = useRoute(); +const { t } = useI18n(); +const $props = defineProps({ + id: { + type: String, + required: false, + default: null, + }, +}); +const entityId = computed(() => $props.id || route.params.id); + +const entriesTableColumns = computed(() => [ + { + align: 'left', + name: 'itemFk', + field: 'itemFk', + label: t('globals.id'), + }, + { + align: 'left', + name: 'item', + label: t('entry.summary.item'), + field: (row) => row.item.name, + }, + { + align: 'left', + name: 'packagingFk', + label: t('entry.summary.package'), + field: 'packagingFk', + }, + { + align: 'left', + name: 'stickers', + label: t('entry.summary.stickers'), + field: 'stickers', + }, + { + align: 'left', + name: 'packing', + label: t('entry.summary.packing'), + field: 'packing', + }, + { + align: 'left', + name: 'grouping', + label: t('entry.summary.grouping'), + field: 'grouping', + }, +]); +</script> + +<template> + <QDialog ref="dialogRef"> + <QCard style="min-width: 800px"> + <QCardSection class="row items-center q-pb-none"> + <QAvatar + :icon="icon" + color="primary" + text-color="white" + size="xl" + v-if="icon" + /> + <span class="text-h6 text-grey">{{ title }}</span> + <QSpace /> + <QBtn icon="close" :disable="isLoading" flat round dense v-close-popup /> + </QCardSection> + <QCardActions align="right"> + <QBtn + :label="t('Print buys')" + color="primary" + icon="print" + :loading="isLoading" + @click="openReport(`Entries/${entityId}/buy-label`)" + unelevated + autofocus + /> + </QCardActions> + <QCardSection class="row items-center"> + <VnPaginate + ref="entryBuysPaginateRef" + :limit="0" + data-key="EntryBuys" + :url="`Entries/${entityId}/getBuys`" + auto-load + > + <template #body="{ rows }"> + <QTable + :rows="rows" + :columns="entriesTableColumns" + row-key="id" + flat + dense + class="q-ml-lg" + :grid="$q.screen.lt.md" + :no-data-label="t('globals.noResults')" + > + <template #body="props"> + <QTr> + <QTd v-for="col in props.cols" :key="col.name"> + {{ col.value }} + </QTd> + </QTr> + </template> + </QTable> + </template> + </VnPaginate> + </QCardSection> + </QCard> + </QDialog> +</template> diff --git a/src/pages/Entry/EntryLatestBuys.vue b/src/pages/Entry/EntryLatestBuys.vue index 5da3309d8..dca32c08d 100644 --- a/src/pages/Entry/EntryLatestBuys.vue +++ b/src/pages/Entry/EntryLatestBuys.vue @@ -1,759 +1,200 @@ <script setup> import { onMounted, ref, computed, reactive, onUnmounted } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useRouter } from 'vue-router'; - -import FetchData from 'components/FetchData.vue'; -import FetchedTags from 'components/ui/FetchedTags.vue'; -import EntryDescriptorProxy from './Card/EntryDescriptorProxy.vue'; -import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue'; -import EditTableCellValueForm from 'src/components/EditTableCellValueForm.vue'; -import VnInput from 'src/components/common/VnInput.vue'; -import VnSelect from 'src/components/common/VnSelect.vue'; -import EntryLatestBuysFilter from './EntryLatestBuysFilter.vue'; -import ItemDescriptorProxy from '../Item/Card/ItemDescriptorProxy.vue'; -import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; - -import { useStateStore } from 'stores/useStateStore'; -import { toDate, toCurrency } from 'src/filters'; -// import { useSession } from 'composables/useSession'; -import { dashIfEmpty } from 'src/filters'; -import { useArrayData } from 'composables/useArrayData'; import RightMenu from 'src/components/common/RightMenu.vue'; -import VnImg from 'src/components/ui/VnImg.vue'; - -const router = useRouter(); -// const { getTokenMultimedia } = useSession(); -// const token = getTokenMultimedia(); +import VnTable from 'components/VnTable/VnTable.vue'; +import EntryLatestBuysFilter from './EntryLatestBuysFilter.vue'; +import { useStateStore } from 'stores/useStateStore'; const stateStore = useStateStore(); const { t } = useI18n(); +import { toDate } from 'src/filters'; +import VnImg from 'src/components/ui/VnImg.vue'; +import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; -const rowsFetchDataRef = ref(null); -const itemTypesOptions = ref([]); -const originsOptions = ref([]); -const itemFamiliesOptions = ref([]); -const intrastatOptions = ref([]); -const packagingsOptions = ref([]); -const editTableCellDialogRef = ref(null); -const visibleColumns = ref([]); -const allColumnNames = ref([]); - -const exprBuilder = (param, value) => { - switch (param) { - case 'id': - case 'size': - case 'weightByPiece': - case 'isActive': - case 'family': - case 'minPrice': - case 'packingOut': - return { [`i.${param}`]: value }; - case 'name': - case 'description': - return { [`i.${param}`]: { like: `%${value}%` } }; - case 'code': - return { 'it.code': value }; - case 'intrastat': - return { 'intr.description': value }; - case 'origin': - return { 'ori.code': value }; - case 'landing': - return { [`lb.${param}`]: value }; - case 'packing': - case 'grouping': - case 'quantity': - case 'entryFk': - case 'buyingValue': - case 'freightValue': - case 'comissionValue': - case 'packageValue': - case 'isIgnored': - case 'price2': - case 'price3': - case 'ektFk': - case 'weight': - case 'packagingFk': - return { [`b.${param}`]: value }; - } -}; - -const params = reactive({}); -const arrayData = useArrayData('EntryLatestBuys', { - url: 'Buys/latestBuysFilter', - order: ['itemFk DESC'], - exprBuilder: exprBuilder, -}); -const store = arrayData.store; -const rows = computed(() => store.data); -const rowsSelected = ref([]); - -const getInputEvents = (col) => { - return col.columnFilter.type === 'select' - ? { 'update:modelValue': () => applyColumnFilter(col) } - : { - 'keyup.enter': () => applyColumnFilter(col), - }; -}; - -const columns = computed(() => [ +const columns = [ { - label: t('entry.latestBuys.picture'), - name: 'picture', - align: 'left', + align: 'center', + label: t('entry.latestBuys.tableVisibleColumns.image'), + name: 'image', + columnField: { + component: VnImg, + attrs: (id) => { + return { + id, + width: '50px', + }; + }, + }, + columnFilter: false, }, { - label: t('entry.latestBuys.itemFk'), + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.itemFk'), name: 'itemFk', - field: 'itemFk', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, + isTitle: true, }, { - label: t('entry.latestBuys.packing'), - field: 'packing', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.packing'), name: 'packing', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, - format: (val) => dashIfEmpty(val), }, { - label: t('entry.latestBuys.grouping'), - field: 'grouping', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.grouping'), name: 'grouping', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, - format: (val) => dashIfEmpty(val), }, { - label: t('entry.latestBuys.quantity'), - field: 'quantity', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.quantity'), name: 'quantity', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, }, { - label: t('entry.latestBuys.description'), - field: 'description', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.description'), name: 'description', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, - format: (val) => dashIfEmpty(val), }, { - label: t('entry.latestBuys.size'), - field: 'size', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.size'), name: 'size', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, }, { - label: t('entry.latestBuys.tags'), + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.tags'), name: 'tags', - align: 'left', - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, }, { - label: t('entry.latestBuys.type'), - field: 'code', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.type'), name: 'type', - align: 'left', - sortable: true, - columnFilter: { - component: VnSelect, - type: 'select', - filterValue: null, - event: getInputEvents, - attrs: { - options: itemTypesOptions.value, - 'option-value': 'code', - 'option-label': 'code', - dense: true, - }, - }, }, { - label: t('entry.latestBuys.intrastat'), - field: 'intrastat', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.intrastat'), name: 'intrastat', - align: 'left', - sortable: true, - columnFilter: { - component: VnSelect, - type: 'select', - filterValue: null, - event: getInputEvents, - attrs: { - options: intrastatOptions.value, - 'option-value': 'description', - 'option-label': 'description', - dense: true, - }, - }, }, { - label: t('entry.latestBuys.origin'), - field: 'origin', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.origin'), name: 'origin', - align: 'left', - sortable: true, - columnFilter: { - component: VnSelect, - type: 'select', - filterValue: null, - event: getInputEvents, - attrs: { - options: originsOptions.value, - 'option-value': 'code', - 'option-label': 'code', - dense: true, - }, - }, }, { - label: t('entry.latestBuys.weightByPiece'), - field: 'weightByPiece', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.weightByPiece'), name: 'weightByPiece', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, - - format: (val) => dashIfEmpty(val), }, { - label: t('entry.latestBuys.isActive'), - field: 'isActive', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.isActive'), name: 'isActive', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, }, { - label: t('entry.latestBuys.family'), - field: 'family', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.family'), name: 'family', - align: 'left', - sortable: true, - columnFilter: { - component: VnSelect, - type: 'select', - filterValue: null, - event: getInputEvents, - attrs: { - options: itemFamiliesOptions.value, - 'option-value': 'code', - 'option-label': 'code', - dense: true, - }, - }, }, { - label: t('entry.latestBuys.entryFk'), - field: 'entryFk', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.entryFk'), name: 'entryFk', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, }, { - label: t('entry.latestBuys.buyingValue'), - field: 'buyingValue', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.buyingValue'), name: 'buyingValue', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, - format: (val) => toCurrency(val), }, { - label: t('entry.latestBuys.freightValue'), - field: 'freightValue', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.freightValue'), name: 'freightValue', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, - format: (val) => toCurrency(val), }, { - label: t('entry.latestBuys.comissionValue'), - field: 'comissionValue', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.comissionValue'), name: 'comissionValue', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, - format: (val) => toCurrency(val), }, { - label: t('entry.latestBuys.packageValue'), - field: 'packageValue', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.packageValue'), name: 'packageValue', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, - format: (val) => toCurrency(val), }, { - label: t('entry.latestBuys.isIgnored'), - field: 'isIgnored', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.isIgnored'), name: 'isIgnored', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, }, { - label: t('entry.latestBuys.price2'), - field: 'price2', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.price2'), name: 'price2', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, - - format: (val) => toCurrency(val), }, { - label: t('entry.latestBuys.price3'), - field: 'price3', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.price3'), name: 'price3', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, - format: (val) => toCurrency(val), }, { - label: t('entry.latestBuys.minPrice'), - field: 'minPrice', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.minPrice'), name: 'minPrice', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, - format: (val) => toCurrency(val), }, { - label: t('entry.latestBuys.ektFk'), - field: 'ektFk', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.ektFk'), name: 'ektFk', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, - format: (val) => dashIfEmpty(val), }, { - label: t('entry.latestBuys.weight'), - field: 'weight', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.weight'), name: 'weight', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, }, { - label: t('entry.latestBuys.packagingFk'), - field: 'packagingFk', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.packagingFk'), name: 'packagingFk', - align: 'left', - sortable: true, - columnFilter: { - component: VnSelect, - type: 'select', - filterValue: null, - event: getInputEvents, - attrs: { - options: packagingsOptions.value, - 'option-value': 'id', - 'option-label': 'id', - dense: true, - }, - }, }, { - label: t('entry.latestBuys.packingOut'), - field: 'packingOut', + align: 'left', + label: t('entry.latestBuys.tableVisibleColumns.packingOut'), name: 'packingOut', - align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, - }, - format: (val) => dashIfEmpty(val), }, { - label: t('entry.latestBuys.landing'), - field: 'landing', - name: 'landing', align: 'left', - sortable: true, - columnFilter: { - component: VnInput, - type: 'text', - filterValue: null, - event: getInputEvents, - attrs: { - dense: true, - }, + label: t('entry.latestBuys.tableVisibleColumns.landing'), + name: 'landing', + component: 'date', + columnField: { + component: null, }, - format: (val) => toDate(val), + format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landing)), }, -]); - -const editTableCellFormFieldsOptions = [ - { field: 'packing', label: t('entry.latestBuys.packing') }, - { field: 'grouping', label: t('entry.latestBuys.grouping') }, - { field: 'packageValue', label: t('entry.latestBuys.packageValue') }, - { field: 'weight', label: t('entry.latestBuys.weight') }, - { field: 'description', label: t('globals.description') }, - { field: 'size', label: t('entry.latestBuys.size') }, - { field: 'weightByPiece', label: t('entry.latestBuys.weightByPiece') }, - { field: 'packingOut', label: t('entry.latestBuys.packingOut') }, - { field: 'landing', label: t('entry.latestBuys.landing') }, ]; -const openEditTableCellDialog = () => { - editTableCellDialogRef.value.show(); -}; - -const onEditCellDataSaved = async () => { - rowsSelected.value = []; - await rowsFetchDataRef.value.fetch(); -}; - -const redirectToEntryBuys = (entryFk) => { - router.push({ name: 'EntryBuys', params: { id: entryFk } }); -}; - -const applyColumnFilter = async (col) => { - try { - params[col.field] = col.columnFilter.filterValue; - await arrayData.addFilter({ params }); - } catch (err) { - console.error('Error applying column filter', err); - } -}; - onMounted(async () => { stateStore.rightDrawer = true; - const filteredColumns = columns.value.filter((col) => col.name !== 'picture'); - allColumnNames.value = filteredColumns.map((col) => col.name); - await arrayData.fetch({ append: false }); }); onUnmounted(() => (stateStore.rightDrawer = false)); </script> <template> - <FetchData - url="ItemTypes" - :filter="{ fields: ['code'], order: 'code ASC', limit: 30 }" - auto-load - @on-fetch="(data) => (itemTypesOptions = data)" - /> - <FetchData - url="Origins" - :filter="{ fields: ['code'], order: 'code ASC', limit: 30 }" - auto-load - @on-fetch="(data) => (originsOptions = data)" - /> - <FetchData - url="ItemFamilies" - :filter="{ fields: ['code'], order: 'code ASC', limit: 30 }" - auto-load - @on-fetch="(data) => (itemFamiliesOptions = data)" - /> - <FetchData - url="Packagings" - :filter="{ fields: ['id'], order: 'id ASC', limit: 30 }" - auto-load - @on-fetch="(data) => (packagingsOptions = data)" - /> - <FetchData - url="Intrastats" - :filter="{ fields: ['description'], order: 'description ASC', limit: 30 }" - auto-load - @on-fetch="(data) => (intrastatOptions = data)" - /> - <VnSubToolbar> - <template #st-data> - <TableVisibleColumns - :all-columns="allColumnNames" - table-code="latestBuys" - labels-traductions-path="entry.latestBuys" - @on-config-saved="visibleColumns = ['picture', ...$event]" - /> - </template> - </VnSubToolbar> <RightMenu> <template #right-panel> - <EntryLatestBuysFilter data-key="EntryLatestBuys" /> + <EntryLatestBuysFilter data-key="LatestBuys" /> </template> </RightMenu> - <Teleport to="#actions-append"> - <div class="row q-gutter-x-sm"> - <QBtn flat @click="stateStore.toggleRightDrawer()" round dense icon="menu"> - <QTooltip bottom anchor="bottom right"> - {{ t('globals.collapseMenu') }} - </QTooltip> - </QBtn> - </div> - </Teleport> - <QPage class="column items-center q-pa-md"> - <QTable - :rows="rows" - :columns="columns" - selection="multiple" - row-key="id" - class="full-width q-mt-md" - :visible-columns="visibleColumns" - v-model:selected="rowsSelected" - :no-data-label="t('globals.noResults')" - @row-click="(_, row) => redirectToEntryBuys(row.entryFk)" - > - <template #top-row="{ cols }"> - <QTr> - <QTd /> - <QTd - v-for="(col, index) in cols" - :key="index" - style="max-width: 100px" - > - <component - :is="col.columnFilter.component" - v-if="col.name !== 'picture'" - v-model="col.columnFilter.filterValue" - v-bind="col.columnFilter.attrs" - v-on="col.columnFilter.event(col)" - dense - /> - </QTd> - </QTr> - </template> - <template #body-cell-picture="{ row }"> - <QTd> - <VnImg :id="row.itemFk" size="50x50" class="image" /> - </QTd> - </template> - <template #body-cell-itemFk="{ row }"> - <QTd @click.stop> - <QBtn flat color="primary"> - {{ row.itemFk }} - </QBtn> - <ItemDescriptorProxy :id="row.itemFk" /> - </QTd> - </template> - <template #body-cell-tags="{ row }"> - <QTd> - <FetchedTags :item="row" :max-length="6" /> - </QTd> - </template> - <template #body-cell-entryFk="{ row }"> - <QTd @click.stop> - <QBtn flat color="primary"> - <EntryDescriptorProxy :id="row.entryFk" /> - {{ row.entryFk }} - </QBtn> - </QTd> - </template> - <template #body-cell-isIgnored="{ row }"> - <QTd> - <QIcon - :name="row.isIgnored ? `check` : `close`" - :color="row.isIgnored ? `positive` : `negative`" - size="sm" - /> - </QTd> - </template> - <template #body-cell-isActive="{ row }"> - <QTd> - <QIcon - :name="row.isActive ? `check` : `close`" - :color="row.isActive ? `positive` : `negative`" - size="sm" - /> - </QTd> - </template> - </QTable> - <QPageSticky v-if="rowsSelected.length > 0" :offset="[20, 20]"> - <QBtn @click="openEditTableCellDialog()" color="primary" fab icon="edit" /> - <QTooltip> - {{ t('Edit buy(s)') }} - </QTooltip> - </QPageSticky> - <QDialog ref="editTableCellDialogRef"> - <EditTableCellValueForm - edit-url="Buys/editLatestBuys" - :rows="rowsSelected" - :fields-options="editTableCellFormFieldsOptions" - @on-data-saved="onEditCellDataSaved()" - /> - </QDialog> - </QPage> + <VnSubToolbar /> + <VnTable + ref="tableRef" + data-key="LatestBuys" + url="Buys/latestBuysFilter" + order="id DESC" + :columns="columns" + redirect="entry" + default-mode="table" + auto-load + :right-search="false" + /> </template> <i18n> diff --git a/src/pages/Entry/EntryList.vue b/src/pages/Entry/EntryList.vue index e5d37900c..fe719db6e 100644 --- a/src/pages/Entry/EntryList.vue +++ b/src/pages/Entry/EntryList.vue @@ -1,31 +1,178 @@ <script setup> -import { onMounted } from 'vue'; +import { onMounted, ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; -import VnPaginate from 'src/components/ui/VnPaginate.vue'; -import VnLv from 'src/components/ui/VnLv.vue'; -import CardList from 'src/components/ui/CardList.vue'; -import EntrySummary from './Card/EntrySummary.vue'; +import { useRoute } from 'vue-router'; import EntryFilter from './EntryFilter.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import { useStateStore } from 'stores/useStateStore'; -import { toDate } from 'src/filters/index'; -import { useSummaryDialog } from 'src/composables/useSummaryDialog'; +import VnTable from 'components/VnTable/VnTable.vue'; import RightMenu from 'src/components/common/RightMenu.vue'; +import { toDate } from 'src/filters'; const stateStore = useStateStore(); -const router = useRouter(); const { t } = useI18n(); -const { viewSummary } = useSummaryDialog(); +const route = useRoute(); +const entityId = computed(() => route.params.id); +const tableRef = ref(); -function navigate(id) { - router.push({ path: `/entry/${id}` }); -} - -const redirectToCreateView = () => { - router.push({ name: 'EntryCreate' }); +const entryFilter = { + include: [ + { + relation: 'suppliers', + scope: { + fields: ['id', 'name'], + }, + }, + { + relation: 'travels', + scope: { + fields: ['id', 'ref'], + }, + }, + { + relation: 'companies', + scope: { + fields: ['id', 'code'], + }, + }, + ], }; +const columns = computed(() => [ + { + align: 'left', + label: t('entry.list.tableVisibleColumns.id'), + name: 'id', + isTitle: true, + cardVisible: true, + }, + { + align: 'left', + label: t('entry.list.tableVisibleColumns.reference'), + name: 'reference', + isTitle: true, + component: 'input', + columnField: { + component: null, + }, + create: true, + cardVisible: true, + }, + { + align: 'left', + label: t('entry.list.tableVisibleColumns.created'), + name: 'created', + create: true, + cardVisible: true, + component: 'date', + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.created)), + }, + { + align: 'left', + label: t('entry.list.tableVisibleColumns.supplierFk'), + name: 'supplierFk', + create: true, + cardVisible: true, + component: 'select', + attrs: { + url: 'suppliers', + fields: ['id', 'name'], + }, + columnField: { + component: null, + }, + format: (row, dashIfEmpty) => dashIfEmpty(row.supplierName), + }, + { + align: 'left', + label: t('entry.list.tableVisibleColumns.isBooked'), + name: 'isBooked', + cardVisible: true, + create: true, + component: 'checkbox', + }, + { + align: 'left', + label: t('entry.list.tableVisibleColumns.isConfirmed'), + name: 'isConfirmed', + cardVisible: true, + create: true, + component: 'checkbox', + }, + { + align: 'left', + label: t('entry.list.tableVisibleColumns.isOrdered'), + name: 'isOrdered', + cardVisible: true, + create: true, + component: 'checkbox', + }, + { + align: 'left', + label: t('entry.list.tableVisibleColumns.companyFk'), + name: 'companyFk', + component: 'select', + attrs: { + url: 'companies', + fields: ['id', 'code'], + optionLabel: 'code', + optionValue: 'id', + }, + columnField: { + component: null, + }, + create: true, + + format: (row, dashIfEmpty) => dashIfEmpty(row.companyCode), + }, + { + align: 'left', + label: t('entry.list.tableVisibleColumns.travelFk'), + name: 'travelFk', + component: 'select', + attrs: { + url: 'travels', + fields: ['id', 'ref'], + optionLabel: 'ref', + optionValue: 'id', + }, + columnField: { + component: null, + }, + create: true, + format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef), + }, + { + align: 'left', + label: t('entry.list.tableVisibleColumns.isExcludedFromAvailable'), + name: 'isExcludedFromAvailable', + chip: { + color: null, + condition: (value) => value, + icon: 'vn:inventory', + }, + columnFilter: { + inWhere: true, + }, + }, + { + align: 'left', + label: t('entry.list.tableVisibleColumns.isRaid'), + name: 'isRaid', + chip: { + color: null, + condition: (value) => value, + icon: 'vn:net', + }, + columnFilter: { + inWhere: true, + }, + }, +]); onMounted(async () => { stateStore.rightDrawer = true; }); @@ -42,83 +189,24 @@ onMounted(async () => { <EntryFilter data-key="EntryList" /> </template> </RightMenu> - <QPage class="column items-center q-pa-md"> - <div class="vn-card-list"> - <VnPaginate - data-key="EntryList" - url="Entries/filter" - :order="['landed DESC', 'id DESC']" - auto-load - > - <template #body="{ rows }"> - <CardList - v-for="row of rows" - :key="row.id" - :title="row.reference" - @click="navigate(row.id)" - :id="row.id" - :has-info-icons="!!row.isExcludedFromAvailable || !!row.isRaid" - > - <template #info-icons> - <QIcon - v-if="row.isExcludedFromAvailable" - name="vn:inventory" - color="primary" - size="xs" - > - <QTooltip>{{ t('Inventory entry') }}</QTooltip> - </QIcon> - <QIcon - v-if="row.isRaid" - name="vn:net" - color="primary" - size="xs" - > - <QTooltip>{{ t('Virtual entry') }}</QTooltip> - </QIcon> - </template> - <template #list-items> - <VnLv :label="t('landed')" :value="toDate(row.landed)" /> - <VnLv - :label="t('entry.list.booked')" - :value="!!row.isBooked" - /> - <VnLv - :label="t('entry.list.invoiceNumber')" - :value="row.invoiceNumber" - /> - <VnLv - :label="t('entry.list.confirmed')" - :value="!!row.isConfirmed" - /> - <VnLv - :label="t('entry.list.supplier')" - :value="row.supplierName" - /> - <VnLv - :label="t('entry.list.ordered')" - :value="!!row.isOrdered" - /> - </template> - <template #actions> - <QBtn - :label="t('components.smartCard.openSummary')" - @click.stop="viewSummary(row.id, EntrySummary)" - color="primary" - type="submit" - /> - </template> - </CardList> - </template> - </VnPaginate> - </div> - </QPage> - <QPageSticky :offset="[20, 20]"> - <QBtn fab icon="add" color="primary" @click="redirectToCreateView()" /> - <QTooltip> - {{ t('entry.list.newEntry') }} - </QTooltip> - </QPageSticky> + <VnTable + ref="tableRef" + data-key="EntryList" + url="Entries/filter" + :filter="entryFilter" + :create="{ + urlCreate: 'Entries', + title: 'Create entry', + onDataSaved: ({ id }) => tableRef.redirect(id), + formInitialData: {}, + }" + order="id DESC" + :columns="columns" + redirect="entry" + default-mode="table" + auto-load + :right-search="false" + /> </template> <i18n> diff --git a/src/pages/Entry/MyEntries.vue b/src/pages/Entry/MyEntries.vue new file mode 100644 index 000000000..58a69f70c --- /dev/null +++ b/src/pages/Entry/MyEntries.vue @@ -0,0 +1,106 @@ +<script setup> +import { computed, onMounted } from 'vue'; +import { useI18n } from 'vue-i18n'; +import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; +import { useStateStore } from 'stores/useStateStore'; +import { toDate } from 'src/filters/index'; +import { useQuasar } from 'quasar'; +import EntryBuysTableDialog from './EntryBuysTableDialog.vue'; +import VnTable from 'components/VnTable/VnTable.vue'; + +const stateStore = useStateStore(); +const { t } = useI18n(); +const quasar = useQuasar(); + +onMounted(async () => { + stateStore.rightDrawer = true; +}); + +const columns = computed(() => [ + { + align: 'left', + name: 'id', + label: t('customer.extendedList.tableVisibleColumns.id'), + chip: { + condition: () => true, + }, + isId: true, + isTitle: false, + }, + { + align: 'left', + label: t('shipped'), + name: 'shipped', + isTitle: false, + create: true, + cardVisible: true, + format: ({ shipped }) => toDate(shipped), + }, + { + align: 'left', + label: t('landed'), + name: 'landed', + isTitle: false, + create: true, + cardVisible: false, + format: ({ landed }) => toDate(landed), + }, + { + align: 'left', + label: t('globals.wareHouseIn'), + name: 'warehouseInName', + isTitle: false, + cardVisible: true, + create: false, + }, + { + align: 'right', + name: 'tableActions', + computed, + actions: [ + { + title: t('printBuys'), + icon: 'print', + action: (row) => printBuys(row.id), + }, + ], + }, +]); + +const printBuys = (rowId) => { + quasar.dialog({ + component: EntryBuysTableDialog, + componentProps: { + id: rowId, + }, + }); +}; +</script> +<template> + <VnSearchbar + data-key="EntryList" + url="Entries/filter" + :label="t('Search entries')" + :info="t('You can search by entry reference')" + /> + <QPage class="column items-center q-pa-md"> + <div class="vn-card-list"> + <VnTable + ref="myEntriesRef" + data-key="myEntriesList" + url="Entries/filter" + :order="['landed DESC', 'id DESC']" + :columns="columns" + default-mode="card" + auto-load + > + </VnTable> + </div> + </QPage> +</template> + +<i18n> +es: + Search entries: Buscar entradas + You can search by entry reference: Puedes buscar por referencia de la entrada +</i18n> diff --git a/src/pages/Entry/locale/en.yml b/src/pages/Entry/locale/en.yml index 9a476c80c..677c3c055 100644 --- a/src/pages/Entry/locale/en.yml +++ b/src/pages/Entry/locale/en.yml @@ -8,3 +8,4 @@ entryFilter: reference: Reference landed: Landed shipped: Shipped +printBuys: Print buys diff --git a/src/pages/Entry/locale/es.yml b/src/pages/Entry/locale/es.yml index fba6c5460..10b77c2ee 100644 --- a/src/pages/Entry/locale/es.yml +++ b/src/pages/Entry/locale/es.yml @@ -1,5 +1,6 @@ Search entries: Buscar entradas You can search by entry reference: Puedes buscar por referencia de la entrada + entryList: list: inventoryEntry: Es inventario @@ -11,3 +12,4 @@ entryFilter: landed: F. llegada shipped: F. salida +Print buys: Imprimir etiquetas diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue index a36656c1d..e9ca762ed 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue @@ -1,5 +1,6 @@ <script setup> -import InvoiceInDescriptor from "pages/InvoiceIn/Card/InvoiceInDescriptor.vue"; +import InvoiceInDescriptor from 'pages/InvoiceIn/Card/InvoiceInDescriptor.vue'; +import InvoiceInSummary from './InvoiceInSummary.vue'; const $props = defineProps({ id: { @@ -10,6 +11,10 @@ const $props = defineProps({ </script> <template> <QPopupProxy> - <InvoiceInDescriptor v-if="$props.id" :id="$props.id" /> + <InvoiceInDescriptor + v-if="$props.id" + :id="$props.id" + :summary="InvoiceInSummary" + /> </QPopupProxy> </template> diff --git a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue index 428e7a7a1..cdf012b2a 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue @@ -209,7 +209,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; <QCardSection class="q-pa-none"> <VnTitle :url="getLink('basic-data')" - :text="t('invoiceIn.pageTitles.basicData')" + :text="t('globals.pageTitles.basicData')" /> </QCardSection> <VnLv @@ -240,7 +240,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; <QCardSection class="q-pa-none"> <VnTitle :url="getLink('basic-data')" - :text="t('invoiceIn.pageTitles.basicData')" + :text="t('globals.pageTitles.basicData')" /> </QCardSection> <VnLv @@ -265,7 +265,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; <QCardSection class="q-pa-none"> <VnTitle :url="getLink('basic-data')" - :text="t('invoiceIn.pageTitles.basicData')" + :text="t('globals.pageTitles.basicData')" /> </QCardSection> <VnLv @@ -289,7 +289,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; <QCardSection class="q-pa-none"> <VnTitle :url="getLink('basic-data')" - :text="t('invoiceIn.pageTitles.basicData')" + :text="t('globals.pageTitles.basicData')" /> </QCardSection> <QCardSection class="q-pa-none"> diff --git a/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue b/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue index e96c74689..84a79661e 100644 --- a/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue +++ b/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue @@ -150,21 +150,19 @@ const downloadCSV = async () => { > <template #body-cell-clientId="{ row }"> <QTd> - <QBtn flat dense color="blue"> {{ row.clientId }}</QBtn> + <QBtn flat dense class="link"> {{ row.clientId }}</QBtn> <CustomerDescriptorProxy :id="row.clientId" /> </QTd> </template> <template #body-cell-ticketId="{ row }"> <QTd> - <QBtn flat dense color="blue"> {{ row.ticketFk }}</QBtn> + <QBtn flat dense class="link"> {{ row.ticketFk }}</QBtn> <TicketDescriptorProxy :id="row.ticketFk" /> </QTd> </template> <template #body-cell-worker="{ row }"> <QTd> - <QBtn class="no-uppercase" flat dense color="blue">{{ - row.workerName - }}</QBtn> + <QBtn class="no-uppercase link" flat dense>{{ row.workerName }}</QBtn> <WorkerDescriptorProxy :id="row.comercialId" /> </QTd> </template> diff --git a/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue b/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue index 66b9257a0..9eeac8355 100644 --- a/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue +++ b/src/pages/InvoiceOut/InvoiceOutNegativeBasesFilter.vue @@ -20,6 +20,7 @@ const props = defineProps({ :data-key="props.dataKey" :search-button="true" :unremovable-params="['from', 'to']" + :hidden-tags="['from', 'to']" > <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> diff --git a/src/pages/Item/Card/ItemLastEntries.vue b/src/pages/Item/Card/ItemLastEntries.vue index 9071cc6eb..a3ae99da6 100644 --- a/src/pages/Item/Card/ItemLastEntries.vue +++ b/src/pages/Item/Card/ItemLastEntries.vue @@ -200,16 +200,10 @@ onUnmounted(() => (stateStore.rightDrawer = false)); <VnInputDate :label="t('lastEntries.since')" dense - emit-date-format v-model="from" class="q-mr-lg" /> - <VnInputDate - :label="t('lastEntries.to')" - dense - emit-date-format - v-model="to" - /> + <VnInputDate :label="t('lastEntries.to')" dense v-model="to" /> </div> <QSpace /> <div id="st-actions"></div> diff --git a/src/pages/Item/ItemRequestFilter.vue b/src/pages/Item/ItemRequestFilter.vue index 2c056acc6..2fbe849d1 100644 --- a/src/pages/Item/ItemRequestFilter.vue +++ b/src/pages/Item/ItemRequestFilter.vue @@ -192,7 +192,6 @@ const decrement = (paramsObj, key) => { v-model="params.from" @update:model-value="searchFn()" is-outlined - emit-date-format /> </QItemSection> </QItem> @@ -203,7 +202,6 @@ const decrement = (paramsObj, key) => { v-model="params.to" @update:model-value="searchFn()" is-outlined - emit-date-format /> </QItemSection> </QItem> diff --git a/src/pages/ItemType/Card/ItemTypeDescriptor.vue b/src/pages/ItemType/Card/ItemTypeDescriptor.vue index e565a791b..cd12fc238 100644 --- a/src/pages/ItemType/Card/ItemTypeDescriptor.vue +++ b/src/pages/ItemType/Card/ItemTypeDescriptor.vue @@ -51,20 +51,6 @@ const setData = (entity) => (data.value = useCardDescription(entity.code, entity @on-fetch="setData" data-key="entry" > - <template #header-extra-action> - <QBtn - round - flat - size="sm" - icon="vn:item" - color="white" - :to="{ name: 'ItemTypeList' }" - > - <QTooltip> - {{ t('Go to module index') }} - </QTooltip> - </QBtn> - </template> <template #body="{ entity }"> <VnLv :label="t('shared.code')" :value="entity.code" /> <VnLv :label="t('shared.name')" :value="entity.name" /> diff --git a/src/pages/Monitor/SalesClientsTable.vue b/src/pages/Monitor/SalesClientsTable.vue index 3f2389471..8008b1fda 100644 --- a/src/pages/Monitor/SalesClientsTable.vue +++ b/src/pages/Monitor/SalesClientsTable.vue @@ -169,7 +169,6 @@ const columns = computed(() => [ <VnInputDate :label="t('salesClientsTable.from')" dense - emit-date-format v-model="from" class="q-mr-lg" style="width: 150px" @@ -177,7 +176,6 @@ const columns = computed(() => [ <VnInputDate :label="t('salesClientsTable.to')" dense - emit-date-format v-model="to" style="width: 150px" /> diff --git a/src/pages/Order/Card/OrderCatalogFilter.vue b/src/pages/Order/Card/OrderCatalogFilter.vue index fd5300122..c354ec94b 100644 --- a/src/pages/Order/Card/OrderCatalogFilter.vue +++ b/src/pages/Order/Card/OrderCatalogFilter.vue @@ -37,6 +37,10 @@ const selectedOrder = ref(null); const selectedOrderField = ref(null); const moreFields = ref([]); const moreFieldsOrder = ref([]); +const selectedTag = ref(null); +const tagValues = ref([{}]); +const tagOptions = ref([]); + const createValue = (val, done) => { if (val.length > 2) { if (!tagOptions.value.includes(val)) { @@ -95,10 +99,6 @@ function exprBuilder(param, value) { } } -const selectedTag = ref(null); -const tagValues = ref([{}]); -const tagOptions = ref([]); - const applyTagFilter = (params, search) => { if (!tagValues.value?.length) { params.tagGroups = null; @@ -139,39 +139,23 @@ const onOrderChange = (value, params) => { }; const onOrderFieldChange = (value, params) => { - const tagObj = JSON.parse(params.orderBy); // esto donde va - const fields = { - 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); + const tagObj = JSON.parse(params.orderBy); switch (value) { case 'Relevancy': - tagObj.field = value + ' DESC, name'; + tagObj.name = value + ' DESC, name'; params.orderBy = JSON.stringify(tagObj); - console.log('params: ', params); break; case 'ColorAndPrice': - tagObj.field = 'showOrder, price'; + tagObj.name = 'showOrder, price'; params.orderBy = JSON.stringify(tagObj); - console.log('params: ', params); break; case 'Name': - tagObj.field = 'name'; + tagObj.name = 'name'; params.orderBy = JSON.stringify(tagObj); - console.log('params: ', params); break; case 'Price': - tagObj.field = 'price'; + tagObj.name = 'price'; params.orderBy = JSON.stringify(tagObj); - console.log('params: ', params); break; } }; @@ -312,6 +296,7 @@ const useLang = (values) => { v-model="selectedOrder" :options="moreFields" option-label="label" + option-value="way" dense outlined rounded diff --git a/src/pages/Order/Card/OrderCatalogItem.vue b/src/pages/Order/Card/OrderCatalogItem.vue index 34e22915d..8fa0bb5b9 100644 --- a/src/pages/Order/Card/OrderCatalogItem.vue +++ b/src/pages/Order/Card/OrderCatalogItem.vue @@ -27,7 +27,7 @@ const dialog = ref(null); <div class="container order-catalog-item overflow-hidden"> <QCard class="card shadow-6"> <div class="img-wrapper"> - <VnImg :id="item.id" class="image" /> + <VnImg :id="item.id" zoom-size="lg" class="image" /> <div v-if="item.hex" class="item-color-container"> <div class="item-color" diff --git a/src/pages/Order/Card/OrderForm.vue b/src/pages/Order/Card/OrderForm.vue index 20b29cd9c..98bcf4d3a 100644 --- a/src/pages/Order/Card/OrderForm.vue +++ b/src/pages/Order/Card/OrderForm.vue @@ -122,8 +122,6 @@ 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); diff --git a/src/pages/Order/Card/OrderSummary.vue b/src/pages/Order/Card/OrderSummary.vue index d82dbaa17..7961b250e 100644 --- a/src/pages/Order/Card/OrderSummary.vue +++ b/src/pages/Order/Card/OrderSummary.vue @@ -7,6 +7,8 @@ import VnLv from 'components/ui/VnLv.vue'; import CardSummary from 'components/ui/CardSummary.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import FetchedTags from 'components/ui/FetchedTags.vue'; +import VnTitle from 'src/components/common/VnTitle.vue'; +import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; const { t } = useI18n(); const route = useRoute(); @@ -62,6 +64,10 @@ const detailsColumns = ref([ </template> <template #body="{ entity }"> <QCard class="vn-one"> + <VnTitle + :url="`#/order/${entity.id}/basic-data`" + :text="t('globals.pageTitles.basicData')" + /> <VnLv label="ID" :value="entity.id" /> <VnLv :label="t('order.summary.nickname')" dash> <template #value> @@ -81,6 +87,10 @@ const detailsColumns = ref([ /> </QCard> <QCard class="vn-one"> + <VnTitle + :url="`#/order/${entity.id}/basic-data`" + :text="t('globals.pageTitles.basicData')" + /> <VnLv :label="t('order.summary.created')" :value="toDateHourMinSec(entity?.created)" @@ -116,14 +126,13 @@ const detailsColumns = ref([ /> </QCard> <QCard class="vn-one"> - <p class="header"> - {{ t('order.summary.notes') }} - </p> + <VnTitle :text="t('globals.pageTitles.notes')" /> <p v-if="entity?.note" class="no-margin"> {{ entity?.note }} </p> </QCard> <QCard class="vn-one"> + <VnTitle :text="t('order.summary.total')" /> <VnLv> <template #label> <span class="text-h6">{{ t('order.summary.subtotal') }}</span> @@ -152,9 +161,7 @@ const detailsColumns = ref([ </VnLv> </QCard> <QCard> - <p class="header"> - {{ t('order.summary.details') }} - </p> + <VnTitle :text="t('order.summary.details')" /> <QTable :columns="detailsColumns" :rows="entity?.rows" flat> <template #header="props"> <QTr :props="props"> @@ -168,7 +175,10 @@ const detailsColumns = ref([ <template #body="props"> <QTr :props="props"> <QTd key="item" :props="props" class="item"> - {{ props.row.item?.id }} + <span class="link"> + {{ props.row.item?.id }} + <ItemDescriptorProxy :id="props.row.item?.id" /> + </span> </QTd> <QTd key="description" :props="props" class="description"> <div class="name"> diff --git a/src/pages/Order/OrderCatalog.vue b/src/pages/Order/OrderCatalog.vue index 1d97663d0..081a2cd2f 100644 --- a/src/pages/Order/OrderCatalog.vue +++ b/src/pages/Order/OrderCatalog.vue @@ -70,6 +70,7 @@ function extractValueTags(items) { :user-params="catalogParams" auto-load @on-fetch="extractTags" + :update-router="false" > <template #body="{ rows }"> <div class="catalog-list"> diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue index 247a8e3b2..0e6e11482 100644 --- a/src/pages/Order/OrderList.vue +++ b/src/pages/Order/OrderList.vue @@ -9,7 +9,7 @@ import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import VnPaginate from 'components/ui/VnPaginate.vue'; import VnLv from 'components/ui/VnLv.vue'; -import OrderSearchbar from 'pages/Order/Card/OrderSearchbar.vue'; +import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import OrderFilter from 'pages/Order/Card/OrderFilter.vue'; import OrderSummary from 'pages/Order/Card/OrderSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; @@ -28,7 +28,11 @@ function navigate(id) { } </script> <template> - <OrderSearchbar /> + <VnSearchbar + data-key="OrderList" + :label="t('Search order')" + :info="t('You can search orders by reference')" + /> <RightMenu> <template #right-panel> <OrderFilter data-key="OrderList" /> diff --git a/src/pages/Parking/Card/ParkingDescriptor.vue b/src/pages/Parking/Card/ParkingDescriptor.vue index b5c8820ed..b57bfb0cc 100644 --- a/src/pages/Parking/Card/ParkingDescriptor.vue +++ b/src/pages/Parking/Card/ParkingDescriptor.vue @@ -2,7 +2,6 @@ import { computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; -import { useArrayData } from 'src/composables/useArrayData'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'components/ui/VnLv.vue'; @@ -17,8 +16,7 @@ const props = defineProps({ const { t } = useI18n(); const route = useRoute(); const entityId = computed(() => props.id || route.params.id); -const { store } = useArrayData('Parking'); -const parking = computed(() => store.data); + const filter = { fields: ['id', 'sectorFk', 'code', 'pickingOrder', 'row', 'column'], include: [{ relation: 'sector', scope: { fields: ['id', 'description'] } }], @@ -29,15 +27,13 @@ const filter = { module="Parking" data-key="Parking" :url="`Parkings/${entityId}`" - :title="parking?.code" - :subtitle="parking?.id" + title="code" :filter="filter" - @on-fetch="(data) => (parking = data)" > - <template #body> - <VnLv :label="t('globals.code')" :value="parking.code" /> - <VnLv :label="t('parking.pickingOrder')" :value="parking.pickingOrder" /> - <VnLv :label="t('parking.sector')" :value="parking.sector?.description" /> + <template #body="{ entity }"> + <VnLv :label="t('globals.code')" :value="entity.code" /> + <VnLv :label="t('parking.pickingOrder')" :value="entity.pickingOrder" /> + <VnLv :label="t('parking.sector')" :value="entity.sector?.description" /> </template> </CardDescriptor> </template> diff --git a/src/pages/Parking/Card/ParkingSummary.vue b/src/pages/Parking/Card/ParkingSummary.vue index 7406856b9..95620ebfd 100644 --- a/src/pages/Parking/Card/ParkingSummary.vue +++ b/src/pages/Parking/Card/ParkingSummary.vue @@ -1,10 +1,9 @@ <script setup> -import { ref, computed } from 'vue'; +import { computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'components/ui/VnLv.vue'; -import { useArrayData } from 'src/composables/useArrayData'; const $props = defineProps({ id: { @@ -15,9 +14,7 @@ const $props = defineProps({ const router = useRoute(); const { t } = useI18n(); const entityId = computed(() => $props.id || router.params.id); -const { store } = useArrayData('Parking'); -const parking = ref(store.data); const filter = { fields: ['id', 'sectorFk', 'code', 'pickingOrder', 'row', 'column'], include: [{ relation: 'sector', scope: { fields: ['id', 'description'] } }], @@ -26,14 +23,9 @@ const filter = { <template> <div class="q-pa-md"> - <CardSummary - :url="`Parkings/${entityId}`" - :filter="filter" - @on-fetch="(data) => (parking = data)" - data-key="Parking" - > - <template #header>{{ parking.code }}</template> - <template #body> + <CardSummary :url="`Parkings/${entityId}`" data-key="Parking" :filter="filter"> + <template #header="{ entity }">{{ entity.code }}</template> + <template #body="{ entity }"> <QCard class="vn-one"> <QCardSection class="q-pa-none"> <a @@ -44,17 +36,17 @@ const filter = { <QIcon name="open_in_new" /> </a> </QCardSection> - <VnLv :label="t('globals.code')" :value="parking.code" /> + <VnLv :label="t('globals.code')" :value="entity.code" /> <VnLv :label="t('parking.pickingOrder')" - :value="parking.pickingOrder" + :value="entity.pickingOrder" /> <VnLv :label="t('parking.sector')" - :value="parking.sector?.description" + :value="entity.sector?.description" /> - <VnLv :label="t('parking.row')" :value="parking.row" /> - <VnLv :label="t('parking.column')" :value="parking.column" /> + <VnLv :label="t('parking.row')" :value="entity.row" /> + <VnLv :label="t('parking.column')" :value="entity.column" /> </QCard> </template> </CardSummary> diff --git a/src/pages/Route/Agency/AgencyList.vue b/src/pages/Route/Agency/AgencyList.vue new file mode 100644 index 000000000..eb301c9ba --- /dev/null +++ b/src/pages/Route/Agency/AgencyList.vue @@ -0,0 +1,92 @@ +<script setup> +import { computed } from 'vue'; +import { useRouter } from 'vue-router'; +import { useI18n } from 'vue-i18n'; +import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; +import VnTable from 'components/VnTable/VnTable.vue'; + +const { t } = useI18n(); +const router = useRouter(); +function navigate(id) { + router.push({ path: `/agency/${id}` }); +} +const exprBuilder = (param, value) => { + if (!value) return; + if (param !== 'search') return; + if (!isNaN(value)) return { id: value }; + + return { name: { like: `%${value}%` } }; +}; + +const columns = computed(() => [ + { + align: 'left', + name: 'id', + label: 'Id', + chip: { + condition: () => true, + }, + isId: true, + }, + { + align: 'left', + label: t('globals.name'), + name: 'name', + isTitle: true, + }, + { + align: 'left', + label: t('isOwn'), + name: 'isOwn', + component: 'checkbox', + cardVisible: true, + }, + { + align: 'left', + label: t('isAnyVolumeAllowed'), + name: 'isAnyVolumeAllowed', + component: 'checkbox', + cardVisible: true, + disable: true, + }, + { + align: 'right', + label: '', + name: 'tableActions', + actions: [ + { + title: t('Client ticket list'), + icon: 'preview', + action: (row) => navigate(row.id), + }, + ], + }, +]); +</script> +<template> + <VnSearchbar + :info="t('You can search by name')" + :label="t('Search agency')" + data-key="AgencyList" + :expr-builder="exprBuilder" + /> + <VnTable + ref="tableRef" + data-key="AgencyList" + url="Agencies" + order="name" + :columns="columns" + :right-search="false" + :use-model="true" + /> +</template> +<i18n> + es: + isOwn: Tiene propietario + isAnyVolumeAllowed: Permite cualquier volumen + Search agency: Buscar agencia + You can search by name: Puedes buscar por nombre + en: + isOwn: Has owner + isAnyVolumeAllowed: Allows any volume +</i18n> diff --git a/src/pages/Agency/Card/AgencyBasicData.vue b/src/pages/Route/Agency/Card/AgencyBasicData.vue similarity index 100% rename from src/pages/Agency/Card/AgencyBasicData.vue rename to src/pages/Route/Agency/Card/AgencyBasicData.vue diff --git a/src/pages/Agency/Card/AgencyCard.vue b/src/pages/Route/Agency/Card/AgencyCard.vue similarity index 82% rename from src/pages/Agency/Card/AgencyCard.vue rename to src/pages/Route/Agency/Card/AgencyCard.vue index 6b2296df3..e1eebabf7 100644 --- a/src/pages/Agency/Card/AgencyCard.vue +++ b/src/pages/Route/Agency/Card/AgencyCard.vue @@ -1,5 +1,5 @@ <script setup> -import AgencyDescriptor from 'pages/Agency/Card/AgencyDescriptor.vue'; +import AgencyDescriptor from 'pages/Route/Agency/Card/AgencyDescriptor.vue'; import VnCard from 'components/common/VnCard.vue'; </script> <template> diff --git a/src/pages/Agency/Card/AgencyDescriptor.vue b/src/pages/Route/Agency/Card/AgencyDescriptor.vue similarity index 100% rename from src/pages/Agency/Card/AgencyDescriptor.vue rename to src/pages/Route/Agency/Card/AgencyDescriptor.vue diff --git a/src/pages/Agency/Card/AgencyLog.vue b/src/pages/Route/Agency/Card/AgencyLog.vue similarity index 100% rename from src/pages/Agency/Card/AgencyLog.vue rename to src/pages/Route/Agency/Card/AgencyLog.vue diff --git a/src/pages/Agency/Card/AgencyModes.vue b/src/pages/Route/Agency/Card/AgencyModes.vue similarity index 100% rename from src/pages/Agency/Card/AgencyModes.vue rename to src/pages/Route/Agency/Card/AgencyModes.vue diff --git a/src/pages/Agency/Card/AgencySummary.vue b/src/pages/Route/Agency/Card/AgencySummary.vue similarity index 81% rename from src/pages/Agency/Card/AgencySummary.vue rename to src/pages/Route/Agency/Card/AgencySummary.vue index f9a1c5ea0..71a6d1066 100644 --- a/src/pages/Agency/Card/AgencySummary.vue +++ b/src/pages/Route/Agency/Card/AgencySummary.vue @@ -18,12 +18,10 @@ const entityId = computed(() => $props.id || useRoute().params.id); <template #header="{ entity: agency }">{{ agency.name }}</template> <template #body="{ entity: agency }"> <QCard class="vn-one"> - <QCardSection class="q-pa-none"> - <VnTitle - :url="`#/agency/${entityId}/basic-data`" - :text="t('globals.pageTitles.basicData')" - /> - </QCardSection> + <VnTitle + :url="`#/agency/${entityId}/basic-data`" + :text="t('globals.pageTitles.basicData')" + /> <VnLv :label="t('globals.name')" :value="agency.name" /> <QCheckbox :label="t('agency.isOwn')" diff --git a/src/pages/Agency/Card/AgencyWorkcenter.vue b/src/pages/Route/Agency/Card/AgencyWorkcenter.vue similarity index 100% rename from src/pages/Agency/Card/AgencyWorkcenter.vue rename to src/pages/Route/Agency/Card/AgencyWorkcenter.vue diff --git a/src/pages/Agency/locale/en.yml b/src/pages/Route/Agency/locale/en.yml similarity index 100% rename from src/pages/Agency/locale/en.yml rename to src/pages/Route/Agency/locale/en.yml diff --git a/src/pages/Agency/locale/es.yml b/src/pages/Route/Agency/locale/es.yml similarity index 100% rename from src/pages/Agency/locale/es.yml rename to src/pages/Route/Agency/locale/es.yml diff --git a/src/pages/Route/Card/RouteDescriptor.vue b/src/pages/Route/Card/RouteDescriptor.vue index 218764277..2fa0e77e0 100644 --- a/src/pages/Route/Card/RouteDescriptor.vue +++ b/src/pages/Route/Card/RouteDescriptor.vue @@ -74,8 +74,9 @@ const setData = (entity) => (data.value = useCardDescription(entity.code, entity :filter="filter" :title="data.title" :subtitle="data.subtitle" - data-key="Routes" + data-key="routeData" @on-fetch="setData" + :summary="$attrs" > <template #body="{ entity }"> <VnLv :label="t('Date')" :value="toDate(entity?.created)" /> diff --git a/src/pages/Route/Card/RouteDescriptorProxy.vue b/src/pages/Route/Card/RouteDescriptorProxy.vue index 541f9716a..1ff39a51e 100644 --- a/src/pages/Route/Card/RouteDescriptorProxy.vue +++ b/src/pages/Route/Card/RouteDescriptorProxy.vue @@ -1,5 +1,6 @@ <script setup> import RouteDescriptor from 'pages/Route/Card/RouteDescriptor.vue'; +import RouteSummary from './RouteSummary.vue'; const $props = defineProps({ id: { @@ -10,6 +11,6 @@ const $props = defineProps({ </script> <template> <QPopupProxy> - <RouteDescriptor v-if="$props.id" :id="$props.id" /> + <RouteDescriptor v-if="$props.id" :id="$props.id" :summary="RouteSummary" /> </QPopupProxy> </template> diff --git a/src/pages/Route/Card/RouteSearchbar.vue b/src/pages/Route/Card/RouteSearchbar.vue index 924280580..0e5c1643d 100644 --- a/src/pages/Route/Card/RouteSearchbar.vue +++ b/src/pages/Route/Card/RouteSearchbar.vue @@ -9,7 +9,6 @@ const { t } = useI18n(); data-key="RouteList" :label="t('Search route')" :info="t('You can search by route reference')" - custom-route-redirect-name="RouteList" /> </template> diff --git a/src/pages/Route/Cmr/CmrFilter.vue b/src/pages/Route/Cmr/CmrFilter.vue index 32040ddd6..bea58b2d8 100644 --- a/src/pages/Route/Cmr/CmrFilter.vue +++ b/src/pages/Route/Cmr/CmrFilter.vue @@ -154,7 +154,7 @@ const warehouses = ref(); es: params: cmrFk: Id cmr - hasCmrDms: Adjuntado en gestdoc + hasCmrDms: Gestdoc ticketFk: Id ticket country: País clientFk: Id cliente diff --git a/src/pages/Route/Cmr/CmrList.vue b/src/pages/Route/Cmr/CmrList.vue index 327b37ac5..74a070cbf 100644 --- a/src/pages/Route/Cmr/CmrList.vue +++ b/src/pages/Route/Cmr/CmrList.vue @@ -12,6 +12,8 @@ import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy import RightMenu from 'src/components/common/RightMenu.vue'; import { useStateStore } from 'src/stores/useStateStore'; +import VnTable from 'components/VnTable/VnTable.vue'; + const { t } = useI18n(); const { getTokenMultimedia } = useSession(); const token = getTokenMultimedia(); @@ -107,88 +109,15 @@ function downloadPdfs() { } </script> <template> - <RightMenu> - <template #right-panel> - <CmrFilter data-key="CmrList" /> - </template> - </RightMenu> - <div class="column items-center"> - <div class="list"> - <VnPaginate data-key="CmrList" :url="`Routes/cmrs`" order="cmrFk DESC"> - <template #body="{ rows }"> - <QTable - :columns="columns" - :rows="rows" - :dense="$q.screen.lt.md" - row-key="cmrFk" - selection="multiple" - v-model:selected="selected" - :grid="$q.screen.lt.md" - auto-load - > - <template #top> - <div style="width: 100%; display: table"> - <div style="float: right; color: lightgray"> - {{ `${rows.length} ${t('route.cmr.list.results')}` }} - </div> - </div> - </template> - <template #body-cell-hasCmrDms="{ value }"> - <QTd align="center"> - <QBadge - text-color="black" - :id="value ? 'true' : 'false'" - :label=" - value - ? t('route.cmr.list.true') - : t('route.cmr.list.false') - " - /> - </QTd> - </template> - <template #body-cell-ticketFk="{ value }"> - <QTd align="right" class="text-primary"> - <span class="text-primary link">{{ value }}</span> - <TicketDescriptorProxy :id="value" /> - </QTd> - </template> - <template #body-cell-clientFk="{ value }"> - <QTd align="right" class="text-primary"> - <span class="text-primary link">{{ value }}</span> - <CustomerDescriptorProxy :id="value" /> - </QTd> - </template> - <template #body-cell-warehouseFk="{ value }"> - <QTd align="center"> - {{ warehouses.find(({ id }) => id === value)?.name }} - </QTd> - </template> - <template #body-cell-icons="{ value }"> - <QTd align="center"> - <a :href="getCmrUrl(value)" target="_blank"> - <QIcon - name="visibility" - color="primary" - size="md" - class="q-mr-sm q-ml-sm" - /> - <QTooltip> - {{ t('route.cmr.list.viewCmr') }} - </QTooltip> - </a> - </QTd> - </template> - </QTable> - </template> - </VnPaginate> - </div> - <QPageSticky :offset="[20, 20]"> - <QBtn @click="downloadPdfs" fab icon="cloud_download" color="primary" /> - <QTooltip> - {{ t('route.cmr.list.downloadCmrs') }} - </QTooltip> - </QPageSticky> - </div> + <VnTable + ref="tableRef" + data-key="CmrList" + url="Routes/cmrs" + order="cmrFk DESC" + :columns="columns" + :right-search="true" + :use-model="true" + /> </template> <style lang="scss" scoped> .list { diff --git a/src/pages/Route/RouteList.vue b/src/pages/Route/RouteList.vue index de60d164e..e116fd823 100644 --- a/src/pages/Route/RouteList.vue +++ b/src/pages/Route/RouteList.vue @@ -1,29 +1,32 @@ <script setup> -import { useI18n } from 'vue-i18n'; -import { computed, onMounted, ref } from 'vue'; +import { useArrayData } from 'composables/useArrayData'; import { dashIfEmpty, toHour } from 'src/filters'; +import { computed, onMounted, ref } from 'vue'; +import { useI18n } from 'vue-i18n'; 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 { useRouter } from 'vue-router'; +import TableVisibleColumns from 'src/components/common/TableVisibleColumns.vue'; +import RouteFilter from 'pages/Route/Card/RouteFilter.vue'; import RightMenu from 'src/components/common/RightMenu.vue'; +import RouteSummary from 'pages/Route/Card/RouteSummary.vue'; import VnPaginate from 'components/ui/VnPaginate.vue'; -import VnSelect from 'components/common/VnSelect.vue'; -import VnInputDate from 'components/common/VnInputDate.vue'; import VnInput from 'components/common/VnInput.vue'; import VnInputTime from 'components/common/VnInputTime.vue'; -import VnLv from 'src/components/ui/VnLv.vue'; +import VnSelect from 'components/common/VnSelect.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; +import VnInputDate from 'components/common/VnInputDate.vue'; +import VnLv from 'src/components/ui/VnLv.vue'; +import VnTable from 'components/VnTable/VnTable.vue'; +import { useStateStore } from 'src/stores/useStateStore'; const { t } = useI18n(); const { validate } = useValidator(); @@ -40,90 +43,100 @@ const allColumnNames = ref([]); const confirmationDialog = ref(false); const startingDate = ref(null); const refreshKey = ref(0); - +const router = useRouter(); +const stateStore = useStateStore(); const columns = computed(() => [ { - name: 'Id', - label: t('Id'), - field: (row) => row.id, - sortable: true, - align: 'center', + align: 'left', + name: 'id', + label: 'Id', + chip: { + condition: () => true, + }, + isId: true, + columnFilter: { + name: 'search', + }, }, { + align: 'left', name: 'worker', label: t('Worker'), - field: (row) => row.workerUserName, - sortable: true, - align: 'left', + create: true, + component: 'select', + attrs: { + url: 'payrollComponents', + fields: ['id', 'name'], + }, + cardVisible: true, }, { name: 'agency', label: t('Agency'), - field: (row) => row.agencyName, - sortable: true, align: 'left', + isTitle: true, + cardVisible: true, + create: true, }, { name: 'vehicle', label: t('Vehicle'), - field: (row) => row.vehiclePlateNumber, - sortable: true, align: 'left', + cardVisible: true, + create: true, }, { name: 'date', label: t('Date'), - field: (row) => row.created, - sortable: true, align: 'left', + cardVisible: true, + create: true, }, { name: 'volume', label: 'm³', - field: (row) => dashIfEmpty(row.m3), - sortable: true, align: 'center', + cardVisible: true, }, { name: 'description', label: t('Description'), - field: (row) => row.description, - sortable: true, align: 'left', + isTitle: true, + create: true, }, { name: 'started', label: t('hourStarted'), - field: (row) => toHour(row.started), - sortable: true, align: 'left', }, { name: 'finished', label: t('hourFinished'), - field: (row) => toHour(row.finished), - sortable: true, align: 'left', }, { name: 'isServed', label: t('Served'), - field: (row) => Boolean(row.isOk), - sortable: true, align: 'left', }, { - name: 'actions', - label: '', - sortable: false, align: 'right', + label: 'asdasd', + name: 'tableActions', + actions: [ + { + title: t('Client ticket list'), + icon: 'preview', + action: (row) => navigate(row.id), + }, + ], }, ]); -const arrayData = useArrayData('EntryLatestBuys', { - url: 'Buys/latestBuysFilter', - order: ['itemFk DESC'], -}); +function navigate(id) { + router.push({ path: `/route/${id}` }); +} const updateRoute = async (route) => { try { @@ -181,20 +194,10 @@ const openTicketsDialog = (id) => { }) .onOk(() => refreshKey.value++); }; - -onMounted(async () => { - allColumnNames.value = columns.value.map((col) => col.name); - await arrayData.fetch({ append: false }); -}); </script> <template> <RouteSearchbar /> - <RightMenu> - <template #right-panel> - <RouteFilter data-key="RouteList" /> - </template> - </RightMenu> <QDialog v-model="confirmationDialog"> <QCard style="min-width: 350px"> <QCardSection> @@ -227,256 +230,53 @@ onMounted(async () => { /> <FetchData url="AgencyModes" @on-fetch="(data) => (agencyList = data)" auto-load /> <FetchData url="Vehicles" @on-fetch="(data) => (vehicleList = data)" auto-load /> - <QPage class="column items-center"> - <VnSubToolbar> - <template #st-data> - <TableVisibleColumns - class="LeftIcon" - :all-columns="allColumnNames" - table-code="routesList" - labels-traductions-path="route.columnLabels" - @on-config-saved="visibleColumns = [...$event]" - /> - </template> - <template #st-actions> - <QBtn - icon="vn:clone" - color="primary" - class="q-mr-sm" - :disable="!selectedRows?.length" - @click="confirmationDialog = true" - > - <QTooltip>{{ t('Clone Selected Routes') }}</QTooltip> - </QBtn> - <QBtn - icon="cloud_download" - color="primary" - class="q-mr-sm" - :disable="!selectedRows?.length" - @click="showRouteReport" - > - <QTooltip>{{ t('Download selected routes as PDF') }}</QTooltip> - </QBtn> - <QBtn - icon="check" - color="primary" - class="q-mr-sm" - :disable="!selectedRows?.length" - @click="markAsServed()" - > - <QTooltip>{{ t('Mark as served') }}</QTooltip> - </QBtn> - </template> - </VnSubToolbar> - <div class="route-list"> - <VnPaginate - :key="refreshKey" - data-key="RouteList" - url="Routes/filter" - :order="['created ASC', 'started ASC', 'id ASC']" - :limit="20" + <VnSubToolbar /> + <VnTable + ref="tableRef" + data-key="RouteList" + url="Routes/filter" + :columns="columns" + :right-search="true" + default-mode="table" + :is-editable="true" + :create="{ + urlCreate: 'Routes', + title: t('Create route'), + onDataSaved: () => tableRef.reload(), + }" + save-url="routes" + :disable-option="{ card: true }" + > + <template #moreBeforeActions> + <QBtn + icon="vn:clone" + color="primary" + class="q-mr-sm" + :disable="!selectedRows?.length" + @click="confirmationDialog = true" > - <template #body="{ rows }"> - <div class="q-pa-md route-table"> - <QTable - v-model:selected="selectedRows" - :columns="columns" - :rows="rows" - flat - row-key="id" - selection="multiple" - :rows-per-page-options="[0]" - :visible-columns="visibleColumns" - hide-pagination - :no-data-label="t('globals.noResults')" - style="max-height: 82vh" - > - <template #body-cell-worker="{ row }"> - <QTd class="table-input-cell"> - <VnSelect - :label="t('Worker')" - v-model="row.workerFk" - :options="workers" - option-value="id" - option-label="nickname" - hide-selected - dense - :emit-value="false" - :rules="validate('Route.workerFk')" - :is-clearable="false" - @update:model-value="updateRoute(row)" - > - <template #option="{ opt, itemProps }"> - <QItem - v-bind="itemProps" - class="q-pa-xs row items-center" - > - <QItemSection - class="col-9 justify-center" - > - <span>{{ opt.name }}</span> - <span class="text-grey">{{ - opt.nickname - }}</span> - </QItemSection> - </QItem> - </template> - </VnSelect> - </QTd> - </template> - <template #body-cell-agency="{ row }"> - <QTd class="table-input-cell"> - <VnSelect - :label="t('Agency')" - v-model="row.agencyModeFk" - :options="agencyList" - option-value="id" - option-label="name" - hide-selected - dense - :emit-value="false" - :rules="validate('route.agencyFk')" - :is-clearable="false" - @update:model-value="updateRoute(row)" - /> - </QTd> - </template> - <template #body-cell-vehicle="{ row }"> - <QTd class="table-input-cell small-column"> - <VnSelect - :label="t('Vehicle')" - v-model="row.vehicleFk" - :options="vehicleList" - option-value="id" - option-label="numberPlate" - hide-selected - dense - :emit-value="false" - :rules="validate('route.vehicleFk')" - :is-clearable="false" - @update:model-value="updateRoute(row)" - /> - </QTd> - </template> - <template #body-cell-date="{ row }"> - <QTd class="table-input-cell small-column"> - <VnInputDate - v-model="row.created" - hide-bottom-space - dense - :label="t('Date')" - :rules="validate('route.created')" - :is-clearable="false" - @update:model-value="updateRoute(row)" - /> - </QTd> - </template> - <template #body-cell-description="{ row }"> - <QTd class="table-input-cell"> - <VnInput - v-model="row.description" - :label="t('Description')" - :rules="validate('route.description')" - :is-clearable="false" - dense - @update:model-value="updateRoute(row)" - /> - </QTd> - </template> - <template #body-cell-started="{ row }"> - <QTd class="table-input-cell small-column"> - <VnInputTime - v-model="row.started" - :label="t('hourStarted')" - :rules="validate('route.started')" - :is-clearable="false" - hide-bottom-space - dense - @update:model-value="updateRoute(row)" - /> - </QTd> - </template> - <template #body-cell-finished="{ row }"> - <QTd class="table-input-cell small-column"> - <VnInputTime - v-model="row.finished" - autofocus - :label="t('hourFinished')" - :rules="validate('route.finished')" - :is-clearable="false" - hide-bottom-space - dense - @update:model-value="updateRoute(row)" - /> - </QTd> - </template> - <template #body-cell-isServed="props"> - <QTd class="table-input-cell small-column"> - <QCheckbox v-model="props.value" disable> - <QTooltip> - {{ - props.value - ? t('Route is closed') - : t('Route is not served') - }} - </QTooltip> - </QCheckbox> - </QTd> - </template> - <template #body-cell-actions="props"> - <QTd :props="props"> - <div class="flex items-center no-wrap table-actions"> - <QIcon - name="vn:ticketAdd" - size="sm" - color="primary" - class="cursor-pointer" - @click="openTicketsDialog(props?.row?.id)" - > - <QTooltip>{{ t('Add ticket') }}</QTooltip> - </QIcon> - <QIcon - name="preview" - size="sm" - color="primary" - @click=" - viewSummary(props?.row?.id, RouteSummary) - " - class="cursor-pointer" - > - <QTooltip>{{ t('Preview') }}</QTooltip> - </QIcon> - <RouterLink - :to="{ - name: 'RouteSummary', - params: { id: props?.row?.id }, - }" - > - <QIcon - name="vn:eye" - size="xs" - color="primary" - > - <QTooltip>{{ t('Summary') }}</QTooltip> - </QIcon> - </RouterLink> - </div> - </QTd> - </template> - </QTable> - </div> - </template> - </VnPaginate> - </div> - <QPageSticky :offset="[20, 20]"> - <RouterLink :to="{ name: 'RouteCreate' }"> - <QBtn fab icon="add" color="primary" /> - <QTooltip> - {{ t('newRoute') }} - </QTooltip> - </RouterLink> - </QPageSticky> - </QPage> + <QTooltip>{{ t('Clone Selected Routes') }}</QTooltip> + </QBtn> + <QBtn + icon="cloud_download" + color="primary" + class="q-mr-sm" + :disable="!selectedRows?.length" + @click="showRouteReport" + > + <QTooltip>{{ t('Download selected routes as PDF') }}</QTooltip> + </QBtn> + <QBtn + icon="check" + color="primary" + class="q-mr-sm" + :disable="!selectedRows?.length" + @click="markAsServed()" + > + <QTooltip>{{ t('Mark as served') }}</QTooltip> + </QBtn> + </template> + </VnTable> </template> <style lang="scss" scoped> diff --git a/src/pages/Route/RouteMain.vue b/src/pages/Route/RouteMain.vue index bbf19068e..aace646fb 100644 --- a/src/pages/Route/RouteMain.vue +++ b/src/pages/Route/RouteMain.vue @@ -2,7 +2,7 @@ import { useStateStore } from 'stores/useStateStore'; import LeftMenu from 'src/components/LeftMenu.vue'; import { onMounted } from 'vue'; - +import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; const stateStore = useStateStore(); onMounted(() => (stateStore.leftDrawer = false)); </script> diff --git a/src/pages/Shelving/Card/ShelvingDescriptor.vue b/src/pages/Shelving/Card/ShelvingDescriptor.vue index 6beca6d88..52b4896ad 100644 --- a/src/pages/Shelving/Card/ShelvingDescriptor.vue +++ b/src/pages/Shelving/Card/ShelvingDescriptor.vue @@ -51,6 +51,7 @@ const setData = (entity) => (data.value = useCardDescription(entity.code, entity :subtitle="data.subtitle" data-key="Shelvings" @on-fetch="setData" + :summary="$attrs" > <template #body="{ entity }"> <VnLv :label="t('shelving.summary.code')" :value="entity.code" /> diff --git a/src/pages/Shelving/Card/ShelvingDescriptorProxy.vue b/src/pages/Shelving/Card/ShelvingDescriptorProxy.vue index 8023271b0..9561b77db 100644 --- a/src/pages/Shelving/Card/ShelvingDescriptorProxy.vue +++ b/src/pages/Shelving/Card/ShelvingDescriptorProxy.vue @@ -1,5 +1,6 @@ <script setup> -import ShelvingDescriptor from "pages/Shelving/Card/ShelvingDescriptor.vue"; +import ShelvingDescriptor from 'pages/Shelving/Card/ShelvingDescriptor.vue'; +import ShelvingSummary from './ShelvingSummary.vue'; const $props = defineProps({ id: { @@ -10,6 +11,6 @@ const $props = defineProps({ </script> <template> <QPopupProxy> - <ShelvingDescriptor v-if="$props.id" :id="$props.id" /> + <ShelvingDescriptor v-if="$props.id" :id="$props.id" :summary="ShelvingSummary" /> </QPopupProxy> </template> diff --git a/src/pages/Shelving/Card/ShelvingSummary.vue b/src/pages/Shelving/Card/ShelvingSummary.vue index 08d05626e..94175b0c1 100644 --- a/src/pages/Shelving/Card/ShelvingSummary.vue +++ b/src/pages/Shelving/Card/ShelvingSummary.vue @@ -51,7 +51,7 @@ const filter = { class="header header-link" :to="{ name: 'ShelvingBasicData', params: { id: entityId } }" > - {{ t('shelving.pageTitles.basicData') }} + {{ t('globals.pageTitles.basicData') }} <QIcon name="open_in_new" /> </RouterLink> <VnLv :label="t('shelving.summary.code')" :value="entity.code" /> diff --git a/src/pages/Supplier/Card/SupplierDescriptor.vue b/src/pages/Supplier/Card/SupplierDescriptor.vue index 4ef76c2f8..a31a3bc8d 100644 --- a/src/pages/Supplier/Card/SupplierDescriptor.vue +++ b/src/pages/Supplier/Card/SupplierDescriptor.vue @@ -112,22 +112,6 @@ const getEntryQueryParams = (supplier) => { data-key="supplier" :summary="$props.summary" > - <template #header-extra-action> - <QBtn - round - flat - dense - size="md" - icon="vn:supplier" - color="white" - class="link" - :to="{ name: 'SupplierList' }" - > - <QTooltip> - {{ t('Go to module index') }} - </QTooltip> - </QBtn> - </template> <template #body="{ entity }"> <VnLv :label="t('supplier.summary.taxNumber')" :value="entity.nif" /> <VnLv label="Alias" :value="entity.nickname" /> diff --git a/src/pages/Supplier/Card/SupplierSummary.vue b/src/pages/Supplier/Card/SupplierSummary.vue index d007ad08f..9337724b0 100644 --- a/src/pages/Supplier/Card/SupplierSummary.vue +++ b/src/pages/Supplier/Card/SupplierSummary.vue @@ -9,8 +9,6 @@ import { dashIfEmpty } from 'src/filters'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; -onUpdated(() => summaryRef.value.fetch()); - const route = useRoute(); const roleState = useRole(); const { t } = useI18n(); diff --git a/src/pages/Supplier/SupplierList.vue b/src/pages/Supplier/SupplierList.vue index d53781a38..ce7d234c8 100644 --- a/src/pages/Supplier/SupplierList.vue +++ b/src/pages/Supplier/SupplierList.vue @@ -1,26 +1,81 @@ <script setup> +import { computed, ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useRouter } from 'vue-router'; -import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; -import VnPaginate from 'src/components/ui/VnPaginate.vue'; -import CardList from 'src/components/ui/CardList.vue'; -import VnLv from 'src/components/ui/VnLv.vue'; -import SupplierSummary from './Card/SupplierSummary.vue'; +import VnTable from 'components/VnTable/VnTable.vue'; +import VnSearchbar from 'components/ui/VnSearchbar.vue'; +import RightMenu from 'components/common/RightMenu.vue'; import SupplierListFilter from './SupplierListFilter.vue'; -import { useSummaryDialog } from 'src/composables/useSummaryDialog'; -import RightMenu from 'src/components/common/RightMenu.vue'; -const router = useRouter(); const { t } = useI18n(); -const { viewSummary } = useSummaryDialog(); +const tableRef = ref(); -function navigate(id) { - router.push({ path: `/supplier/${id}` }); -} - -const redirectToCreateView = () => { - router.push({ name: 'SupplierCreate' }); -}; +const columns = computed(() => [ + { + align: 'left', + label: t('supplier.list.tableVisibleColumns.id'), + name: 'id', + isTitle: true, + }, + { + align: 'left', + label: t('supplier.list.tableVisibleColumns.name'), + name: 'socialName', + create: true, + component: 'input', + columnField: { + component: null, + }, + }, + { + align: 'left', + label: t('supplier.list.tableVisibleColumns.nif'), + name: 'nif', + component: 'input', + columnField: { + component: null, + }, + }, + { + align: 'left', + label: t('supplier.list.tableVisibleColumns.nickname'), + name: 'alias', + component: 'input', + columnField: { + component: null, + }, + }, + { + align: 'left', + label: t('supplier.list.tableVisibleColumns.account'), + name: 'account', + component: 'input', + columnField: { + component: null, + }, + }, + { + align: 'left', + label: t('supplier.list.tableVisibleColumns.payMethod'), + name: 'payMethod', + component: 'select', + attrs: { + url: 'payMethods', + fields: ['id', 'name'], + }, + columnField: { + component: null, + }, + }, + { + align: 'left', + label: t('supplier.list.tableVisibleColumns.payDay'), + name: 'payDat', + component: 'input', + columnField: { + component: null, + }, + }, +]); </script> <template> @@ -30,56 +85,25 @@ const redirectToCreateView = () => { <SupplierListFilter data-key="SuppliersList" /> </template> </RightMenu> - <QPage class="column items-center q-pa-md"> - <div class="vn-card-list"> - <VnPaginate data-key="SuppliersList" url="Suppliers/filter"> - <template #body="{ rows }"> - <CardList - v-for="row of rows" - :key="row.id" - :title="row.socialName" - :id="row.id" - @click="navigate(row.id)" - > - <template #list-items> - <VnLv label="NIF/CIF" :value="row.nif" /> - <VnLv label="Alias" :value="row.alias" /> - <VnLv - :label="t('supplier.list.payMethod')" - :value="row.payMethod" - /> - <VnLv - :label="t('supplier.list.payDeadline')" - :title-label="t('invoiceOut.list.created')" - :value="row.payDem" - /> - <VnLv - :label="t('supplier.list.payDay')" - :value="row.payDay" - /> - <VnLv - :label="t('supplier.list.account')" - :value="row.account" - /> - </template> - <template #actions> - <QBtn - :label="t('components.smartCard.openSummary')" - @click.stop="viewSummary(row.id, SupplierSummary)" - color="primary" - /> - </template> - </CardList> - </template> - </VnPaginate> - </div> - <QPageSticky :offset="[20, 20]"> - <QBtn fab icon="add" color="primary" @click="redirectToCreateView()" /> - <QTooltip> - {{ t('supplier.list.newSupplier') }} - </QTooltip> - </QPageSticky> - </QPage> + <VnTable + ref="tableRef" + data-key="SuppliersList" + url="Suppliers/filter" + save-url="Suppliers/crud" + redirect="supplier" + :create="{ + urlCreate: 'Suppliers/newSupplier', + title: t('Create Supplier'), + onDataSaved: ({ id }) => tableRef.redirect(id), + formInitialData: {}, + }" + order="id ASC" + :columns="columns" + default-mode="table" + auto-load + :right-search="false" + :use-model="true" + /> </template> <i18n> diff --git a/src/pages/Ticket/Card/TicketCreateServiceType.vue b/src/pages/Ticket/Card/TicketCreateServiceType.vue new file mode 100644 index 000000000..d392ec206 --- /dev/null +++ b/src/pages/Ticket/Card/TicketCreateServiceType.vue @@ -0,0 +1,49 @@ +<script setup> +import { reactive, ref, onMounted, nextTick } from 'vue'; +import { useI18n } from 'vue-i18n'; +import VnInput from 'src/components/common/VnInput.vue'; + +import VnRow from 'components/ui/VnRow.vue'; +import FormModelPopup from 'components/FormModelPopup.vue'; + +const { t } = useI18n(); +const emit = defineEmits(['onDataSaved']); + +const nameInputRef = ref(null); +const serviceFormData = reactive({}); + +const onDataSaved = (formData, requestResponse) => { + emit('onDataSaved', formData, requestResponse); +}; + +onMounted(async () => { + await nextTick(); + nameInputRef.value.focus(); +}); +</script> + +<template> + <FormModelPopup + url-create="TicketServiceTypes" + model="TicketServiceType" + :title="t('New service type')" + :form-initial-data="serviceFormData" + @on-data-saved="onDataSaved" + > + <template #form-inputs="{ data }"> + <VnRow class="row q-gutter-md q-mb-md"> + <VnInput + ref="nameInputRef" + :label="t('service.description')" + v-model="data.name" + :required="true" + /> + </VnRow> + </template> + </FormModelPopup> +</template> + +<i18n> +es: + New service type: Nuevo tipo de servicio +</i18n> diff --git a/src/pages/Ticket/Card/TicketCreateTracking.vue b/src/pages/Ticket/Card/TicketCreateTracking.vue new file mode 100644 index 000000000..399663571 --- /dev/null +++ b/src/pages/Ticket/Card/TicketCreateTracking.vue @@ -0,0 +1,86 @@ +<script setup> +import { ref } from 'vue'; +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; + +import FormModelPopup from 'components/FormModelPopup.vue'; +import VnRow from 'components/ui/VnRow.vue'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import FetchData from 'components/FetchData.vue'; + +import { useState } from 'src/composables/useState'; + +const emit = defineEmits(['onRequestCreated']); + +const route = useRoute(); +const { t } = useI18n(); +const state = useState(); +const user = state.getUser(); +const stateFetchDataRef = ref(null); + +const statesOptions = ref([]); +const workersOptions = ref([]); + +const onStateFkChange = (formData) => (formData.userFk = user.value.id); +</script> +<template> + <FetchData + ref="stateFetchDataRef" + url="States" + auto-load + @on-fetch="(data) => (statesOptions = data)" + /> + <FetchData + url="Workers/search" + :filter="{ fields: ['id', 'name'], order: 'name ASC' }" + auto-load + @on-fetch="(data) => (workersOptions = data)" + /> + <FormModelPopup + :title="t('Create tracking')" + url-create="Tickets/state" + model="CreateTicketTracking" + :form-initial-data="{ ticketFk: route.params.id }" + @on-data-saved="() => emit('onRequestCreated')" + > + <template #form-inputs="{ data }"> + <VnRow class="row q-gutter-md q-mb-md"> + <VnSelect + v-model="data.stateFk" + :label="t('tracking.state')" + :options="statesOptions" + @update:model-value="onStateFkChange(data)" + hide-selected + option-label="name" + option-value="id" + /> + <VnSelect + :label="t('tracking.worker')" + v-model="data.userFk" + :options="workersOptions" + hide-selected + option-label="name" + option-value="id" + > + <template #option="{ opt, itemProps }"> + <QItem v-bind="itemProps"> + <QItemSection> + <QItemLabel> + {{ opt.name }} + </QItemLabel> + <QItemLabel caption> + {{ opt.nickname }}, {{ opt.code }} + </QItemLabel> + </QItemSection> + </QItem> + </template></VnSelect + > + </VnRow> + </template> + </FormModelPopup> +</template> + +<i18n> + es: + Create tracking: Crear estado +</i18n> diff --git a/src/pages/Ticket/Card/TicketDescriptor.vue b/src/pages/Ticket/Card/TicketDescriptor.vue index dfbcfc106..b753d0b4c 100644 --- a/src/pages/Ticket/Card/TicketDescriptor.vue +++ b/src/pages/Ticket/Card/TicketDescriptor.vue @@ -70,8 +70,6 @@ const filter = { }; const data = ref(useCardDescription()); -const setData = (entity) => - (data.value = useCardDescription(entity.client.name, entity.id)); </script> <template> @@ -82,7 +80,6 @@ const setData = (entity) => :title="data.title" :subtitle="data.subtitle" data-key="ticketData" - @on-fetch="setData" > <template #menu="{ entity }"> <TicketDescriptorMenu :ticket="entity" /> @@ -92,7 +89,7 @@ const setData = (entity) => <template #value> <span class="link"> {{ entity.clientFk }} - <CustomerDescriptorProxy :id="entity.client.id" /> + <CustomerDescriptorProxy :id="entity.client?.id" /> </span> </template> </VnLv> @@ -109,8 +106,8 @@ const setData = (entity) => <VnLv :label="t('ticket.summary.salesPerson')"> <template #value> <VnUserLink - :name="entity.client.salesPersonUser?.name" - :worker-id="entity.client.salesPersonFk" + :name="entity.client?.salesPersonUser?.name" + :worker-id="entity.client?.salesPersonFk" /> </template> </VnLv> @@ -153,4 +150,5 @@ const setData = (entity) => <i18n> es: This ticket is deleted: Este ticket está eliminado + Go to module index: Ir al índice del modulo </i18n> diff --git a/src/pages/Ticket/Card/TicketDescriptorProxy.vue b/src/pages/Ticket/Card/TicketDescriptorProxy.vue index 7799b9ee6..583ba35e7 100644 --- a/src/pages/Ticket/Card/TicketDescriptorProxy.vue +++ b/src/pages/Ticket/Card/TicketDescriptorProxy.vue @@ -1,5 +1,6 @@ <script setup> import TicketDescriptor from './TicketDescriptor.vue'; +import TicketSummary from './TicketSummary.vue'; const $props = defineProps({ id: { @@ -10,6 +11,6 @@ const $props = defineProps({ </script> <template> <QPopupProxy> - <TicketDescriptor v-if="$props.id" :id="$props.id" /> + <TicketDescriptor v-if="$props.id" :id="$props.id" :summary="TicketSummary" /> </QPopupProxy> </template> diff --git a/src/pages/Ticket/Card/TicketNotes.vue b/src/pages/Ticket/Card/TicketNotes.vue new file mode 100644 index 000000000..cfc0c2431 --- /dev/null +++ b/src/pages/Ticket/Card/TicketNotes.vue @@ -0,0 +1,106 @@ +<script setup> +import { ref, watch, computed, reactive } from 'vue'; +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; + +import CrudModel from 'components/CrudModel.vue'; +import VnRow from 'components/ui/VnRow.vue'; +import VnInput from 'src/components/common/VnInput.vue'; +import VnSelect from 'src/components/common/VnSelect.vue'; +import FetchData from 'components/FetchData.vue'; + +import { useArrayData } from 'src/composables/useArrayData'; + +const route = useRoute(); +const { t } = useI18n(); +const ticketNotesCrudRef = ref(null); +const observationTypes = ref([]); +const arrayData = useArrayData('TicketNotes'); +const { store } = arrayData; + +const crudModelFilter = reactive({ + where: { ticketFk: route.params.id }, + fields: ['id', 'ticketFk', 'observationTypeFk', 'description'], +}); + +const crudModelRequiredData = computed(() => ({ ticketFk: route.params.id })); + +watch( + () => route.params.id, + async () => { + crudModelFilter.where.ticketFk = route.params.id; + store.filter = crudModelFilter; + await ticketNotesCrudRef.value.reload(); + } +); +</script> + +<template> + <FetchData + @on-fetch="(data) => (observationTypes = data)" + auto-load + url="ObservationTypes" + /> + <div class="flex justify-center"> + <CrudModel + ref="ticketNotesCrudRef" + data-key="TicketNotes" + url="TicketObservations" + model="TicketNotes" + :filter="crudModelFilter" + :data-required="crudModelRequiredData" + :default-remove="false" + auto-load + style="max-width: 800px" + > + <template #body="{ rows }"> + <QCard class="q-px-lg q-py-md"> + <div + v-for="(row, index) in rows" + :key="index" + class="q-mb-md row items-center q-gutter-x-md" + > + <VnSelect + :label="t('ticketNotes.observationType')" + :options="observationTypes" + hide-selected + option-label="description" + option-value="id" + v-model="row.observationTypeFk" + :disable="!!row.id" + /> + <VnInput + :label="t('ticketNotes.description')" + v-model="row.description" + class="col" + /> + <QIcon + name="delete" + size="sm" + class="cursor-pointer" + color="primary" + @click="ticketNotesCrudRef.remove([row])" + > + <QTooltip> + {{ t('ticketNotes.removeNote') }} + </QTooltip> + </QIcon> + </div> + <VnRow v-if="observationTypes.length > rows.length"> + <QIcon + name="add_circle" + class="fill-icon-on-hover q-ml-md" + size="sm" + color="primary" + @click="ticketNotesCrudRef.insert()" + > + <QTooltip> + {{ t('ticketNotes.addNote') }} + </QTooltip> + </QIcon> + </VnRow> + </QCard> + </template> + </CrudModel> + </div> +</template> diff --git a/src/pages/Ticket/Card/TicketService.vue b/src/pages/Ticket/Card/TicketService.vue new file mode 100644 index 000000000..4cc8207ef --- /dev/null +++ b/src/pages/Ticket/Card/TicketService.vue @@ -0,0 +1,190 @@ +<script setup> +import { ref, watch, computed, onMounted } from 'vue'; +import { useRoute, useRouter } from 'vue-router'; +import { useI18n } from 'vue-i18n'; + +import CrudModel from 'components/CrudModel.vue'; +import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; +import FetchData from 'components/FetchData.vue'; +import TicketCreateServiceType from './TicketCreateServiceType.vue'; +import VnInput from 'src/components/common/VnInput.vue'; + +import { useArrayData } from 'src/composables/useArrayData'; +import useNotify from 'src/composables/useNotify.js'; +import axios from 'axios'; + +const route = useRoute(); +const router = useRouter(); +const { t } = useI18n(); +const ticketServiceTypeFetchRef = ref(null); +const ticketServiceCrudRef = ref(null); +const ticketServiceOptions = ref([]); +const arrayData = useArrayData('TicketNotes'); +const { store } = arrayData; +const { notify } = useNotify(); + +const selected = ref([]); +const defaultTaxClass = ref(null); + +const crudModelFilter = computed(() => ({ + where: { ticketFk: route.params.id }, +})); + +const crudModelRequiredData = computed(() => ({ + ticketFk: route.params.id, + taxClassFk: defaultTaxClass.value?.id, +})); + +watch( + () => route.params.id, + async () => { + store.filter = crudModelFilter.value; + await ticketServiceCrudRef.value.reload(); + } +); + +onMounted(async () => await getDefaultTaxClass()); + +const createRefund = async () => { + try { + if (!selected.value.length) return; + + const params = { + servicesIds: selected.value.map((s) => +s.ticketFk), + withWarehouse: false, + negative: true, + }; + const { data } = await axios.post('Sales/clone', params); + const [refundTicket] = data; + notify( + t('service.createRefundSuccess', { + ticketId: refundTicket.id, + }), + 'positive' + ); + router.push({ name: 'TicketSale', params: { id: refundTicket.id } }); + } catch (error) { + console.error(error); + } +}; + +const getDefaultTaxClass = async () => { + try { + let filter = { + where: { code: 'G' }, + }; + const { data } = await axios.get('TaxClasses/findOne', { + params: { filter: JSON.stringify(filter) }, + }); + defaultTaxClass.value = data; + console.log('defaultTaxClass', defaultTaxClass.value); + } catch (error) { + console.error(error); + } +}; + +const columns = computed(() => [ + { + name: 'description', + label: t('service.description'), + field: (row) => row.ticketServiceTypeFk, + sortable: true, + align: 'left', + }, + { + name: 'quantity', + label: t('service.quantity'), + field: (row) => row.quantity, + sortable: true, + align: 'left', + }, + { + name: 'price', + label: t('service.price'), + field: (row) => row.price, + sortable: true, + align: 'left', + }, +]); +</script> + +<template> + <FetchData + ref="ticketServiceTypeFetchRef" + @on-fetch="(data) => (ticketServiceOptions = data)" + auto-load + url="TicketServiceTypes" + /> + <CrudModel + ref="ticketServiceCrudRef" + data-key="TicketService" + url="TicketServices" + model="TicketService" + :filter="crudModelFilter" + :data-required="crudModelRequiredData" + auto-load + v-model:selected="selected" + > + <template #moreBeforeActions> + <QBtn + color="primary" + :label="t('service.pay')" + :disabled="!selected.length" + @click.stop="createRefund()" + /> + </template> + <template #body="{ rows }"> + <QTable + :columns="columns" + :rows="rows" + row-key="$index" + selection="multiple" + v-model:selected="selected" + table-header-class="text-left" + > + <template #body-cell-description="{ row, col }"> + <QTd auto-width> + <VnSelectDialog + :label="col.label" + v-model="row.ticketServiceTypeFk" + :options="ticketServiceOptions" + option-label="name" + option-value="id" + hide-selected + > + <template #form> + <TicketCreateServiceType + @on-data-saved="ticketServiceTypeFetchRef.fetch()" + /> + </template> + </VnSelectDialog> + </QTd> + </template> + <template #body-cell-quantity="{ row, col }"> + <QTd auto-width> + <VnInput + :label="col.label" + v-model.number="row.quantity" + type="number" + min="0" + :info="t('service.quantityInfo')" + /> + </QTd> + </template> + <template #body-cell-price="{ row, col }"> + <QTd auto-width> + <VnInput + :label="col.label" + v-model.number="row.price" + type="number" + min="0" + /> + </QTd> + </template> + </QTable> + </template> + </CrudModel> + <QPageSticky position="bottom-right" :offset="[25, 25]"> + <QBtn fab color="primary" icon="add" @click="ticketServiceCrudRef.insert()" /> + </QPageSticky> +</template> diff --git a/src/pages/Ticket/Card/TicketTracking.vue b/src/pages/Ticket/Card/TicketTracking.vue new file mode 100644 index 000000000..2ddb278fa --- /dev/null +++ b/src/pages/Ticket/Card/TicketTracking.vue @@ -0,0 +1,121 @@ +<script setup> +import { ref, computed, watch, reactive } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { useRoute } from 'vue-router'; + +import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; +import TicketCreateTracking from './TicketCreateTracking.vue'; +import VnPaginate from 'components/ui/VnPaginate.vue'; + +import { toDateTimeFormat } from 'src/filters/date.js'; + +const route = useRoute(); +const { t } = useI18n(); +const createTrackingDialogRef = ref(null); +const paginateRef = ref(null); + +watch( + () => route.params.id, + async (val) => { + paginateFilter.where.ticketFk = val; + paginateRef.value.fetch(); + } +); + +const paginateFilter = reactive({ + include: [ + { + relation: 'user', + scope: { + fields: ['id', 'name'], + include: { + relation: 'worker', + scope: { + fields: ['id'], + }, + }, + }, + }, + { + relation: 'state', + scope: { + fields: ['name'], + }, + }, + ], + + order: ['created DESC'], + where: { + ticketFk: route.params.id, + }, +}); + +const columns = computed(() => [ + { + label: t('tracking.state'), + name: 'state', + field: 'state', + align: 'left', + format: (val) => val.name, + }, + { + label: t('tracking.worker'), + name: 'worker', + align: 'left', + }, + { + label: t('tracking.created'), + name: 'created', + field: 'created', + align: 'left', + format: (val) => toDateTimeFormat(val), + }, +]); + +const openCreateModal = () => createTrackingDialogRef.value.show(); +</script> + +<template> + <QPage class="column items-center q-pa-md"> + <VnPaginate + ref="paginateRef" + data-key="TicketTracking" + :filter="paginateFilter" + url="TicketTrackings" + auto-load + > + <template #body="{ rows }"> + <QTable + :rows="rows" + :columns="columns" + row-key="id" + :pagination="{ rowsPerPage: 0 }" + class="full-width q-mt-md" + :no-data-label="t('globals.noResults')" + > + <template #body-cell-worker="{ row }"> + <QTd @click.stop> + <QBtn flat color="primary"> + {{ row.user?.name }} + <WorkerDescriptorProxy :id="row.user?.worker?.id" /> + </QBtn> + </QTd> + </template> + </QTable> + </template> + </VnPaginate> + <QDialog + ref="createTrackingDialogRef" + transition-show="scale" + transition-hide="scale" + > + <TicketCreateTracking @on-request-created="paginateRef.fetch()" /> + </QDialog> + <QPageSticky :offset="[20, 20]"> + <QBtn @click="openCreateModal()" color="primary" fab icon="add" /> + <QTooltip class="text-no-wrap"> + {{ t('tracking.addState') }} + </QTooltip> + </QPageSticky> + </QPage> +</template> diff --git a/src/pages/Ticket/Card/TicketVolume.vue b/src/pages/Ticket/Card/TicketVolume.vue new file mode 100644 index 000000000..93da31e53 --- /dev/null +++ b/src/pages/Ticket/Card/TicketVolume.vue @@ -0,0 +1,153 @@ +<script setup> +import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'; +import { useI18n } from 'vue-i18n'; +import { useRoute } from 'vue-router'; + +import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; +import FetchedTags from 'components/ui/FetchedTags.vue'; +import RightMenu from 'src/components/common/RightMenu.vue'; +import FetchData from 'components/FetchData.vue'; + +import { useStateStore } from 'stores/useStateStore'; +import { dashIfEmpty } from 'src/filters'; +import axios from 'axios'; + +const route = useRoute(); +const stateStore = useStateStore(); +const { t } = useI18n(); +const salesRef = ref(null); + +watch( + () => route.params.id, + async () => { + await nextTick(); + salesRef.value?.fetch(); + } +); + +const salesFilter = computed(() => ({ + include: { relation: 'item' }, + order: 'concept', + where: { ticketFk: route.params.id }, + limit: 20, +})); + +const sales = ref([]); +const packingTypeVolume = ref([]); +const rows = computed(() => sales.value); + +const columns = computed(() => [ + { + label: t('volume.item'), + name: 'item', + align: 'left', + }, + { + label: t('volume.description'), + name: 'description', + align: 'left', + }, + { + label: t('volume.packingType'), + name: 'quantity', + field: (row) => row.item.itemPackingTypeFk, + align: 'left', + format: (val) => dashIfEmpty(val), + }, + { + label: t('volume.quantity'), + name: 'quantity', + field: 'quantity', + align: 'left', + }, + { + label: t('volume.volumeQuantity'), + name: 'quantity', + field: (row) => row.saleVolume?.volume, + align: 'left', + }, +]); + +const applyVolumes = async (salesData) => { + try { + if (!salesData.length) return; + + sales.value = salesData; + const ticket = sales.value[0].ticketFk; + const { data } = await axios.get(`Tickets/${ticket}/getVolume`); + const volumes = new Map(data.saleVolume.map((volume) => [volume.saleFk, volume])); + + sales.value.forEach((sale) => { + sale.saleVolume = volumes.get(sale.id); + }); + + packingTypeVolume.value = data.packingTypeVolume; + } catch (error) { + console.error(error); + } +}; + +onMounted(() => { + stateStore.rightDrawer = true; +}); + +onUnmounted(() => (stateStore.rightDrawer = false)); +</script> + +<template> + <FetchData + ref="salesRef" + url="sales" + :filter="salesFilter" + @on-fetch="(data) => applyVolumes(data)" + auto-load + /> + <RightMenu v-if="packingTypeVolume.length"> + <template #right-panel> + <QCard + v-for="(packingType, index) in packingTypeVolume" + :key="index" + class="q-pa-md q-mb-md q-ma-md color-vn-text" + bordered + flat + style="border-color: black" + > + <QCardSection class="column items-center" horizontal> + <span> + {{ t('volume.type') }}: + {{ dashIfEmpty(packingType.description) }} + </span> + </QCardSection> + <QCardSection class="column items-center" horizontal> + <span> {{ t('volume.volume') }}: {{ packingType.volume }} </span> + </QCardSection> + </QCard> + </template> + </RightMenu> + <QTable + :rows="rows" + :columns="columns" + row-key="id" + :pagination="{ rowsPerPage: 0 }" + class="full-width q-mt-md" + :no-data-label="t('globals.noResults')" + > + <template #body-cell-item="{ row }"> + <QTd> + <QBtn flat color="primary"> + {{ row.itemFk }} + <ItemDescriptorProxy :id="row.itemFk" /> + </QBtn> + </QTd> + </template> + <template #body-cell-description="{ row }"> + <QTd> + <div class="column"> + <span>{{ row.item.name }}</span> + <span class="color-vn-label">{{ row.item.subName }}</span> + <FetchedTags :item="row.item" :max-length="6" /> + </div> + </QTd> + </template> + </QTable> +</template> diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index 7b74117bb..63ac48393 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -17,15 +17,6 @@ const props = defineProps({ }, }); -const from = Date.vnNew(); -const to = Date.vnNew(); -to.setDate(to.getDate() + 1); - -const defaultParams = { - from: toDateString(from), - to: toDateString(to), -}; - const workers = ref(); const provinces = ref(); const states = ref(); @@ -44,11 +35,7 @@ const warehouses = ref(); @on-fetch="(data) => (workers = data)" auto-load /> - <VnFilterPanel - :data-key="props.dataKey" - :params="defaultParams" - :search-button="true" - > + <VnFilterPanel :data-key="props.dataKey" :search-button="true"> <template #tags="{ tag, formatFn }"> <div class="q-gutter-x-xs"> <strong>{{ t(`params.${tag.label}`) }}: </strong> diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index c065cf6bf..16d7aac5d 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -26,8 +26,8 @@ const to = Date.vnNew(); to.setDate(to.getDate() + 1); const userParams = { - from: toDateString(from), - to: toDateString(to), + from: from.toISOString(), + to: to.toISOString(), }; function navigate(id) { diff --git a/src/pages/Ticket/locale/en.yml b/src/pages/Ticket/locale/en.yml index 2c648e7f2..39aed4af7 100644 --- a/src/pages/Ticket/locale/en.yml +++ b/src/pages/Ticket/locale/en.yml @@ -1,3 +1,19 @@ +card: + search: Search tickets + searchInfo: You can search by ticket id or alias +volume: + item: Item + description: Description + packingType: Packing Type + quantity: Quantity + volumeQuantity: m³ per quantity + type: Type + volume: Volume +ticketNotes: + observationType: Observation type + description: Description + removeNote: Remove note + addNote: Add note ticketSale: id: Id visible: Visible @@ -113,9 +129,6 @@ basicData: negativesConfirmMessage: Negatives are going to be generated, are you sure you want to advance all the lines? chooseAnOption: Choose an option unroutedTicket: The ticket has been unrouted -card: - search: Search tickets - searchInfo: You can search by ticket id or alias purchaseRequest: id: Id description: Description @@ -136,3 +149,18 @@ weeklyTickets: salesperson: Salesperson search: Search weekly tickets searchInfo: Search weekly tickets by id or client id +service: + pay: Pay + description: Description + quantity: Quantity + price: Price + removeService: Remove service + newService: New service type + addService: Add service + quantityInfo: To create services with negative amounts mark the service on the source ticket and press the pay button. + createRefundSuccess: 'The following refund ticket have been created: { ticketId }' +tracking: + state: State + worker: Worker + created: Created + addState: Add state diff --git a/src/pages/Ticket/locale/es.yml b/src/pages/Ticket/locale/es.yml index 3ce4c0545..d5b50efc5 100644 --- a/src/pages/Ticket/locale/es.yml +++ b/src/pages/Ticket/locale/es.yml @@ -1,6 +1,34 @@ +service: + pay: Abonar + description: Descripción + quantity: Cantidad + price: Precio + removeService: Quitar servicio + newService: Nuevo tipo de servicio + addService: Añadir servicio + quantityInfo: Para crear sevicios con cantidades negativas marcar servicio en el ticket origen y apretar el boton abonar. + createRefundSuccess: 'Se ha creado siguiente ticket de abono: { ticketId }' +tracking: + state: Estado + worker: Trabajador + created: Fecha creación + addState: Añadir estado card: search: Buscar tickets searchInfo: Buscar tickets por identificador o alias +volume: + item: Artículo + description: Descripción + packingType: Encajado + quantity: Cantidad + volumeQuantity: m³ por cantidad + type: Tipo + volume: Volumen +ticketNotes: + observationType: Tipo de observación + description: Descripción + removeNote: Quitar nota + addNote: Añadir nota purchaseRequest: Id: Id description: Descripción @@ -112,8 +140,6 @@ futureTickets: moveTicketSuccess: Tickets movidos correctamente searchInfo: Buscar tickets por fecha futureTicket: Tickets a futuro -Search ticket: Buscar tickets -You can search by ticket id or alias: Puedes buscar por id o alias del ticket ticketSale: id: Id visible: Visible @@ -138,3 +164,5 @@ ticketSale: shipped: F. Envío agency: Agencia address: Consignatario +Search ticket: Buscar tickets +You can search by ticket id or alias: Puedes buscar por id o alias del ticket diff --git a/src/pages/Travel/Card/TravelCard.vue b/src/pages/Travel/Card/TravelCard.vue index bf7e6d57a..a3c1430e9 100644 --- a/src/pages/Travel/Card/TravelCard.vue +++ b/src/pages/Travel/Card/TravelCard.vue @@ -33,8 +33,12 @@ const filter = { <template> <VnCard data-key="Travel" - :filter="filter" base-url="Travels" + search-data-key="TravelList" + searchbar-label="Search travel" + searchbar-info="You can search by travel id or name" + search-url="Travels" + :filter="filter" :descriptor="TravelDescriptor" /> </template> diff --git a/src/pages/Travel/Card/TravelDescriptor.vue b/src/pages/Travel/Card/TravelDescriptor.vue index 6d3707828..b74761b37 100644 --- a/src/pages/Travel/Card/TravelDescriptor.vue +++ b/src/pages/Travel/Card/TravelDescriptor.vue @@ -1,10 +1,11 @@ <script setup> -import { computed } from 'vue'; +import { computed, ref } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import useCardDescription from 'src/composables/useCardDescription'; import TravelDescriptorMenuItems from './TravelDescriptorMenuItems.vue'; import { toDate } from 'src/filters'; @@ -51,32 +52,22 @@ const filter = { const entityId = computed(() => { return $props.id || route.params.id; }); + +const data = ref(useCardDescription()); +const setData = (entity) => (data.value = useCardDescription(entity.ref, entity.id)); </script> <template> <CardDescriptor module="Travel" :url="`Travels/${entityId}`" - title="ref" + :title="data.title" + :subtitle="data.subtitle" :filter="filter" - data-key="Travel" + data-key="travelData" + :summary="$attrs" + @on-fetch="setData" > - <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> diff --git a/src/pages/Travel/Card/TravelDescriptorProxy.vue b/src/pages/Travel/Card/TravelDescriptorProxy.vue index ab5c42d7e..eddadb382 100644 --- a/src/pages/Travel/Card/TravelDescriptorProxy.vue +++ b/src/pages/Travel/Card/TravelDescriptorProxy.vue @@ -1,5 +1,6 @@ <script setup> import TravelDescriptor from './TravelDescriptor.vue'; +import TravelSummary from './TravelSummary.vue'; const $props = defineProps({ id: { @@ -11,6 +12,6 @@ const $props = defineProps({ <template> <QPopupProxy> - <TravelDescriptor v-if="$props.id" :id="$props.id" /> + <TravelDescriptor v-if="$props.id" :id="$props.id" :summary="TravelSummary" /> </QPopupProxy> </template> diff --git a/src/pages/Travel/Card/TravelSummary.vue b/src/pages/Travel/Card/TravelSummary.vue index 88ddee9a2..af9aa5f7f 100644 --- a/src/pages/Travel/Card/TravelSummary.vue +++ b/src/pages/Travel/Card/TravelSummary.vue @@ -12,8 +12,6 @@ import FetchData from 'src/components/FetchData.vue'; import { toDate, toCurrency } from 'src/filters'; import axios from 'axios'; -onUpdated(() => summaryRef.value.fetch()); - const $props = defineProps({ id: { type: Number, @@ -49,7 +47,7 @@ const entriesTableColumns = computed(() => { showValue: false, }, { - label: t('supplier.pageTitles.supplier'), + label: t('globals.pageTitles.supplier'), field: 'supplierName', name: 'supplierName', align: 'left', @@ -248,7 +246,7 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`; <QCardSection class="q-pa-none"> <VnTitle :url="getLink('basic-data')" - :text="t('travel.pageTitles.basicData')" + :text="t('globals.pageTitles.basicData')" /> </QCardSection> <VnLv :label="t('globals.shipped')" :value="toDate(travel.shipped)" /> @@ -266,7 +264,7 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`; <QCardSection class="q-pa-none"> <VnTitle :url="getLink('basic-data')" - :text="t('travel.pageTitles.basicData')" + :text="t('globals.pageTitles.basicData')" /> </QCardSection> <VnLv :label="t('globals.landed')" :value="toDate(travel.landed)" /> @@ -284,7 +282,7 @@ const getLink = (param) => `#/travel/${entityId.value}/${param}`; <QCardSection class="q-pa-none"> <VnTitle :url="getLink('basic-data')" - :text="t('travel.pageTitles.basicData')" + :text="t('globals.pageTitles.basicData')" /> </QCardSection> <VnLv :label="t('globals.agency')" :value="travel.agency?.name" /> diff --git a/src/pages/Travel/ExtraCommunity.vue b/src/pages/Travel/ExtraCommunity.vue index 98648512f..4dc7eb052 100644 --- a/src/pages/Travel/ExtraCommunity.vue +++ b/src/pages/Travel/ExtraCommunity.vue @@ -140,7 +140,7 @@ const columns = computed(() => [ sortable: true, }, { - label: t('supplier.pageTitles.supplier'), + label: t('globals.pageTitles.supplier'), field: 'cargoSupplierNickname', name: 'cargoSupplierNickname', align: 'left', diff --git a/src/pages/Travel/ExtraCommunityFilter.vue b/src/pages/Travel/ExtraCommunityFilter.vue index 7fe628180..c51151451 100644 --- a/src/pages/Travel/ExtraCommunityFilter.vue +++ b/src/pages/Travel/ExtraCommunityFilter.vue @@ -7,6 +7,7 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; +import axios from 'axios'; const { t } = useI18n(); const props = defineProps({ @@ -20,6 +21,7 @@ const warehousesOptions = ref([]); const continentsOptions = ref([]); const agenciesOptions = ref([]); const suppliersOptions = ref([]); +const warehousesByContinent = ref({}); const add = (paramsObj, key) => { if (paramsObj[key] === undefined) { @@ -34,6 +36,28 @@ const decrement = (paramsObj, key) => { paramsObj[key]--; }; + +const warehouses = async () => { + const warehousesResponse = await axios.get('Warehouses'); + const countriesResponse = await axios.get('Countries'); + const continentsResponse = await axios.get('Continents'); + + const countryContinentMap = countriesResponse.data.reduce((acc, country) => { + acc[country.id] = country.continentFk; + return acc; + }, {}); + + continentsResponse.data.forEach((continent) => { + const countriesInContinent = Object.keys(countryContinentMap).filter( + (countryId) => countryContinentMap[countryId] === continent.id.toString() + ); + + warehousesByContinent.value[continent.code] = warehousesResponse.data.filter( + (warehouse) => countriesInContinent.includes(warehouse.countryFk.toString()) + ); + }); +}; +warehouses(); </script> <template> @@ -116,7 +140,6 @@ const decrement = (paramsObj, key) => { <VnSelect :label="t('params.agencyModeFk')" v-model="params.agencyModeFk" - @update:model-value="searchFn()" :options="agenciesOptions" option-value="agencyFk" option-label="name" @@ -147,12 +170,26 @@ const decrement = (paramsObj, key) => { /> </QItemSection> </QItem> - <QItem> + <QItem v-if="warehousesByContinent[params.continent]"> + <QItemSection> + <VnSelect + :label="t('params.warehouseOutFk')" + v-model="params.warehouseOutFk" + :options="warehousesByContinent[params.continent]" + option-value="id" + option-label="name" + hide-selected + dense + outlined + rounded + /> + </QItemSection> + </QItem> + <QItem v-else> <QItemSection> <VnSelect :label="t('params.warehouseOutFk')" v-model="params.warehouseOutFk" - @update:model-value="searchFn()" :options="warehousesOptions" option-value="id" option-label="name" @@ -168,7 +205,6 @@ const decrement = (paramsObj, key) => { <VnSelect :label="t('params.warehouseInFk')" v-model="params.warehouseInFk" - @update:model-value="searchFn()" :options="warehousesOptions" option-value="id" option-label="name" @@ -182,9 +218,8 @@ const decrement = (paramsObj, key) => { <QItem> <QItemSection> <VnSelect - :label="t('supplier.pageTitles.supplier')" + :label="t('globals.pageTitles.supplier')" v-model="params.cargoSupplierFk" - @update:model-value="searchFn()" :options="suppliersOptions" option-value="id" option-label="name" @@ -200,7 +235,6 @@ const decrement = (paramsObj, key) => { <VnSelect :label="t('params.continent')" v-model="params.continent" - @update:model-value="searchFn()" :options="continentsOptions" option-value="code" option-label="name" diff --git a/src/pages/Travel/TravelList.vue b/src/pages/Travel/TravelList.vue index e6696b26f..12a5fafa5 100644 --- a/src/pages/Travel/TravelList.vue +++ b/src/pages/Travel/TravelList.vue @@ -2,31 +2,27 @@ import { onMounted, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; -import VnPaginate from 'src/components/ui/VnPaginate.vue'; -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 VnSearchbar from 'src/components/ui/VnSearchbar.vue'; - +import { useRoute } from 'vue-router'; import { useStateStore } from 'stores/useStateStore'; -import { toDate } from 'src/filters/index'; +import VnTable from 'components/VnTable/VnTable.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; -import { getDateQBadgeColor } from 'src/composables/getDateQBadgeColor.js'; -import RightMenu from 'src/components/common/RightMenu.vue'; - +import { computed } from 'vue'; +import TravelSummary from './Card/TravelSummary.vue'; +import VnSearchbar from 'components/ui/VnSearchbar.vue'; +import { dashIfEmpty, toDate } from 'src/filters'; +const { viewSummary } = useSummaryDialog(); const router = useRouter(); const { t } = useI18n(); const stateStore = useStateStore(); -const { viewSummary } = useSummaryDialog(); - -const warehouses = ref([]); - -const navigateToTravelId = (id) => { - router.push({ path: `/travel/${id}` }); -}; - +const route = useRoute(); +const tableRef = ref(); +const $props = defineProps({ + id: { + type: Number, + default: 0, + }, +}); +const entityId = computed(() => $props.id || route.params.id); const cloneTravel = (travelData) => { const stringifiedTravelData = JSON.stringify(travelData); redirectToCreateView(stringifiedTravelData); @@ -40,125 +36,170 @@ const redirectCreateEntryView = (travelData) => { router.push({ name: 'EntryCreate', query: { travelFk: travelData.id } }); }; -const getWarehouseName = (id) => { - return warehouses.value.find((warehouse) => warehouse.id === id).name; -}; - onMounted(async () => { stateStore.rightDrawer = true; }); + +const columns = computed(() => [ + { + align: 'left', + name: 'id', + label: t('travel.travelList.tableVisibleColumns.id'), + isId: true, + field: 'id', + cardVisible: true, + }, + { + align: 'left', + name: 'ref', + label: t('travel.travelList.tableVisibleColumns.ref'), + field: 'ref', + component: 'input', + columnField: { + component: null, + }, + cardVisible: true, + create: true, + }, + { + align: 'left', + name: 'agencyModeFk', + label: t('travel.travelList.tableVisibleColumns.agency'), + field: 'agencyModeFk', + component: 'select', + attrs: { + url: 'agencyModes', + fields: ['id', 'name'], + }, + columnField: { + component: null, + }, + cardVisible: true, + create: true, + }, + { + align: 'left', + name: 'shipped', + label: t('travel.travelList.tableVisibleColumns.shipped'), + field: 'shipped', + component: 'date', + columnField: { + component: null, + }, + cardVisible: true, + create: true, + format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.shipped)), + }, + { + align: 'left', + name: 'landed', + label: t('travel.travelList.tableVisibleColumns.landed'), + field: 'landed', + component: 'date', + columnField: { + component: null, + }, + cardVisible: true, + create: true, + format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landed)), + }, + { + align: 'left', + name: 'warehouseInFk', + label: t('travel.travelList.tableVisibleColumns.warehouseIn'), + field: 'warehouseInFk', + component: 'select', + attrs: { + url: 'warehouses', + fields: ['id', 'name'], + }, + columnField: { + component: null, + }, + cardVisible: true, + create: true, + }, + { + align: 'left', + name: 'warehouseOutFk', + label: t('travel.travelList.tableVisibleColumns.warehouseOut'), + field: 'warehouseOutFk', + component: 'select', + attrs: { + url: 'warehouses', + fields: ['id', 'name'], + }, + columnField: { + component: null, + }, + cardVisible: true, + create: true, + }, + { + align: 'left', + name: 'totalEntries', + label: t('travel.travelList.tableVisibleColumns.totalEntries'), + field: 'totalEntries', + component: 'input', + columnField: { + component: null, + }, + cardVisible: true, + create: true, + }, + { + align: 'right', + label: '', + name: 'tableActions', + actions: [ + { + title: t('Clone'), + icon: 'vn:clone', + action: cloneTravel, + isPrimary: true, + }, + { + title: t('Add entry'), + icon: 'contact_support', + action: redirectCreateEntryView, + }, + { + title: t('View Summary'), + icon: 'preview', + action: (row) => viewSummary(row.id, TravelSummary), + }, + ], + }, +]); </script> <template> - <FetchData - url="Warehouses" - :filter="{ fields: ['id', 'name'] }" - order="name" - @on-fetch="(data) => (warehouses = data)" - auto-load + <VnSearchbar + :info="t('You can search by travel id or name')" + :label="t('Search travel')" + data-key="TravelList" + /> + <VnTable + ref="tableRef" + data-key="TravelList" + url="Travels/filter" + :create="{ + urlCreate: 'Travels', + title: t('Create Travels'), + onDataSaved: ({ id }) => tableRef.redirect(id), + formInitialData: { + editorFk: entityId, + }, + }" + order="landed DESC" + :columns="columns" + default-mode="table" + auto-load + redirect="travel" + :right-search="false" + :is-editable="false" + :use-model="true" /> - <VnSearchbar data-key="TravelList" :limit="20" :label="t('searchByIdOrReference')" /> - <RightMenu> - <template #right-panel> - <TravelFilter data-key="TravelList" /> - </template> - </RightMenu> - <QPage class="column items-center q-pa-md"> - <div class="vn-card-list"> - <VnPaginate - data-key="TravelList" - url="Travels/filter" - auto-load - order="shipped DESC, landed DESC" - > - <template #body="{ rows }"> - <CardList - v-for="row of rows" - :key="row.id" - :title="row.ref" - :id="row.id" - @click="navigateToTravelId(row.id)" - > - <template #list-items> - <VnLv - :label="t('globals.agency')" - :value="row.agencyModeName" - /> - <VnLv - v-if="warehouses.length > 0" - :label="t('globals.wareHouseOut')" - :value="getWarehouseName(row.warehouseOutFk)" - /> - <VnLv :label="t('globals.shipped')"> - <template #value> - <QBadge - text-color="black" - 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 - text-color="black" - 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="getWarehouseName(row.warehouseInFk)" - /> - <VnLv - :label="t('globals.totalEntries')" - :value="row.totalEntries" - /> - </template> - <template #actions> - <QBtn - :label="t('components.smartCard.clone')" - @click.stop="cloneTravel(row)" - outline - /> - <QBtn - :label="t('addEntry')" - @click.stop="redirectCreateEntryView(row)" - outline - style="margin-top: 15px" - /> - <QBtn - :label="t('components.smartCard.openSummary')" - @click.stop="viewSummary(row.id, TravelSummary)" - color="primary" - style="margin-top: 15px" - /> - </template> - </CardList> - </template> - </VnPaginate> - </div> - <QPageSticky :offset="[20, 20]"> - <QBtn fab icon="add" color="primary" @click="redirectToCreateView()" /> - <QTooltip> - {{ t('supplier.list.newSupplier') }} - </QTooltip> - </QPageSticky> - </QPage> </template> <i18n> @@ -169,4 +210,6 @@ en: es: addEntry: Añadir entrada searchByIdOrReference: Buscar por ID o por referencia + You can search by travel id or name: Buscar por envio por id o nombre + Search travel: Buscar envio </i18n> diff --git a/src/pages/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue index 6739b3356..82a698878 100644 --- a/src/pages/Worker/Card/WorkerDescriptor.vue +++ b/src/pages/Worker/Card/WorkerDescriptor.vue @@ -17,10 +17,6 @@ const $props = defineProps({ required: false, default: null, }, - summary: { - type: Object, - default: null, - }, }); const route = useRoute(); @@ -115,7 +111,7 @@ const refetch = async () => await cardDescriptorRef.value.getData(); :filter="filter" :title="data.title" :subtitle="data.subtitle" - :summary="$props.summary" + :summary="$attrs" @on-fetch=" (data) => { worker = data; diff --git a/src/pages/Worker/Card/WorkerNotificationsManager.vue b/src/pages/Worker/Card/WorkerNotificationsManager.vue index 8699392e0..731e073cd 100644 --- a/src/pages/Worker/Card/WorkerNotificationsManager.vue +++ b/src/pages/Worker/Card/WorkerNotificationsManager.vue @@ -56,7 +56,6 @@ const swapEntry = (from, to, key) => { }; function setNotifications(data) { - console.log('data: ', data); active.value = new Map(data.active); available.value = new Map(data.available); } diff --git a/src/pages/Worker/Card/WorkerSummary.vue b/src/pages/Worker/Card/WorkerSummary.vue index 319592fe9..d8bee4714 100644 --- a/src/pages/Worker/Card/WorkerSummary.vue +++ b/src/pages/Worker/Card/WorkerSummary.vue @@ -9,6 +9,7 @@ import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; import CardSummary from 'components/ui/CardSummary.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnTitle from 'src/components/common/VnTitle.vue'; +import RoleDescriptorProxy from 'src/pages/Account/Role/Card/RoleDescriptorProxy.vue'; const route = useRoute(); const { t } = useI18n(); @@ -161,7 +162,14 @@ const filter = { <VnTitle :text="t('worker.summary.userData')" /> <VnLv :label="t('worker.summary.userId')" :value="worker.user.id" /> <VnLv :label="t('worker.card.name')" :value="worker.user.nickname" /> - <VnLv :label="t('worker.summary.role')" :value="worker.user.role.name" /> + <VnLv :label="t('worker.summary.role')"> + <template #value> + <span class="link"> + {{ worker.user.role.name }} + <RoleDescriptorProxy :id="worker.user.role.id" /> + </span> + </template> + </VnLv> <VnLv :value="worker?.sip?.extension"> <template #label> {{ t('worker.summary.sipExtension') }} diff --git a/src/pages/Zone/Card/ZoneBasicData.vue b/src/pages/Zone/Card/ZoneBasicData.vue index 7a2c505fa..9f2195766 100644 --- a/src/pages/Zone/Card/ZoneBasicData.vue +++ b/src/pages/Zone/Card/ZoneBasicData.vue @@ -74,7 +74,7 @@ const agencyOptions = ref([]); type="number" min="0" /> - <VnInputTime v-model="data.hour" :label="t('Closing')" clearable /> + <VnInputTime v-model="data.hour" :label="t('Closing')" /> </VnRow> <VnRow class="row q-gutter-md q-mb-md"> diff --git a/src/pages/Zone/Card/ZoneDescriptor.vue b/src/pages/Zone/Card/ZoneDescriptor.vue index b3a55bbe0..602fefde3 100644 --- a/src/pages/Zone/Card/ZoneDescriptor.vue +++ b/src/pages/Zone/Card/ZoneDescriptor.vue @@ -38,7 +38,6 @@ const entityId = computed(() => { }); const data = ref(useCardDescription()); - const setData = (entity) => { data.value = useCardDescription(entity.ref, entity.id); }; @@ -53,23 +52,8 @@ const setData = (entity) => { :filter="filter" @on-fetch="setData" data-key="zoneData" + :summary="$attrs" > - <template #header-extra-action> - <QBtn - round - flat - dense - size="md" - icon="vn:zone" - color="white" - class="link" - :to="{ name: 'ZoneList' }" - > - <QTooltip> - {{ t('Summary') }} - </QTooltip> - </QBtn> - </template> <template #menu="{ entity }"> <ZoneDescriptorMenuItems :zone="entity" /> </template> @@ -82,3 +66,8 @@ const setData = (entity) => { </template> </CardDescriptor> </template> + +<i18n> +es: + Go to module index: Ir al índice del módulo +</i18n> diff --git a/src/pages/Zone/Card/ZoneDescriptorProxy.vue b/src/pages/Zone/Card/ZoneDescriptorProxy.vue index 15c5fb0e5..27102ac07 100644 --- a/src/pages/Zone/Card/ZoneDescriptorProxy.vue +++ b/src/pages/Zone/Card/ZoneDescriptorProxy.vue @@ -1,5 +1,6 @@ <script setup> import ZoneDescriptor from './ZoneDescriptor.vue'; +import ZoneSummary from './ZoneSummary.vue'; const $props = defineProps({ id: { @@ -11,6 +12,6 @@ const $props = defineProps({ <template> <QPopupProxy> - <ZoneDescriptor v-if="$props.id" :id="$props.id" /> + <ZoneDescriptor v-if="$props.id" :id="$props.id" :summary="ZoneSummary" /> </QPopupProxy> </template> diff --git a/src/router/index.js b/src/router/index.js index 41ff4c1da..faa3ab5d4 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -11,7 +11,6 @@ import { useState } from 'src/composables/useState'; import { useSession } from 'src/composables/useSession'; import { useRole } from 'src/composables/useRole'; import { useUserConfig } from 'src/composables/useUserConfig'; -import { toLowerCamel } from 'src/filters'; import { useTokenConfig } from 'src/composables/useTokenConfig'; import { useAcl } from 'src/composables/useAcl'; @@ -79,13 +78,11 @@ export default route(function (/* { store, ssrContext } */) { let title = t(`login.title`); const matches = to.matched; - let moduleName; if (matches && matches.length > 1) { const module = matches[1]; const moduleTitle = module.meta && module.meta.title; - moduleName = toLowerCamel(module.name); if (moduleTitle) { - title = t(`${moduleName}.pageTitles.${moduleTitle}`); + title = t(`globals.pageTitles.${moduleTitle}`); } } @@ -94,7 +91,7 @@ export default route(function (/* { store, ssrContext } */) { if (childPageTitle && matches.length > 2) { if (title != '') title += ': '; - const moduleLocale = `${moduleName}.pageTitles.${childPageTitle}`; + const moduleLocale = `globals.pageTitles.${childPageTitle}`; const pageTitle = te(moduleLocale) ? t(moduleLocale) : t(`globals.pageTitles.${childPageTitle}`); diff --git a/src/router/modules/agency.js b/src/router/modules/agency.js index 6f281e21d..c714865fa 100644 --- a/src/router/modules/agency.js +++ b/src/router/modules/agency.js @@ -18,7 +18,7 @@ export default { { path: '/agency/:id', name: 'AgencyCard', - component: () => import('src/pages/Agency/Card/AgencyCard.vue'), + component: () => import('src/pages/Route/Agency/Card/AgencyCard.vue'), redirect: { name: 'AgencySummary' }, children: [ { @@ -28,7 +28,8 @@ export default { title: 'summary', icon: 'view_list', }, - component: () => import('src/pages/Agency/Card/AgencySummary.vue'), + component: () => + import('src/pages/Route/Agency/Card/AgencySummary.vue'), }, { name: 'AgencyBasicData', @@ -37,7 +38,8 @@ export default { title: 'basicData', icon: 'vn:settings', }, - component: () => import('pages/Agency/Card/AgencyBasicData.vue'), + component: () => + import('pages/Route/Agency/Card/AgencyBasicData.vue'), }, { path: 'workCenter', @@ -52,7 +54,9 @@ export default { title: 'workCenters', }, component: () => - import('src/pages/Agency/Card/AgencyWorkcenter.vue'), + import( + 'src/pages/Route/Agency/Card/AgencyWorkcenter.vue' + ), }, ], }, @@ -69,7 +73,7 @@ export default { title: 'modes', }, component: () => - import('src/pages/Agency/Card/AgencyModes.vue'), + import('src/pages/Route/Agency/Card/AgencyModes.vue'), }, ], }, @@ -80,7 +84,7 @@ export default { title: 'log', icon: 'history', }, - component: () => import('src/pages/Agency/Card/AgencyLog.vue'), + component: () => import('src/pages/Route/Agency/Card/AgencyLog.vue'), }, ], }, diff --git a/src/router/modules/entry.js b/src/router/modules/entry.js index 67fc41824..4f5f05231 100644 --- a/src/router/modules/entry.js +++ b/src/router/modules/entry.js @@ -11,7 +11,7 @@ export default { component: RouterView, redirect: { name: 'EntryMain' }, menus: { - main: ['EntryList', 'EntryLatestBuys'], + main: ['EntryList', 'MyEntries', 'EntryLatestBuys'], card: ['EntryBasicData', 'EntryBuys', 'EntryNotes', 'EntryDms', 'EntryLog'], }, children: [ @@ -30,6 +30,15 @@ export default { }, component: () => import('src/pages/Entry/EntryList.vue'), }, + { + path: 'my', + name: 'MyEntries', + meta: { + title: 'labeler', + icon: 'sell', + }, + component: () => import('src/pages/Entry/MyEntries.vue'), + }, { path: 'create', name: 'EntryCreate', diff --git a/src/router/modules/item.js b/src/router/modules/item.js index 6e8659747..06fe680eb 100644 --- a/src/router/modules/item.js +++ b/src/router/modules/item.js @@ -201,15 +201,6 @@ export default { }, component: () => import('src/pages/Item/Card/ItemLog.vue'), }, - { - path: 'botanical', - name: 'ItemBotanical', - meta: { - title: 'botanical', - icon: 'vn:botanical', - }, - component: () => import('src/pages/Item/Card/ItemBotanical.vue'), - }, ], }, ], diff --git a/src/router/modules/route.js b/src/router/modules/route.js index 8e08d7222..72f423ae3 100644 --- a/src/router/modules/route.js +++ b/src/router/modules/route.js @@ -86,7 +86,8 @@ export default { title: 'agencyList', icon: 'view_list', }, - component: () => import('src/pages/Agency/AgencyList.vue'), + component: () => + import('src/pages/Route/Agency/AgencyList.vue'), }, ], }, diff --git a/src/router/modules/ticket.js b/src/router/modules/ticket.js index 81ca405ee..1e635470b 100644 --- a/src/router/modules/ticket.js +++ b/src/router/modules/ticket.js @@ -19,6 +19,10 @@ export default { 'TicketSale', 'TicketLog', 'TicketPurchaseRequest', + 'TicketService', + 'TicketTracking', + 'TicketVolume', + 'TicketNotes', ], }, children: [ @@ -29,8 +33,8 @@ export default { redirect: { name: 'TicketList' }, children: [ { - name: 'TicketList', path: 'list', + name: 'TicketList', meta: { title: 'list', icon: 'view_list', @@ -38,8 +42,8 @@ export default { component: () => import('src/pages/Ticket/TicketList.vue'), }, { - name: 'TicketCreate', path: 'create', + name: 'TicketCreate', meta: { title: 'createTicket', icon: 'vn:ticketAdd', @@ -48,8 +52,8 @@ export default { component: () => import('src/pages/Ticket/TicketCreate.vue'), }, { - name: 'TicketWeekly', path: 'weekly', + name: 'TicketWeekly', meta: { title: 'weeklyTickets', icon: 'access_time', @@ -57,8 +61,8 @@ export default { component: () => import('src/pages/Ticket/TicketWeekly.vue'), }, { - name: 'TicketFuture', path: 'future', + name: 'TicketFuture', meta: { title: 'futureTickets', icon: 'keyboard_double_arrow_right', @@ -66,8 +70,8 @@ export default { component: () => import('src/pages/Ticket/TicketFuture.vue'), }, { - name: 'TicketAdvance', path: 'advance', + name: 'TicketAdvance', meta: { title: 'ticketAdvance', icon: 'keyboard_double_arrow_left', @@ -83,8 +87,8 @@ export default { redirect: { name: 'TicketSummary' }, children: [ { - name: 'TicketSummary', path: 'summary', + name: 'TicketSummary', meta: { title: 'summary', icon: 'launch', @@ -92,8 +96,8 @@ export default { component: () => import('src/pages/Ticket/Card/TicketSummary.vue'), }, { - name: 'TicketBasicData', path: 'basic-data', + name: 'TicketBasicData', meta: { title: 'basicData', icon: 'vn:settings', @@ -102,8 +106,8 @@ export default { import('src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue'), }, { - name: 'TicketSale', path: 'sale', + name: 'TicketSale', meta: { title: 'sale', icon: 'vn:lines', @@ -120,6 +124,15 @@ export default { component: () => import('src/pages/Ticket/Card/TicketPurchaseRequest.vue'), }, + { + path: 'tracking', + name: 'TicketTracking', + meta: { + title: 'tracking', + icon: 'vn:eye', + }, + component: () => import('src/pages/Ticket/Card/TicketTracking.vue'), + }, { path: 'log', name: 'TicketLog', @@ -147,6 +160,34 @@ export default { }, component: () => import('src/pages/Ticket/Card/TicketSms.vue'), }, + + { + path: 'service', + name: 'TicketService', + meta: { + title: 'services', + icon: 'vn:services', + }, + component: () => import('src/pages/Ticket/Card/TicketService.vue'), + }, + { + path: 'volume', + name: 'TicketVolume', + meta: { + title: 'volume', + icon: 'vn:volume', + }, + component: () => import('src/pages/Ticket/Card/TicketVolume.vue'), + }, + { + path: 'observation', + name: 'TicketNotes', + meta: { + title: 'notes', + icon: 'vn:notes', + }, + component: () => import('src/pages/Ticket/Card/TicketNotes.vue'), + }, ], }, ], diff --git a/src/stores/useArrayDataStore.js b/src/stores/useArrayDataStore.js index ebe32f8d0..ea029bc12 100644 --- a/src/stores/useArrayDataStore.js +++ b/src/stores/useArrayDataStore.js @@ -3,36 +3,56 @@ import { defineStore } from 'pinia'; export const useArrayDataStore = defineStore('arrayDataStore', () => { const state = ref({}); + const defaultOpts = { + filter: {}, + userFilter: {}, + userParams: {}, + url: '', + limit: 10, + skip: 0, + order: '', + isLoading: false, + userParamsChanged: false, + exprBuilder: null, + searchUrl: 'params', + navigate: null, + page: 1, + }; function get(key) { return state.value[key]; } function set(key) { - state.value[key] = { - filter: {}, - userFilter: {}, - userParams: {}, - url: '', - limit: 10, - skip: 0, - order: '', - data: ref(), - isLoading: false, - userParamsChanged: false, - exprBuilder: null, - searchUrl: 'params', - navigate: null, - }; + state.value[key] = getDefaultState(); } function clear(key) { delete state.value[key]; } + function reset(key, opts = []) { + if (!opts.length) state.value[key] = getDefaultState(); + else + opts.forEach((opt) => { + if (opt.includes('.')) { + const [parent, child] = opt.split('.'); + state.value[key][parent][child] = defaultOpts[child]; + } else if (Object.hasOwn(state.value[key], opt)) + state.value[key][opt] = defaultOpts[opt]; + }); + } + + function getDefaultState() { + return Object.assign(JSON.parse(JSON.stringify(defaultOpts)), { + data: ref(), + }); + } + return { get, set, clear, + reset, }; }); diff --git a/src/stores/useNavigationStore.js b/src/stores/useNavigationStore.js index 4dd5ed2ae..51f266800 100644 --- a/src/stores/useNavigationStore.js +++ b/src/stores/useNavigationStore.js @@ -60,7 +60,7 @@ export const useNavigationStore = defineStore('navigationStore', () => { menuChildren = menuChildren.map(({ name, title, icon }) => ({ name, icon, - title: `${module}.pageTitles.${title}`, + title: `globals.pageTitles.${title}`, })); if (meta && meta.roles && role.hasAny(meta.roles) === false) return; @@ -70,7 +70,7 @@ export const useNavigationStore = defineStore('navigationStore', () => { children: menuChildren, }; if (meta) { - item.title = `${module}.pageTitles.${meta.title}`; + item.title = `globals.pageTitles.${meta.title}`; item.icon = meta.icon; } diff --git a/test/cypress/integration/entry/entryDms.spec.js b/test/cypress/integration/entry/entryDms.spec.js index 5f9fae3dd..ed4a3d79c 100644 --- a/test/cypress/integration/entry/entryDms.spec.js +++ b/test/cypress/integration/entry/entryDms.spec.js @@ -1,4 +1,4 @@ -describe('WagonTypeCreate', () => { +describe('EntryDms', () => { const entryId = 1; beforeEach(() => { diff --git a/test/cypress/integration/entry/myEntry.spec.js b/test/cypress/integration/entry/myEntry.spec.js new file mode 100644 index 000000000..fc80c93f8 --- /dev/null +++ b/test/cypress/integration/entry/myEntry.spec.js @@ -0,0 +1,20 @@ +// describe('WagonTypeCreate', () => { +// beforeEach(() => { +// cy.viewport(1920, 1080); +// cy.login('customer'); +// cy.visit(`/#/entry/my`, { +// onBeforeLoad(win) { +// cy.stub(win, 'open'); +// }, +// }); +// cy.waitForElement('.q-page', 6000); +// }); + +// it('should create edit and remove new dms', () => { +// cy.get( +// '[to="/null/2"] > .q-card > .column > .q-btn > .q-btn__content > .q-icon' +// ).click(); +// cy.get('.q-card__actions > .q-btn').click(); +// cy.window().its('open').should('be.called'); +// }); +// }); diff --git a/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js b/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js index ca0b309e9..a2e9998bb 100644 --- a/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInDescriptor.spec.js @@ -1,8 +1,7 @@ describe('InvoiceInDescriptor', () => { const dialogBtns = '.q-card__actions button'; const firstDescritorOpt = '.q-menu > .q-list > :nth-child(1) > .q-item__section'; - const isBookedField = - '.q-card:nth-child(3) .vn-label-value:nth-child(5) > .value > span'; + const isBookedField = '.q-card:nth-child(3) .vn-label-value:nth-child(5) .q-checkbox'; it('should booking and unbooking the invoice properly', () => { cy.viewport(1280, 720); @@ -12,10 +11,10 @@ describe('InvoiceInDescriptor', () => { cy.openActionsDescriptor(); cy.get(firstDescritorOpt).click(); cy.get(dialogBtns).eq(1).click(); - cy.get(isBookedField).should('have.attr', 'title', 'true'); + cy.get(isBookedField).should('have.attr', 'aria-checked', 'true'); cy.get(firstDescritorOpt).click(); cy.get(dialogBtns).eq(1).click(); - cy.get(isBookedField).should('have.attr', 'title', 'false'); + cy.get(isBookedField).should('have.attr', 'aria-checked', 'false'); }); }); diff --git a/test/cypress/integration/invoiceIn/invoiceInList.spec.js b/test/cypress/integration/invoiceIn/invoiceInList.spec.js index 5e2a5aa4c..b2fa10d52 100644 --- a/test/cypress/integration/invoiceIn/invoiceInList.spec.js +++ b/test/cypress/integration/invoiceIn/invoiceInList.spec.js @@ -5,12 +5,11 @@ describe('InvoiceInList', () => { ':nth-child(1) > :nth-child(1) > .justify-between > .flex > .q-chip > .q-chip__content'; const firstDetailBtn = '.q-card:nth-child(1) .q-btn:nth-child(2)'; const summaryHeaders = '.summaryBody .header-link'; - const screen = '.q-page-container > .q-drawer-container > .fullscreen'; beforeEach(() => { + cy.viewport(1920, 1080); cy.login('developer'); cy.visit(`/#/invoice-in/list`); - cy.get(screen).click(); }); it('should redirect on clicking a invoice', () => { diff --git a/test/cypress/integration/parking/parkingList.spec.js b/test/cypress/integration/parking/parkingList.spec.js index 3ac46f8cd..b78a660d1 100644 --- a/test/cypress/integration/parking/parkingList.spec.js +++ b/test/cypress/integration/parking/parkingList.spec.js @@ -8,9 +8,9 @@ describe('ParkingList', () => { const summaryHeader = '.summaryBody .header'; beforeEach(() => { + cy.viewport(1920, 1080); cy.login('developer'); cy.visit(`/#/parking/list`); - cy.closeSideMenu(); }); it('should redirect on clicking a parking', () => { diff --git a/test/cypress/integration/agency/agencyWorkCenter.spec.js b/test/cypress/integration/route/agency/agencyWorkCenter.spec.js similarity index 72% rename from test/cypress/integration/agency/agencyWorkCenter.spec.js rename to test/cypress/integration/route/agency/agencyWorkCenter.spec.js index 8349260ee..6d33dbc39 100644 --- a/test/cypress/integration/agency/agencyWorkCenter.spec.js +++ b/test/cypress/integration/route/agency/agencyWorkCenter.spec.js @@ -2,13 +2,10 @@ describe('AgencyWorkCenter', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('developer'); - cy.visit(`/#/agency`); + cy.visit(`/#/agency/11/workCenter`); }); it('assign workCenter', () => { - cy.visit(`/#/agency`); - cy.get(':nth-child(1) > :nth-child(1) > .card-list-body > .list-items').click(); - cy.get('[href="#/agency/11/workCenter"] > .q-item__section--main').click(); cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); cy.get( '.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container' @@ -17,8 +14,6 @@ describe('AgencyWorkCenter', () => { }); it('delete workCenter', () => { - cy.get(':nth-child(1) > :nth-child(1) > .card-list-body > .list-items').click(); - cy.get('[href="#/agency/11/workCenter"] > .q-item__section--main').click(); cy.get('.q-item__section--side > .q-btn > .q-btn__content > .q-icon').click(); cy.get('.q-notification__message').should( 'have.text', @@ -27,9 +22,6 @@ describe('AgencyWorkCenter', () => { }); it('error on duplicate workCenter', () => { - cy.visit(`/#/agency`); - cy.get(':nth-child(1) > :nth-child(1) > .card-list-body > .list-items').click(); - cy.get('[href="#/agency/11/workCenter"] > .q-item__section--main').click(); cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); cy.get( '.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container' diff --git a/test/cypress/integration/vnBreadcrumbs.spec.js b/test/cypress/integration/vnComponent/vnBreadcrumbs.spec.js similarity index 100% rename from test/cypress/integration/vnBreadcrumbs.spec.js rename to test/cypress/integration/vnComponent/vnBreadcrumbs.spec.js diff --git a/test/cypress/integration/VnLocation.spec.js b/test/cypress/integration/vnComponent/vnLocation.spec.js similarity index 100% rename from test/cypress/integration/VnLocation.spec.js rename to test/cypress/integration/vnComponent/vnLocation.spec.js diff --git a/test/cypress/integration/vnLog.spec.js b/test/cypress/integration/vnComponent/vnLog.spec.js similarity index 100% rename from test/cypress/integration/vnLog.spec.js rename to test/cypress/integration/vnComponent/vnLog.spec.js diff --git a/test/cypress/integration/vnSearchBar.spec.js b/test/cypress/integration/vnComponent/vnSearchBar.spec.js similarity index 65% rename from test/cypress/integration/vnSearchBar.spec.js rename to test/cypress/integration/vnComponent/vnSearchBar.spec.js index f1f3a9e82..580199bc3 100644 --- a/test/cypress/integration/vnSearchBar.spec.js +++ b/test/cypress/integration/vnComponent/vnSearchBar.spec.js @@ -3,13 +3,10 @@ describe('VnSearchBar', () => { const employeeId = ' #1'; const salesPersonId = ' #18'; const idGap = '.q-item > .q-item__label'; - const cardList = '.vn-card-list'; - - let url; + const vnTableRow = '.q-virtual-scroll__content'; beforeEach(() => { cy.login('developer'); cy.visit('#/customer/list'); - cy.url().then((currentUrl) => (url = currentUrl)); }); it('should redirect to customer summary page', () => { @@ -19,12 +16,12 @@ describe('VnSearchBar', () => { it('should stay on the list page if there are several results or none', () => { cy.writeSearchbar('salesA{enter}'); - checkCardListAndUrl(2); + checkTableLength(2); cy.clearSearchbar(); cy.writeSearchbar('0{enter}'); - checkCardListAndUrl(0); + checkTableLength(0); }); const searchAndCheck = (searchTerm, expectedText) => { @@ -33,10 +30,7 @@ describe('VnSearchBar', () => { cy.get(idGap).should('have.text', expectedText); }; - const checkCardListAndUrl = (expectedLength) => { - cy.get(cardList).then(($cardList) => { - expect($cardList.find('.q-card').length).to.equal(expectedLength); - cy.url().then((currentUrl) => expect(currentUrl).to.contain(url)); - }); + const checkTableLength = (expectedLength) => { + cy.get(vnTableRow).find('tr').should('have.length', expectedLength); }; }); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index 055cb8021..38a23f71c 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -50,8 +50,8 @@ Cypress.Commands.add('login', (user) => { }); }); -Cypress.Commands.add('waitForElement', (element) => { - cy.get(element, { timeout: 5000 }).should('be.visible'); +Cypress.Commands.add('waitForElement', (element, timeout = 5000) => { + cy.get(element, { timeout }).should('be.visible'); }); Cypress.Commands.add('getValue', (selector) => { @@ -221,10 +221,6 @@ Cypress.Commands.add('openLeftMenu', (element) => { if (element) cy.waitForElement(element); cy.get('.q-toolbar > .q-btn--round.q-btn--dense > .q-btn__content > .q-icon').click(); }); -Cypress.Commands.add('closeSideMenu', (element) => { - if (element) cy.waitForElement(element); - cy.get('.fullscreen.q-drawer__backdrop:not(.hidden)').click(); -}); Cypress.Commands.add('clearSearchbar', (element) => { if (element) cy.waitForElement(element); @@ -243,6 +239,5 @@ Cypress.Commands.add('validateContent', (selector, expectedValue) => { }); Cypress.Commands.add('openActionsDescriptor', () => { - cy.get('.descriptor > .header > .q-btn').click(); + cy.get('.header > :nth-child(3) > .q-btn__content > .q-icon').click(); }); -// registerCommands(); diff --git a/test/vitest/__tests__/components/Leftmenu.spec.js b/test/vitest/__tests__/components/Leftmenu.spec.js index ea1c51f8d..10d9d66fb 100644 --- a/test/vitest/__tests__/components/Leftmenu.spec.js +++ b/test/vitest/__tests__/components/Leftmenu.spec.js @@ -78,13 +78,13 @@ describe('Leftmenu', () => { { children: null, name: 'CustomerList', - title: 'customer.pageTitles.list', + title: 'globals.pageTitles.list', icon: 'view_list', }, { children: null, name: 'CustomerCreate', - title: 'customer.pageTitles.createCustomer', + title: 'globals.pageTitles.createCustomer', icon: 'vn:addperson', }, ];