refactor: refs #7129 refactor with vnTable

This commit is contained in:
Pablo Natek 2024-07-12 10:41:18 +02:00
parent 36fc988df2
commit 9098e63793
23 changed files with 498 additions and 852 deletions

View File

@ -14,5 +14,5 @@
"[vue]": { "[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"cSpell.words": ["axios"] "cSpell.words": ["axios", "composables"]
} }

View File

@ -35,6 +35,7 @@ const closeForm = async () => {
defineExpose({ defineExpose({
isLoading, isLoading,
onDataSaved,
}); });
</script> </script>

View File

@ -7,6 +7,7 @@ import { dashIfEmpty } from 'src/filters';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'components/common/VnInput.vue'; import VnInput from 'components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnInputTime from 'components/common/VnInputTime.vue';
import VnComponent from 'components/common/VnComponent.vue'; import VnComponent from 'components/common/VnComponent.vue';
const model = defineModel(undefined, { required: true }); const model = defineModel(undefined, { required: true });
@ -74,6 +75,15 @@ const defaultComponents = {
label: $props.showLabel && $props.column.label, label: $props.showLabel && $props.column.label,
}, },
}, },
time: {
component: markRaw(VnInputTime),
attrs: {
disable: !$props.isEditable,
},
forceAttrs: {
label: $props.showLabel && $props.column.label,
},
},
checkbox: { checkbox: {
component: markRaw(QCheckbox), component: markRaw(QCheckbox),
attrs: (prop) => { attrs: (prop) => {

View File

@ -3,17 +3,17 @@ import { ref, onMounted, computed, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import FormModelPopup from 'components/FormModelPopup.vue';
import CrudModel from 'src/components/CrudModel.vue';
import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
import VnLv from 'components/ui/VnLv.vue';
import CrudModel from 'src/components/CrudModel.vue';
import FormModelPopup from 'components/FormModelPopup.vue';
import VnFilterPanel from 'components/ui/VnFilterPanel.vue';
import VnTableColumn from 'components/VnTable/VnColumn.vue'; import VnTableColumn from 'components/VnTable/VnColumn.vue';
import VnTableFilter from 'components/VnTable/VnFilter.vue'; import VnTableFilter from 'components/VnTable/VnFilter.vue';
import VnTableChip from 'components/VnTable/VnChip.vue'; import VnTableChip from 'components/VnTable/VnChip.vue';
import TableVisibleColumns from 'src/components/VnTable/VnVisibleColumn.vue'; import TableVisibleColumns from 'src/components/VnTable/VnVisibleColumn.vue';
import VnLv from 'components/ui/VnLv.vue';
const $props = defineProps({ const $props = defineProps({
columns: { columns: {
@ -33,7 +33,7 @@ const $props = defineProps({
default: true, default: true,
}, },
rowClick: { rowClick: {
type: Function, type: [Function, Boolean],
default: null, default: null,
}, },
redirect: { redirect: {
@ -80,6 +80,10 @@ const $props = defineProps({
type: Object, type: Object,
default: () => ({}), default: () => ({}),
}, },
tableHeight: {
type: String,
default: '90vh',
},
}); });
const { t } = useI18n(); const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
@ -128,12 +132,6 @@ watch(
(val) => setUserParams(val) (val) => setUserParams(val)
); );
const rowClickFunction = computed(() => {
if ($props.rowClick) return $props.rowClick;
if ($props.redirect) return ({ id }) => redirectFn(id);
return () => {};
});
const isTableMode = computed(() => mode.value == TABLE_MODE); const isTableMode = computed(() => mode.value == TABLE_MODE);
function setUserParams(watchedParams) { function setUserParams(watchedParams) {
@ -180,6 +178,12 @@ function splitColumns(columns) {
} }
} }
const rowClickFunction = computed(() => {
if ($props.rowClick != undefined) return $props.rowClick;
if ($props.redirect) return ({ id }) => redirectFn(id);
return () => {};
});
function redirectFn(id) { function redirectFn(id) {
router.push({ path: `/${$props.redirect}/${id}` }); router.push({ path: `/${$props.redirect}/${id}` });
} }
@ -224,7 +228,6 @@ defineExpose({
:data-key="$attrs['data-key']" :data-key="$attrs['data-key']"
:search-button="true" :search-button="true"
v-model="params" v-model="params"
:disable-submit-event="true"
:search-url="searchUrl" :search-url="searchUrl"
:redirect="!!redirect" :redirect="!!redirect"
> >
@ -275,14 +278,14 @@ defineExpose({
table-header-class="bg-header" table-header-class="bg-header"
card-container-class="grid-three" card-container-class="grid-three"
flat flat
:style="isTableMode && 'max-height: 90vh'" :style="isTableMode && `max-height: ${tableHeight}`"
virtual-scroll virtual-scroll
@virtual-scroll=" @virtual-scroll="
(event) => (event) =>
event.index > rows.length - 2 && event.index > rows.length - 2 &&
CrudModelRef.vnPaginateRef.paginate() CrudModelRef.vnPaginateRef.paginate()
" "
@row-click="(_, row) => rowClickFunction(row)" @row-click="(_, row) => rowClickFunction && rowClickFunction(row)"
@update:selected="emit('update:selected', $event)" @update:selected="emit('update:selected', $event)"
> >
<template #top-left v-if="!$props.withoutHeader"> <template #top-left v-if="!$props.withoutHeader">
@ -546,8 +549,12 @@ defineExpose({
<i18n> <i18n>
en: en:
status: Status status: Status
table view: Table view
grid view: Grid view
es: es:
status: Estados status: Estados
table view: Vista en tabla
grid view: Vista en cuadrícula
</i18n> </i18n>
<style lang="scss"> <style lang="scss">
@ -557,7 +564,7 @@ es:
} }
.bg-header { .bg-header {
background-color: #5d5d5d; background-color: var(--vn-section-color);
color: var(--vn-text-color); color: var(--vn-text-color);
} }
@ -566,7 +573,7 @@ es:
.q-table--dark tr, .q-table--dark tr,
.q-table--dark th, .q-table--dark th,
.q-table--dark td { .q-table--dark td {
border-color: #222222; border-color: var(--vn-section-color);
} }
.q-table__container > div:first-child { .q-table__container > div:first-child {
@ -636,7 +643,7 @@ es:
right: 0; right: 0;
} }
td.sticky { td.sticky {
background-color: var(--q-dark); background-color: var(--vn-section-color);
z-index: 1; z-index: 1;
} }
} }

View File

@ -119,6 +119,7 @@ watch(
> >
<QDate <QDate
v-model="popupDate" v-model="popupDate"
:landscape="true"
:today-btn="true" :today-btn="true"
@update:model-value=" @update:model-value="
(date) => { (date) => {

View File

@ -43,7 +43,7 @@ const formattedTime = computed({
if (time?.length > 5) time = dateToTime(time); if (time?.length > 5) time = dateToTime(time);
if (!props.timeOnly) { if (!props.timeOnly) {
const hours = time.split(':'); const hours = time.split(':');
const date = new Date(); const date = new Date(model.value);
date.setHours(hours[0], hours[1], 0); date.setHours(hours[0], hours[1], 0);
time = date.toISOString(); time = date.toISOString();
} }
@ -100,13 +100,7 @@ watch(
self="top start" self="top start"
:no-focus="true" :no-focus="true"
> >
<QTime <QTime v-model="formattedTime" mask="HH:mm" landscape now-btn />
:format24h="false"
v-model="formattedTime"
mask="HH:mm"
landscape
now-btn
/>
</QMenu> </QMenu>
</QInput> </QInput>
</div> </div>

View File

@ -18,19 +18,18 @@ const $props = defineProps({
}, },
searchButton: { searchButton: {
type: Boolean, type: Boolean,
required: false,
default: false, default: false,
}, },
showAll: { showAll: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
unremovableParams: { unRemovableParams: {
type: Array, type: Array,
required: false, required: false,
default: () => [], default: () => [],
description: description: `Some filters come with default search parameters and require a value.
'Algunos filtros vienen con parametros de búsqueda por default y necesitan tener si o si un valor, por eso de ser necesario, esta prop nos sirve para saber que filtros podemos remover y cuales no', This prop helps us determine which filters can be removed and which cannot.`,
}, },
exprBuilder: { exprBuilder: {
type: Function, type: Function,
@ -113,7 +112,7 @@ async function search(evt) {
isLoading.value = true; isLoading.value = true;
const filter = { ...userParams.value }; const filter = { ...userParams.value };
store.userParamsChanged = true; store.userParamsChanged = true;
arrayData.reset(['skip', 'filter.skip', 'page']); console.log('userParams.value: ', userParams.value);
const { params: newParams } = await arrayData.addFilter({ params: userParams.value }); const { params: newParams } = await arrayData.addFilter({ params: userParams.value });
userParams.value = newParams; userParams.value = newParams;
@ -140,7 +139,7 @@ async function clearFilters() {
arrayData.reset(['skip', 'filter.skip', 'page']); arrayData.reset(['skip', 'filter.skip', 'page']);
// Filtrar los params no removibles // Filtrar los params no removibles
const removableFilters = Object.keys(userParams.value).filter((param) => const removableFilters = Object.keys(userParams.value).filter((param) =>
$props.unremovableParams.includes(param) $props.unRemovableParams.includes(param)
); );
const newParams = {}; const newParams = {};
// Conservar solo los params que no son removibles // Conservar solo los params que no son removibles
@ -249,7 +248,7 @@ function formatValue(value) {
<VnFilterPanelChip <VnFilterPanelChip
v-for="chip of tags" v-for="chip of tags"
:key="chip.label" :key="chip.label"
:removable="!unremovableParams.includes(chip.label)" :removable="!unRemovableParams.includes(chip.label)"
@remove="remove(chip.label)" @remove="remove(chip.label)"
> >
<slot name="tags" :tag="chip" :format-fn="formatValue"> <slot name="tags" :tag="chip" :format-fn="formatValue">

View File

@ -30,6 +30,7 @@ onBeforeUnmount(() => stateStore.toggleSubToolbar());
<template> <template>
<QToolbar <QToolbar
id="subToolbar"
class="justify-end sticky" class="justify-end sticky"
v-show="hasContent || $slots['st-actions'] || $slots['st-data']" v-show="hasContent || $slots['st-actions'] || $slots['st-data']"
> >

View File

@ -74,7 +74,11 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
}; };
let exprFilter; let exprFilter;
console.log('AQUIII: ', typeof store.userParams, store.userParams);
let userParams = { ...store.userParams }; let userParams = { ...store.userParams };
console.log('AQUIII: ', typeof userParams, userParams);
if (store?.exprBuilder) { if (store?.exprBuilder) {
const where = buildFilter(userParams, (param, value) => { const where = buildFilter(userParams, (param, value) => {
const res = store.exprBuilder(param, value); const res = store.exprBuilder(param, value);
@ -92,6 +96,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
filter.where = where; filter.where = where;
const params = { filter }; const params = { filter };
console.log('AQUIII: ', typeof userParams, userParams);
Object.assign(params, userParams); Object.assign(params, userParams);
params.filter.skip = store.skip; params.filter.skip = store.skip;
@ -152,12 +157,18 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
} }
async function addFilter({ filter, params }) { async function addFilter({ filter, params }) {
console.log('addFilter params: ', params);
if (filter) store.filter = filter; if (filter) store.filter = filter;
console.log('addFilter: ', store.userParams);
let userParams = { ...store.userParams, ...params }; let userParams = { ...store.userParams, ...params };
console.log('addFilter 2: ', userParams, store.userParams);
userParams = sanitizerParams(userParams, store?.exprBuilder); userParams = sanitizerParams(userParams, store?.exprBuilder);
console.log('addFilter 2.5: ', userParams);
store.userParams = userParams; store.userParams = userParams;
console.log('addFilter: 3', store.userParams);
arrayDataStore.reset(['skip', 'filter.skip', 'page']); arrayDataStore.reset(['skip', 'filter.skip', 'page']);
await fetch({ append: false }); await fetch({ append: false });
@ -173,6 +184,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
function sanitizerParams(params, exprBuilder) { function sanitizerParams(params, exprBuilder) {
for (const param in params) { for (const param in params) {
console.log('param: ', param);
if (params[param] === '' || params[param] === null) { if (params[param] === '' || params[param] === null) {
delete store.userParams[param]; delete store.userParams[param];
delete params[param]; delete params[param];

View File

@ -407,7 +407,6 @@ function handleLocation(data, location) {
:columns="columns" :columns="columns"
default-mode="table" default-mode="table"
redirect="customer" redirect="customer"
auto-load
> >
<template #more-create-dialog="{ data }"> <template #more-create-dialog="{ data }">
<VnLocation <VnLocation

View File

@ -78,6 +78,7 @@ const columns = computed(() => [
:columns="columns" :columns="columns"
:right-search="false" :right-search="false"
:use-model="true" :use-model="true"
redirect="agency"
/> />
</template> </template>
<i18n> <i18n>

View File

@ -1,162 +0,0 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const countries = ref();
const warehouses = ref();
</script>
<template>
<FetchData url="Countries" @on-fetch="(data) => (countries = data)" auto-load />
<FetchData url="Warehouses" @on-fetch="(data) => (warehouses = data)" auto-load />
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<QItem>
<QItemSection>
<VnInput
:label="t('route.cmr.list.cmrFk')"
v-model="params.cmrFk"
is-outlined
>
<template #prepend>
<QIcon name="article" size="sm"></QIcon>
</template>
</VnInput>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
:label="t('route.cmr.list.hasCmrDms')"
v-model="params.hasCmrDms"
lazy-rules
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
:label="t('route.cmr.list.ticketFk')"
v-model="params.ticketFk"
is-outlined
>
<template #prepend>
<QIcon name="vn:ticket" size="sm"></QIcon>
</template>
</VnInput>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
:label="t('route.cmr.list.routeFk')"
v-model="params.routeFk"
is-outlined
>
<template #prepend>
<QIcon name="vn:delivery" size="sm"></QIcon>
</template>
</VnInput>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
:label="t('route.cmr.list.clientFk')"
v-model="params.clientFk"
is-outlined
>
<template #prepend>
<QIcon name="vn:client" size="sm"></QIcon>
</template>
</VnInput>
</QItemSection>
</QItem>
<QItem>
<QItemSection v-if="!countries">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="countries">
<VnSelect
:label="t('route.cmr.list.country')"
v-model="params.country"
:options="countries"
option-label="name"
option-value="id"
transition-show="jump-down"
transition-hide="jump-up"
emit-value
map-options
dense
outlined
rounded
>
<template #prepend>
<QIcon name="flag" size="sm"></QIcon>
</template>
</VnSelect>
</QItemSection>
</QItem>
<QItem>
<VnSelect
:label="t('globals.warehouse')"
:options="warehouses"
hide-selected
option-label="name"
option-value="id"
v-model="params.warehouseFk"
rounded
dense
outlined
>
</VnSelect>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
v-model="params.shipped"
:label="t('route.cmr.list.shipped')"
is-outlined
/>
</QItemSection>
</QItem>
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
cmrFk: Cmr id
hasCmrDms: Attached in gestdoc
ticketFk: Ticketd id
country: Country
clientFk: Client id
shipped: Preparation date
es:
params:
cmrFk: Id cmr
hasCmrDms: Gestdoc
ticketFk: Id ticket
country: País
clientFk: Id cliente
shipped: Fecha preparación
</i18n>

View File

@ -2,82 +2,93 @@
import { onBeforeMount, onMounted, computed, ref } from 'vue'; import { onBeforeMount, onMounted, computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { Notify } from 'quasar'; import { Notify } from 'quasar';
import axios from 'axios';
import VnPaginate from 'components/ui/VnPaginate.vue';
import { useSession } from 'src/composables/useSession'; import { useSession } from 'src/composables/useSession';
import { toDate } from 'filters/index'; import { toDate } from 'filters/index';
import CmrFilter from './CmrFilter.vue';
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import { useStateStore } from 'src/stores/useStateStore'; import { useStateStore } from 'src/stores/useStateStore';
import axios from 'axios';
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
const { t } = useI18n(); const { t } = useI18n();
const { getTokenMultimedia } = useSession(); const { getTokenMultimedia } = useSession();
const token = getTokenMultimedia(); const token = getTokenMultimedia();
const state = useStateStore(); const state = useStateStore();
const selected = ref([]);
const warehouses = ref([]); const warehouses = ref([]);
const selectedRows = ref([]);
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left',
name: 'cmrFk', name: 'cmrFk',
label: t('route.cmr.list.cmrFk'), label: t('route.cmr.list.cmrFk'),
field: (row) => row.cmrFk, chip: {
sortable: true, condition: () => true,
},
isId: true,
}, },
{ {
align: 'left',
name: 'hasCmrDms', name: 'hasCmrDms',
label: t('route.cmr.list.hasCmrDms'), label: t('route.cmr.list.hasCmrDms'),
field: (row) => row.hasCmrDms, component: 'checkbox',
align: 'center', cardVisible: true,
sortable: true,
}, },
{ {
name: 'ticketFk',
label: t('route.cmr.list.ticketFk'), label: t('route.cmr.list.ticketFk'),
field: (row) => row.ticketFk, name: 'ticketFk',
sortable: true,
}, },
{ {
name: 'routeFkFk',
label: t('route.cmr.list.routeFk'), label: t('route.cmr.list.routeFk'),
field: (row) => row.routeFk, name: 'routeFk',
sortable: true,
}, },
{ {
name: 'clientFk',
label: t('route.cmr.list.clientFk'), label: t('route.cmr.list.clientFk'),
field: (row) => row.clientFk, name: 'clientFk',
sortable: true,
}, },
{ {
name: 'country',
label: t('route.cmr.list.country'),
field: (row) => row.country,
align: 'left', align: 'left',
sortable: true, label: t('route.cmr.list.country'),
name: 'country',
cardVisible: true,
}, },
{ {
name: 'shipped',
label: t('route.cmr.list.shipped'),
field: (row) => toDate(row.shipped),
align: 'center', align: 'center',
sortable: true, label: t('route.cmr.list.shipped'),
name: 'shipped',
cardVisible: true,
component: 'date',
columnFilter: {
alias: 'c',
inWhere: true,
},
format: ({ date }) => toDate(date),
}, },
{ {
align: 'center',
name: 'warehouseFk', name: 'warehouseFk',
label: t('globals.warehouse'), label: t('globals.warehouse'),
field: ({ warehouseFk }) => warehouseFk, component: 'select',
align: 'center', attrs: {
sortable: true, url: 'warehouses',
fields: ['id', 'name'],
optionLabel: 'name',
optionValue: 'id',
},
}, },
{ {
name: 'icons', align: 'right',
align: 'center', name: 'tableActions',
field: (row) => row.cmrFk, actions: [
{
title: t('Ver cmr'),
icon: 'visibility',
isPrimary: true,
action: (row) => window.open(getCmrUrl(row?.cmrFk), '_blank'),
},
],
}, },
]); ]);
@ -95,7 +106,7 @@ function getCmrUrl(value) {
return `${getApiUrl()}/api/Routes/${value}/cmr?access_token=${token}`; return `${getApiUrl()}/api/Routes/${value}/cmr?access_token=${token}`;
} }
function downloadPdfs() { function downloadPdfs() {
if (!selected.value.length) { if (!selectedRows.value.length) {
Notify.create({ Notify.create({
message: t('globals.noSelectedRows'), message: t('globals.noSelectedRows'),
type: 'warning', type: 'warning',
@ -103,42 +114,51 @@ function downloadPdfs() {
return; return;
} }
let cmrs = []; let cmrs = [];
for (let value of selected.value) cmrs.push(value.cmrFk); for (let value of selectedRows.value) cmrs.push(value.cmrFk);
// prettier-ignore // prettier-ignore
return window.open(`${getApiUrl()}/api/Routes/downloadCmrsZip?ids=${cmrs.join(',')}&access_token=${token}`); return window.open(`${getApiUrl()}/api/Routes/downloadCmrsZip?ids=${cmrs.join(',')}&access_token=${token}`);
} }
</script> </script>
<template> <template>
<VnSubToolbar>
<template #st-actions>
<QBtn
icon="cloud_download"
color="primary"
class="q-mr-sm"
:disable="!selectedRows?.length"
@click="downloadPdfs"
>
<QTooltip>{{ t('route.cmr.list.downloadCmrs') }}</QTooltip>
</QBtn>
</template>
</VnSubToolbar>
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="CmrList" data-key="CmrList"
url="Routes/cmrs" url="Routes/cmrs"
order="cmrFk DESC"
:columns="columns" :columns="columns"
:right-search="true" :right-search="true"
:use-model="true" default-mode="table"
/> v-model:selected="selectedRows"
table-height="85vh"
:table="{
'row-key': 'cmrFk',
selection: 'multiple',
}"
:disable-option="{ card: true }"
>
<template #column-ticketFk="{ row }">
<span class="link" @click.stop>
{{ row.ticketFk }}
<TicketDescriptorProxy :id="row.ticketFk" />
</span>
</template>
<template #column-clientFk="{ row }">
<span class="link" @click.stop>
{{ row.ticketFk }}
<CustomerDescriptorProxy :id="row.clientFk" />
</span>
</template>
</VnTable>
</template> </template>
<style lang="scss" scoped>
.list {
padding-top: 15px;
padding-bottom: 15px;
max-width: 1000px;
width: 100%;
}
.grid-style-transition {
transition: transform 0.28s, background-color 0.28s;
}
#true {
background-color: $positive;
}
#false {
background-color: $negative;
}
:deep(.q-table th) {
max-width: 80px;
}
:deep(.q-table th:nth-child(3)) {
max-width: 100px;
}
</style>

View File

@ -1,88 +0,0 @@
<script setup>
import { useDialogPluginComponent } from 'quasar';
import { useI18n } from 'vue-i18n';
import { reactive, ref } from 'vue';
import axios from 'axios';
import FetchData from 'components/FetchData.vue';
import RoadmapAddStopForm from 'pages/Route/Roadmap/RoadmapAddStopForm.vue';
const emit = defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef, onDialogHide } = useDialogPluginComponent();
const { t } = useI18n();
const props = defineProps({
roadmapFk: {
type: Number,
required: true,
},
});
const isLoading = ref(false);
const roadmapStopForm = reactive({
roadmapFk: Number(props.roadmapFk) || 0,
warehouseFk: null,
eta: new Date().toISOString(),
description: '',
});
const updateDefaultStop = (data) => {
const eta = new Date(data.etd);
eta.setDate(eta.getDate() + 1);
roadmapStopForm.eta = eta.toISOString();
};
const onSave = async () => {
isLoading.value = true;
try {
await axios.post('RoadmapStops', { ...roadmapStopForm });
emit('ok');
} finally {
isLoading.value = false;
emit('hide');
}
};
</script>
<template>
<FetchData
:url="`Roadmaps/${roadmapFk}`"
auto-load
:filter="{ fields: ['etd'] }"
@on-fetch="updateDefaultStop"
/>
<QDialog ref="dialogRef" @hide="onDialogHide">
<QCard class="q-pa-lg">
<QCardSection class="row absolute absolute-top z-fullscreen">
<QSpace />
<QBtn icon="close" flat round dense v-close-popup />
</QCardSection>
<RoadmapAddStopForm
:roadmap-fk="roadmapFk"
:form-data="roadmapStopForm"
layout="dialog"
/>
<QCardActions align="right">
<QBtn :label="t('globals.cancel')" color="primary" flat v-close-popup />
<QBtn
:label="t('globals.confirm')"
color="primary"
:loading="isLoading"
@click="onSave"
unelevated
/>
</QCardActions>
</QCard>
</QDialog>
</template>
<style lang="scss" scoped>
.card-section {
gap: 16px;
}
</style>
<i18n>
es:
Warehouse: Almacén
ETA date: Fecha ETA
ETA time: Hora ETA
Description: Descripción
</i18n>

View File

@ -1,45 +0,0 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import VnRow from 'components/ui/VnRow.vue';
import FormModel from 'components/FormModel.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
import VnInputTime from 'components/common/VnInputTime.vue';
const { t } = useI18n();
const router = useRouter();
const defaultInitialData = {
etd: Date.vnNew().toISOString(),
};
const onSave = (data, response) => {
router.push({ name: 'RoadmapSummary', params: { id: response?.id } });
};
</script>
<template>
<VnSubToolbar />
<FormModel
:url="null"
url-create="Roadmaps"
model="roadmap"
:observe-form-changes="false"
:auto-load="false"
:form-initial-data="defaultInitialData"
@on-data-saved="onSave"
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<VnInput v-model="data.name" :label="t('Roadmap')" clearable />
<VnInputDate v-model="data.etd" :label="t('ETD date')" />
<VnInputTime v-model="data.etd" :label="t('ETD hour')" />
</VnRow>
</template>
</FormModel>
</template>
<i18n>
es:
Roadmap: Troncal
ETD date: Fecha ETD
ETD hour: Hora ETD
</i18n>

View File

@ -2,14 +2,14 @@
import { computed, onMounted, onUnmounted, ref } from 'vue'; import { computed, onMounted, onUnmounted, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { QIcon, useQuasar } from 'quasar'; import { QIcon } from 'quasar';
import { dashIfEmpty, toDateHourMin } from 'src/filters'; import { dashIfEmpty, toDateHourMin } from 'src/filters';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import VnLv from 'components/ui/VnLv.vue'; import VnLv from 'components/ui/VnLv.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue'; import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue';
import VnLinkPhone from 'components/ui/VnLinkPhone.vue'; import VnLinkPhone from 'components/ui/VnLinkPhone.vue';
import RoadmapAddStopDialog from 'pages/Route/Roadmap/RoadmapAddStopDialog.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -20,7 +20,6 @@ const $props = defineProps({
const route = useRoute(); const route = useRoute();
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar();
const summary = ref(null); const summary = ref(null);
const entityId = computed(() => $props.id || route.params.id); const entityId = computed(() => $props.id || route.params.id);
@ -37,7 +36,7 @@ const columns = ref([
{ {
name: 'address', name: 'address',
label: t('Address'), label: t('Address'),
field: (row) => dashIfEmpty(row?.address?.nickname), field: (row) => dashIfEmpty(row?.address?.addressFk),
sortable: true, sortable: true,
align: 'left', align: 'left',
}, },
@ -69,15 +68,6 @@ const filter = {
], ],
where: { id: entityId }, where: { id: entityId },
}; };
const openAddStopDialog = () => {
quasar
.dialog({
component: RoadmapAddStopDialog,
componentProps: { roadmapFk: entityId.value },
})
.onOk(() => summary.value.fetch());
};
</script> </script>
<template> <template>
@ -98,6 +88,10 @@ const openAddStopDialog = () => {
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<QCard class="vn-one"> <QCard class="vn-one">
<VnTitle
:url="`#/route/roadmap/${entityId}/basic-data`"
:text="t('Basic Data')"
/>
<VnLv :label="t('Carrier')"> <VnLv :label="t('Carrier')">
<template #value> <template #value>
<span class="link" v-if="entity?.supplier?.id"> <span class="link" v-if="entity?.supplier?.id">
@ -115,8 +109,6 @@ const openAddStopDialog = () => {
:label="t('Trailer Plate')" :label="t('Trailer Plate')"
:value="dashIfEmpty(entity?.trailerPlate)" :value="dashIfEmpty(entity?.trailerPlate)"
/> />
</QCard>
<QCard class="vn-one">
<VnLv :label="t('Phone')" :value="dashIfEmpty(entity?.phone)"> <VnLv :label="t('Phone')" :value="dashIfEmpty(entity?.phone)">
<template #value> <template #value>
<span> <span>
@ -139,26 +131,10 @@ const openAddStopDialog = () => {
/> />
</QCard> </QCard>
<QCard class="vn-max"> <QCard class="vn-max">
<div class="flex items-center"> <VnTitle
<a class="header" :href="`#/route/roadmap/${entityId}/stops`"> :url="`#/route/roadmap/${entityId}/stops`"
{{ t('Stops') }} :text="t('Stops')"
<QIcon name="open_in_new" color="primary"> />
<QTooltip>
{{ t('Go to stops') }}
</QTooltip>
</QIcon>
</a>
<QIcon
name="add_circle"
color="primary"
class="q-ml-lg header cursor-pointer"
@click.stop="openAddStopDialog"
>
<QTooltip>
{{ t('Add stop') }}
</QTooltip>
</QIcon>
</div>
<QTable <QTable
:columns="columns" :columns="columns"
:rows="entity?.roadmapStop" :rows="entity?.roadmapStop"

View File

@ -1,33 +1,29 @@
<script setup> <script setup>
import VnPaginate from 'components/ui/VnPaginate.vue';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { computed, onMounted, onUnmounted, ref } from 'vue'; import { computed, onMounted, onUnmounted, ref } from 'vue';
import { dashIfEmpty, toCurrency, toDate } from 'src/filters'; import { toCurrency, toDate } from 'src/filters';
import VnSearchbar from 'components/ui/VnSearchbar.vue'; import { useSummaryDialog } from 'composables/useSummaryDialog';
import { useState } from 'composables/useState';
import useNotify from 'composables/useNotify';
import axios from 'axios';
import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
import RouteDescriptorProxy from 'pages/Route/Card/RouteDescriptorProxy.vue'; import RouteDescriptorProxy from 'pages/Route/Card/RouteDescriptorProxy.vue';
import InvoiceInDescriptorProxy from 'pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue'; import InvoiceInDescriptorProxy from 'pages/InvoiceIn/Card/InvoiceInDescriptorProxy.vue';
import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue'; import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue';
import VnLv from 'components/ui/VnLv.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue';
import useNotify from 'composables/useNotify';
import RouteAutonomousFilter from 'pages/Route/Card/RouteAutonomousFilter.vue';
import { useRouter } from 'vue-router';
import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
import { useSummaryDialog } from 'composables/useSummaryDialog';
import VnDms from 'components/common/VnDms.vue'; import VnDms from 'components/common/VnDms.vue';
import { useState } from 'composables/useState'; import VnTable from 'components/VnTable/VnTable.vue';
import axios from 'axios'; import VnLv from 'components/ui/VnLv.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
const { viewSummary } = useSummaryDialog();
const { notify } = useNotify();
const { t } = useI18n();
const stateStore = useStateStore(); const stateStore = useStateStore();
const { t } = useI18n(); const refreshKey = ref(0);
const { notify } = useNotify();
const router = useRouter();
const { viewSummary } = useSummaryDialog();
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
const state = useState(); const state = useState();
const user = state.getUser(); const user = state.getUser();
const dmsDialog = ref({ const dmsDialog = ref({
@ -36,87 +32,89 @@ const dmsDialog = ref({
rowsToCreateInvoiceIn: [], rowsToCreateInvoiceIn: [],
}); });
const selectedRows = ref([]); const selectedRows = ref([]);
const tableRef = ref();
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'ID', align: 'left',
name: 'id',
label: 'Id', label: 'Id',
field: (row) => row.routeFk, chip: {
sortable: true, condition: () => true,
align: 'left', },
isId: true,
}, },
{ {
name: 'date', align: 'left',
name: 'created',
label: t('Date'), label: t('Date'),
field: (row) => toDate(row.created), component: 'date',
sortable: true, columnFilter: {
align: 'left', alias: 'c',
inWhere: true,
},
format: ({ date }) => toDate(date),
}, },
{ {
name: 'agency-route', align: 'left',
name: 'agencyModeName',
label: t('Agency route'), label: t('Agency route'),
field: (row) => row?.agencyModeName, cardVisible: true,
sortable: true,
align: 'left',
}, },
{ {
name: 'agency-agreement', align: 'left',
name: 'agencyAgreement',
label: t('Agency agreement'), label: t('Agency agreement'),
field: (row) => row?.agencyAgreement, cardVisible: true,
sortable: true,
align: 'left',
}, },
{ {
align: 'left',
name: 'packages', name: 'packages',
label: t('Packages'), label: t('Packages'),
field: (row) => dashIfEmpty(row.packages),
sortable: true,
align: 'left',
}, },
{ {
name: 'volume', align: 'left',
name: 'm3',
label: 'm³', label: 'm³',
field: (row) => dashIfEmpty(row.m3), cardVisible: true,
sortable: true, columnFilter: {
align: 'left', inWhere: true,
},
}, },
{ {
name: 'km', align: 'left',
name: 'kmTotal',
label: t('Km'), label: t('Km'),
field: (row) => dashIfEmpty(row?.kmTotal), cardVisible: true,
sortable: true,
align: 'left',
}, },
{ {
align: 'left',
name: 'price', name: 'price',
label: t('Price'), label: t('Price'),
field: (row) => (row?.price ? toCurrency(row.price) : '-'),
sortable: true,
align: 'left',
}, },
{ {
name: 'received', align: 'left',
name: 'invoiceInFk',
label: t('Received'), label: t('Received'),
field: (row) => row?.invoiceInFk,
sortable: true,
align: 'left',
}, },
{ {
name: 'autonomous', align: 'left',
name: 'supplierName',
label: t('Autonomous'), label: t('Autonomous'),
field: (row) => row?.supplierName,
sortable: true,
align: 'left',
}, },
{ {
name: 'actions',
label: '',
sortable: false,
align: 'right', align: 'right',
name: 'tableActions',
actions: [
{
title: t('Preview'),
icon: 'preview',
isPrimary: true,
action: (row) => viewSummary(row?.routeFk, RouteSummary),
},
],
}, },
]); ]);
const refreshKey = ref(0);
const total = computed(() => { const total = computed(() => {
return selectedRows.value.reduce((sum, item) => sum + item.price, 0); return selectedRows.value.reduce((sum, item) => sum + item.price, 0);
}); });
@ -169,9 +167,8 @@ const onDmsSaved = async (dms, response) => {
refreshKey.value++; refreshKey.value++;
}; };
function navigateToRouteSummary(event, row) { onMounted(() => (stateStore.rightDrawer = true));
router.push({ name: 'RouteSummary', params: { id: row.routeFk } }); onUnmounted(() => (stateStore.rightDrawer = false));
}
</script> </script>
<template> <template>
@ -180,13 +177,6 @@ function navigateToRouteSummary(event, row) {
:label="t('Search autonomous')" :label="t('Search autonomous')"
:info="t('You can search by autonomous reference')" :info="t('You can search by autonomous reference')"
/> />
<RightMenu>
<template #right-panel>
<RouteAutonomousFilter data-key="RouteAutonomousList" />
</template>
</RightMenu>
<QPage class="column items-center">
<div class="route-list">
<div class="q-pa-md"> <div class="q-pa-md">
<QCard class="vn-one q-py-sm q-px-lg flex justify-between"> <QCard class="vn-one q-py-sm q-px-lg flex justify-between">
<VnLv class="flex"> <VnLv class="flex">
@ -209,80 +199,41 @@ function navigateToRouteSummary(event, row) {
</QBtn> </QBtn>
</QCard> </QCard>
</div> </div>
<VnPaginate <VnTable
:key="refreshKey" ref="tableRef"
data-key="RouteAutonomousList" data-key="RouteAutonomousList"
url="AgencyTerms/filter" url="AgencyTerms/filter"
:limit="20"
>
<template #body="{ rows }">
<div class="q-pa-md">
<QTable
v-model:selected="selectedRows"
:columns="columns" :columns="columns"
:rows="rows" :right-search="true"
flat default-mode="table"
row-key="routeFk" v-model:selected="selectedRows"
selection="multiple" table-height="85vh"
:rows-per-page-options="[0]" redirect="route"
hide-pagination :row-click="({ routeFk }) => tableRef.redirect(routeFk)"
@row-click="navigateToRouteSummary" :table="{
'row-key': '$index',
selection: 'multiple',
}"
> >
<template #body-cell-ID="props"> <template #column-id="{ row }">
<QTd :props="props">
<span class="link" @click.stop> <span class="link" @click.stop>
{{ props.value }} {{ row.routeFk }}
<RouteDescriptorProxy :id="props.value" /> <RouteDescriptorProxy :id="row.route.id" />
</span> </span>
</QTd>
</template> </template>
<template #body-cell-received="props"> <template #column-invoiceInFk="{ row }">
<QTd :props="props">
<span :class="props.value && 'link'" @click.stop>
{{ dashIfEmpty(props.value) }}
<InvoiceInDescriptorProxy
v-if="props.value"
:id="props.value"
/>
</span>
</QTd>
</template>
<template #body-cell-autonomous="props">
<QTd :props="props">
<span class="link" @click.stop> <span class="link" @click.stop>
{{ props.value }} {{ row.invoiceInFk }}
<SupplierDescriptorProxy <InvoiceInDescriptorProxy v-if="row.invoiceInFk" :id="row.invoiceInFk" />
v-if="props.row?.supplierFk"
:id="props.row?.supplierFk"
/>
</span> </span>
</QTd>
</template> </template>
<template #body-cell-actions="props"> <template #column-supplierName="{ row }">
<QTd :props="props"> <span class="link" @click.stop>
<div class="table-actions"> {{ row.supplierName }}
<QIcon <SupplierDescriptorProxy :id="row.supplierFk" />
name="preview" </span>
size="sm"
color="primary"
@click.stop="
viewSummary(
props?.row?.routeFk,
RouteSummary
)
"
>
<QTooltip>{{ t('Preview') }}</QTooltip>
</QIcon>
</div>
</QTd>
</template> </template>
</QTable> </VnTable>
</div>
</template>
</VnPaginate>
</div>
</QPage>
<QDialog v-model="dmsDialog.show"> <QDialog v-model="dmsDialog.show">
<VnDms <VnDms
url="dms/uploadFile" url="dms/uploadFile"
@ -292,29 +243,6 @@ function navigateToRouteSummary(event, row) {
/> />
</QDialog> </QDialog>
</template> </template>
<style lang="scss" scoped>
.route-list {
width: 100%;
}
.table-actions {
display: flex;
align-items: center;
gap: 12px;
i {
cursor: pointer;
}
}
th:last-child,
td:last-child {
background-color: var(--vn-section-color);
position: sticky;
right: 0;
}
</style>
<i18n> <i18n>
es: es:
Search autonomous: Buscar autónomos Search autonomous: Buscar autónomos

View File

@ -1,50 +1,41 @@
<script setup> <script setup>
import { useArrayData } from 'composables/useArrayData'; import { computed, ref } from 'vue';
import { dashIfEmpty, toHour } from 'src/filters';
import { computed, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useValidator } from 'composables/useValidator';
import { useSession } from 'composables/useSession'; import { useSession } from 'composables/useSession';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { toDate } from 'src/filters';
import { useRouter } from 'vue-router';
import axios from 'axios'; import axios from 'axios';
import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue'; import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue';
import FetchData from 'components/FetchData.vue';
import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.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 RouteSummary from 'pages/Route/Card/RouteSummary.vue';
import VnPaginate from 'components/ui/VnPaginate.vue';
import VnInput from 'components/common/VnInput.vue';
import VnInputTime from 'components/common/VnInputTime.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import { useStateStore } from 'src/stores/useStateStore';
const { t } = useI18n(); const { t } = useI18n();
const { validate } = useValidator();
const { viewSummary } = useSummaryDialog(); const { viewSummary } = useSummaryDialog();
const quasar = useQuasar(); const quasar = useQuasar();
const session = useSession(); const session = useSession();
const paginate = ref();
const visibleColumns = ref([]);
const selectedRows = ref([]); const selectedRows = ref([]);
const workers = ref([]); const tableRef = ref([]);
const agencyList = ref([]);
const vehicleList = ref([]);
const allColumnNames = ref([]);
const confirmationDialog = ref(false); const confirmationDialog = ref(false);
const startingDate = ref(null); const startingDate = ref(null);
const refreshKey = ref(0); const refreshKey = ref(0);
const router = useRouter(); const router = useRouter();
const stateStore = useStateStore(); const routeFilter = {
include: [
{
relation: 'workers',
scope: {
fields: ['id', 'firstName'],
},
},
],
};
const columns = computed(() => [ const columns = computed(() => [
{ {
align: 'left', align: 'left',
@ -60,75 +51,114 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
name: 'worker', name: 'workerFk',
label: t('Worker'), label: t('Worker'),
create: true, create: true,
component: 'select', component: 'select',
attrs: { attrs: {
url: 'payrollComponents', url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name'], fields: ['id', 'nickname'],
optionValue: 'id',
optionLabel: 'nickname',
}, },
useLike: false,
cardVisible: true, cardVisible: true,
format: (row, dashIfEmpty) => dashIfEmpty(row.travelRef),
}, },
{ {
name: 'agency',
label: t('Agency'),
align: 'left', align: 'left',
name: 'agencyModeFk',
label: t('Agency'),
isTitle: true, isTitle: true,
cardVisible: true, cardVisible: true,
create: true, create: true,
component: 'select',
attrs: {
url: 'agencyModes',
fields: ['id', 'name'],
optionLabel: 'name',
optionValue: 'id',
},
}, },
{ {
name: 'vehicle', align: 'left',
name: 'vehicleFk',
label: t('Vehicle'), label: t('Vehicle'),
align: 'left',
cardVisible: true, cardVisible: true,
create: true, create: true,
component: 'select',
attrs: {
url: 'vehicles',
fields: ['id', 'numberPlate'],
optionLabel: 'numberPlate',
optionValue: 'id',
},
}, },
{ {
name: 'date', align: 'left',
name: 'created',
label: t('Date'), label: t('Date'),
align: 'left',
cardVisible: true, cardVisible: true,
create: true, create: true,
component: 'date',
columnFilter: {
alias: 'c',
inWhere: true,
},
format: ({ date }) => toDate(date),
}, },
{ {
name: 'volume',
label: 'm³',
align: 'center', align: 'center',
name: 'm3',
label: 'volume',
cardVisible: true, cardVisible: true,
}, },
{ {
align: 'left',
name: 'description', name: 'description',
label: t('Description'), label: t('Description'),
align: 'left',
isTitle: true, isTitle: true,
create: true, create: true,
component: 'input',
field: 'description',
}, },
{ {
align: 'left',
name: 'started', name: 'started',
label: t('hourStarted'), label: t('hourStarted'),
align: 'left', component: 'time',
}, },
{ {
align: 'left',
name: 'finished', name: 'finished',
label: t('hourFinished'), label: t('hourFinished'),
align: 'left', component: 'time',
}, },
{ {
name: 'isServed',
label: t('Served'),
align: 'left', align: 'left',
name: 'isOk',
label: t('Served'),
component: 'checkbox',
}, },
{ {
align: 'right', align: 'right',
label: 'asdasd',
name: 'tableActions', name: 'tableActions',
actions: [ actions: [
{ {
title: t('Client ticket list'), title: t('Add tickets'),
icon: 'vn:ticketAdd',
action: (row) => openTicketsDialog(row?.id),
},
{
title: t('Preview'),
icon: 'preview', icon: 'preview',
action: (row) => navigate(row.id), action: (row) => viewSummary(row?.id, RouteSummary),
},
{
title: t('Route summary'),
icon: 'arrow_forward',
isPrimary: true,
action: (row) => navigate(row?.id),
}, },
], ],
}, },
@ -138,22 +168,14 @@ function navigate(id) {
router.push({ path: `/route/${id}` }); router.push({ path: `/route/${id}` });
} }
const updateRoute = async (route) => {
try {
return await axios.patch(`Routes/${route.id}`, route);
} catch (err) {
return err;
}
};
const cloneRoutes = () => { const cloneRoutes = () => {
if (!selectedRows.value.length || !startingDate.value) return;
axios.post('Routes/clone', { axios.post('Routes/clone', {
created: startingDate.value, created: startingDate.value,
ids: selectedRows.value.map((row) => row?.id), ids: selectedRows.value.map((row) => row?.id),
}); });
refreshKey.value++; tableRef.value.reload();
startingDate.value = null; startingDate.value = null;
paginate.value.fetch();
}; };
const showRouteReport = () => { const showRouteReport = () => {
@ -175,16 +197,13 @@ const showRouteReport = () => {
function markAsServed() { function markAsServed() {
selectedRows.value.forEach(async (row) => { selectedRows.value.forEach(async (row) => {
if (row?.id) await axios.patch(`Routes/${row?.id}`, { isOk: true }); await axios.patch(`Routes/${row?.id}`, { isOk: true });
}); });
refreshKey.value++; tableRef.value.reload();
startingDate.value = null; startingDate.value = null;
} }
const openTicketsDialog = (id) => { const openTicketsDialog = (id) => {
if (!id) {
return;
}
quasar quasar
.dialog({ .dialog({
component: RouteListTicketsDialog, component: RouteListTicketsDialog,
@ -215,21 +234,10 @@ const openTicketsDialog = (id) => {
<QBtn flat :label="t('Cancel')" v-close-popup class="text-primary" /> <QBtn flat :label="t('Cancel')" v-close-popup class="text-primary" />
<QBtn color="primary" v-close-popup @click="cloneRoutes"> <QBtn color="primary" v-close-popup @click="cloneRoutes">
{{ t('globals.clone') }} {{ t('globals.clone') }}
<VnLv
:label="t('route.summary.packages')"
:value="getTotalPackages(entity.tickets)"
/>
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</QCard> </QCard>
</QDialog> </QDialog>
<FetchData
url="Workers/activeWithInheritedRole"
@on-fetch="(data) => (workers = data)"
auto-load
/>
<FetchData url="AgencyModes" @on-fetch="(data) => (agencyList = data)" auto-load />
<FetchData url="Vehicles" @on-fetch="(data) => (vehicleList = data)" auto-load />
<VnSubToolbar /> <VnSubToolbar />
<VnTable <VnTable
ref="tableRef" ref="tableRef"
@ -239,13 +247,23 @@ const openTicketsDialog = (id) => {
:right-search="true" :right-search="true"
default-mode="table" default-mode="table"
:is-editable="true" :is-editable="true"
:filter="routeFilter"
redirect="route"
:create="{ :create="{
urlCreate: 'Routes', urlCreate: 'Routes',
title: t('Create route'), title: t('Create route'),
onDataSaved: () => tableRef.reload(), onDataSaved: ({ id }) => tableRef.redirect(id),
formInitialData: {},
}" }"
save-url="routes" save-url="Routes/crud"
:disable-option="{ card: true }" :disable-option="{ card: true }"
:use-model="true"
table-height="85vh"
v-model:selected="selectedRows"
:table="{
'row-key': 'id',
selection: 'multiple',
}"
> >
<template #moreBeforeActions> <template #moreBeforeActions>
<QBtn <QBtn

View File

@ -1,65 +1,68 @@
<script setup> <script setup>
import VnPaginate from 'components/ui/VnPaginate.vue';
import { useStateStore } from 'stores/useStateStore';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { computed, onMounted, onUnmounted, ref } from 'vue'; import { computed, ref } from 'vue';
import { dashIfEmpty, toDateHourMin } from 'src/filters'; import { dashIfEmpty } from 'src/filters';
import { toDate } from 'filters/index';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import toCurrency from 'filters/toCurrency';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import SupplierDescriptorProxy from 'pages/Supplier/Card/SupplierDescriptorProxy.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import RoadmapFilter from 'pages/Route/Roadmap/RoadmapFilter.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import axios from 'axios';
import VnInputDate from 'components/common/VnInputDate.vue';
import { useSummaryDialog } from 'composables/useSummaryDialog'; import { useSummaryDialog } from 'composables/useSummaryDialog';
import RoadmapSummary from 'pages/Route/Roadmap/RoadmapSummary.vue'; import toCurrency from 'filters/toCurrency';
import { useRouter } from 'vue-router'; import axios from 'axios';
import RightMenu from 'src/components/common/RightMenu.vue';
const stateStore = useStateStore(); import VnSearchbar from 'components/ui/VnSearchbar.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnTable from 'components/VnTable/VnTable.vue';
import RoadmapSummary from 'pages/Route/Roadmap/RoadmapSummary.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInputTime from 'src/components/common/VnInputTime.vue';
const { viewSummary } = useSummaryDialog();
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const router = useRouter();
const { viewSummary } = useSummaryDialog();
const to = Date.vnNew();
to.setDate(to.getDate() + 1);
to.setHours(0, 0, 0, 0);
const from = Date.vnNew();
from.setDate(from.getDate());
from.setHours(0, 0, 0, 0);
onMounted(() => (stateStore.rightDrawer = true));
onUnmounted(() => (stateStore.rightDrawer = false));
const selectedRows = ref([]); const selectedRows = ref([]);
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'roadmap', align: 'left',
name: 'id',
label: 'Id',
isId: true,
},
{
align: 'left',
name: 'name',
label: t('Roadmap'), label: t('Roadmap'),
field: (row) => row.name, create: true,
sortable: true, cardVisible: true,
align: 'left', columnFilter: {
inWhere: true,
},
}, },
{ {
name: 'ETD', align: 'left',
name: 'etd',
label: t('ETD'), label: t('ETD'),
field: (row) => toDateHourMin(row.etd), component: 'date',
sortable: true, columnFilter: {
align: 'left', alias: 'c',
inWhere: true,
},
format: ({ date }) => toDate(date),
cardVisible: true,
}, },
{ {
name: 'carrier', align: 'left',
name: 'supplierFk',
label: t('Carrier'), label: t('Carrier'),
field: (row) => row.supplier?.nickname, component: 'select',
sortable: true, attrs: {
align: 'left', url: 'suppliers',
fields: ['id', 'name'],
optionLabel: 'name',
optionValue: 'id',
},
}, },
{ {
name: 'plate', name: 'tractorPlate',
label: t('Plate'), label: t('Plate'),
field: (row) => row.tractorPlate, field: (row) => row.tractorPlate,
sortable: true, sortable: true,
@ -80,30 +83,29 @@ const columns = computed(() => [
align: 'left', align: 'left',
}, },
{ {
name: 'actions',
label: '',
sortable: false,
align: 'right', align: 'right',
name: 'tableActions',
actions: [
{
title: t('Ver cmr'),
icon: 'visibility',
isPrimary: true,
action: (row) => viewSummary(row?.id, RoadmapSummary),
},
],
}, },
]); ]);
const refreshKey = ref(0); const tableRef = ref(0);
const isCloneDialogOpen = ref(false); const isCloneDialogOpen = ref(false);
const etdDate = ref(null); const etdDate = ref(null);
const filter = {
include: { relation: 'supplier', scope: { fields: ['nickname'] } },
where: {
and: [{ etd: { gte: from } }, { etd: { lte: to } }],
},
};
const cloneSelection = async () => { const cloneSelection = async () => {
await axios.post('Roadmaps/clone', { await axios.post('Roadmaps/clone', {
etd: etdDate.value, etd: etdDate.value,
ids: selectedRows.value.map((row) => row?.id), ids: selectedRows.value.map((row) => row?.id),
}); });
refreshKey.value++; tableRef.value.reload();
etdDate.value = null; etdDate.value = null;
}; };
@ -125,11 +127,7 @@ function confirmRemove() {
promise: removeSelection, promise: removeSelection,
}, },
}) })
.onOk(() => refreshKey.value++); .onOk(() => tableRef.value++);
}
function navigateToRoadmapSummary(_, { id }) {
router.push({ name: 'RoadmapSummary', params: { id } });
} }
</script> </script>
@ -139,11 +137,6 @@ function navigateToRoadmapSummary(_, { id }) {
:label="t('Search roadmaps')" :label="t('Search roadmaps')"
:info="t('You can search by roadmap reference')" :info="t('You can search by roadmap reference')"
/> />
<RightMenu>
<template #right-panel>
<RoadmapFilter data-key="RoadmapList" />
</template>
</RightMenu>
<QDialog v-model="isCloneDialogOpen"> <QDialog v-model="isCloneDialogOpen">
<QCard style="min-width: 350px"> <QCard style="min-width: 350px">
<QCardSection> <QCardSection>
@ -163,7 +156,6 @@ function navigateToRoadmapSummary(_, { id }) {
</QCardActions> </QCardActions>
</QCard> </QCard>
</QDialog> </QDialog>
<QPage class="column items-center">
<VnSubToolbar class="justify-end"> <VnSubToolbar class="justify-end">
<template #st-actions> <template #st-actions>
<QBtn <QBtn
@ -186,72 +178,33 @@ function navigateToRoadmapSummary(_, { id }) {
</QBtn> </QBtn>
</template> </template>
</VnSubToolbar> </VnSubToolbar>
<div class="route-list"> <VnTable
<VnPaginate ref="tableRef"
:key="refreshKey"
data-key="RoadmapList" data-key="RoadmapList"
url="Roadmaps" url="roadmaps"
:limit="20"
:filter="filter"
>
<template #body="{ rows }">
<div class="q-pa-md">
<QTable
v-model:selected="selectedRows"
:columns="columns" :columns="columns"
:rows="rows" :right-search="true"
flat :use-model="true"
row-key="id" default-mode="table"
selection="multiple" v-model:selected="selectedRows"
:rows-per-page-options="[0]" table-height="85vh"
hide-pagination :table="{
:pagination="{ sortBy: 'ID', descending: true }" selection: 'multiple',
@row-click="navigateToRoadmapSummary" }"
redirect="route/roadmap"
:create="{
urlCreate: 'Roadmaps',
title: t('Create routemap'),
onDataSaved: ({ id }) => tableRef.redirect(id),
formInitialData: {},
}"
:disable-option="{ card: true }"
> >
<template #body-cell-carrier="props"> <template #more-create-dialog="{ data }">
<QTd :props="props"> <VnInputDate v-model="data.etd" />
<span v-if="props.value" class="link" @click.stop> <VnInputTime v-model="data.etd" />
{{ props.value }}
<SupplierDescriptorProxy
:id="props.row?.supplier?.id"
/>
</span>
</QTd>
</template> </template>
<template #body-cell-actions="props"> </VnTable>
<QTd :props="props">
<div class="flex items-center table-actions">
<QIcon
name="preview"
size="sm"
color="primary"
@click.stop="
viewSummary(
props?.row?.id,
RoadmapSummary
)
"
class="cursor-pointer"
>
<QTooltip>{{ t('Preview') }}</QTooltip>
</QIcon>
</div>
</QTd>
</template>
</QTable>
</div>
</template>
</VnPaginate>
</div>
<QPageSticky :offset="[20, 20]">
<RouterLink :to="{ name: 'RouteRoadmapCreate' }">
<QBtn fab icon="add" color="primary" />
<QTooltip>
{{ t('Create roadmap') }}
</QTooltip>
</RouterLink>
</QPageSticky>
</QPage>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -264,6 +217,7 @@ function navigateToRoadmapSummary(_, { id }) {
</style> </style>
<i18n> <i18n>
es: es:
Create routemap: Crear troncal
Search roadmaps: Buscar troncales Search roadmaps: Buscar troncales
You can search by roadmap reference: Puedes buscar por referencia del troncal You can search by roadmap reference: Puedes buscar por referencia del troncal
Delete roadmap(s): Eliminar troncal(es) Delete roadmap(s): Eliminar troncal(es)

View File

@ -57,15 +57,6 @@ export default {
}, },
component: () => import('src/pages/Route/RouteRoadmap.vue'), component: () => import('src/pages/Route/RouteRoadmap.vue'),
}, },
{
path: 'roadmap/create',
name: 'RouteRoadmapCreate',
meta: {
title: 'RouteRoadmapCreate',
icon: 'vn:troncales',
},
component: () => import('src/pages/Route/Roadmap/RoadmapCreate.vue'),
},
{ {
path: 'cmr', path: 'cmr',
name: 'CmrList', name: 'CmrList',

View File

@ -0,0 +1,14 @@
describe('Route', () => {
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('developer');
cy.visit(`/#/route/roadmap`);
});
it('Route list create route', () => {
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
cy.get('input[name="name"]').eq(1).type('routeTestOne{enter}');
cy.get('.q-notification__message').should('have.text', 'Data created');
cy.url().should('include', '/summary');
});
});

View File

@ -0,0 +1,15 @@
describe('Route', () => {
beforeEach(() => {
cy.viewport(1920, 1080);
cy.login('developer');
cy.visit(`/#/route/list`);
});
it('Route list create route', () => {
cy.visit(`/#/route/list`);
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
cy.get('input[name="description"]').eq(1).type('routeTestOne{enter}');
cy.get('.q-notification__message').should('have.text', 'Data created');
cy.url().should('include', '/summary');
});
});