494 lines
19 KiB
Vue
494 lines
19 KiB
Vue
<script setup>
|
|
import VnPaginate from 'components/ui/VnPaginate.vue';
|
|
import { useStateStore } from 'stores/useStateStore';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
|
import { dashIfEmpty, toHour } from 'src/filters';
|
|
import VnSelectFilter from 'components/common/VnSelectFilter.vue';
|
|
import FetchData from 'components/FetchData.vue';
|
|
import { useValidator } from 'composables/useValidator';
|
|
import VnInputDate from 'components/common/VnInputDate.vue';
|
|
import VnInput from 'components/common/VnInput.vue';
|
|
import VnInputTime from 'components/common/VnInputTime.vue';
|
|
import axios from 'axios';
|
|
import RouteSearchbar from 'pages/Route/Card/RouteSearchbar.vue';
|
|
import RouteFilter from 'pages/Route/Card/RouteFilter.vue';
|
|
import RouteSummary from 'pages/Route/Card/RouteSummary.vue';
|
|
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
|
import { useSession } from 'composables/useSession';
|
|
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
|
import RouteListTicketsDialog from 'pages/Route/Card/RouteListTicketsDialog.vue';
|
|
import { useQuasar } from 'quasar';
|
|
|
|
const stateStore = useStateStore();
|
|
const { t } = useI18n();
|
|
const { validate } = useValidator();
|
|
const quasar = useQuasar();
|
|
const session = useSession();
|
|
const { viewSummary } = useSummaryDialog();
|
|
|
|
onMounted(() => (stateStore.rightDrawer = true));
|
|
onUnmounted(() => (stateStore.rightDrawer = false));
|
|
|
|
const selectedRows = ref([]);
|
|
const columns = computed(() => [
|
|
{
|
|
name: 'ID',
|
|
label: t('ID'),
|
|
field: (row) => row.id,
|
|
sortable: true,
|
|
align: 'center',
|
|
},
|
|
{
|
|
name: 'worker',
|
|
label: t('Worker'),
|
|
field: (row) => row.workerUserName,
|
|
sortable: true,
|
|
align: 'left',
|
|
},
|
|
{
|
|
name: 'agency',
|
|
label: t('Agency'),
|
|
field: (row) => row.agencyName,
|
|
sortable: true,
|
|
align: 'left',
|
|
},
|
|
{
|
|
name: 'vehicle',
|
|
label: t('Vehicle'),
|
|
field: (row) => row.vehiclePlateNumber,
|
|
sortable: true,
|
|
align: 'left',
|
|
},
|
|
{
|
|
name: 'date',
|
|
label: t('Date'),
|
|
field: (row) => row.created,
|
|
sortable: true,
|
|
align: 'left',
|
|
},
|
|
{
|
|
name: 'volume',
|
|
label: 'm³',
|
|
field: (row) => dashIfEmpty(row.m3),
|
|
sortable: true,
|
|
align: 'center',
|
|
},
|
|
{
|
|
name: 'description',
|
|
label: t('Description'),
|
|
field: (row) => row.description,
|
|
sortable: true,
|
|
align: 'left',
|
|
},
|
|
{
|
|
name: 'started',
|
|
label: t('Hour started'),
|
|
field: (row) => toHour(row.started),
|
|
sortable: true,
|
|
align: 'left',
|
|
},
|
|
{
|
|
name: 'finished',
|
|
label: t('Hour finished'),
|
|
field: (row) => toHour(row.finished),
|
|
sortable: true,
|
|
align: 'left',
|
|
},
|
|
{
|
|
name: 'actions',
|
|
label: '',
|
|
sortable: false,
|
|
align: 'right',
|
|
},
|
|
]);
|
|
|
|
const refreshKey = ref(0);
|
|
const workers = ref([]);
|
|
const agencyList = ref([]);
|
|
const vehicleList = ref([]);
|
|
const updateRoute = async (route) => {
|
|
try {
|
|
return await axios.patch(`Routes/${route.id}`, route);
|
|
} catch (err) {
|
|
return err;
|
|
}
|
|
};
|
|
|
|
const confirmationDialog = ref(false);
|
|
const startingDate = ref(null);
|
|
|
|
const cloneRoutes = () => {
|
|
axios.post('Routes/clone', {
|
|
created: startingDate.value,
|
|
ids: selectedRows.value.map((row) => row?.id),
|
|
});
|
|
refreshKey.value++;
|
|
startingDate.value = null;
|
|
};
|
|
|
|
const showRouteReport = () => {
|
|
const ids = selectedRows.value.map((row) => row?.id);
|
|
const idString = ids.join(',');
|
|
let url;
|
|
|
|
if (selectedRows.value.length <= 1) {
|
|
url = `api/Routes/${idString}/driver-route-pdf?access_token=${session.getTokenMultimedia()}`;
|
|
} else {
|
|
const params = new URLSearchParams({
|
|
access_token: session.getTokenMultimedia(),
|
|
id: idString,
|
|
});
|
|
url = `api/Routes/downloadZip?${params.toString()}`;
|
|
}
|
|
window.open(url, '_blank');
|
|
};
|
|
|
|
const markAsServed = () => {
|
|
selectedRows.value.forEach((row) => {
|
|
if (row?.id) {
|
|
axios.patch(`Routes/${row?.id}`, { isOk: true });
|
|
}
|
|
});
|
|
refreshKey.value++;
|
|
startingDate.value = null;
|
|
};
|
|
|
|
const openTicketsDialog = (id) => {
|
|
if (!id) {
|
|
return;
|
|
}
|
|
quasar
|
|
.dialog({
|
|
component: RouteListTicketsDialog,
|
|
componentProps: {
|
|
id,
|
|
},
|
|
})
|
|
.onOk(() => refreshKey.value++);
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<template v-if="stateStore.isHeaderMounted()">
|
|
<Teleport to="#searchbar">
|
|
<RouteSearchbar />
|
|
</Teleport>
|
|
<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>
|
|
<QDialog v-model="confirmationDialog">
|
|
<QCard style="min-width: 350px">
|
|
<QCardSection>
|
|
<p class="text-h6 q-ma-none">{{ t('Select the starting date') }}</p>
|
|
</QCardSection>
|
|
|
|
<QCardSection class="q-pt-none">
|
|
<VnInputDate
|
|
:label="t('Stating date')"
|
|
v-model="startingDate"
|
|
autofocus
|
|
/>
|
|
</QCardSection>
|
|
<QCardActions align="right">
|
|
<QBtn flat :label="t('Cancel')" v-close-popup class="text-primary" />
|
|
<QBtn color="primary" v-close-popup @click="cloneRoutes">
|
|
{{ t('Clone') }}
|
|
</QBtn>
|
|
</QCardActions>
|
|
</QCard>
|
|
</QDialog>
|
|
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
|
|
<QScrollArea class="fit text-grey-8">
|
|
<RouteFilter data-key="RouteList" />
|
|
</QScrollArea>
|
|
</QDrawer>
|
|
<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 />
|
|
<QPage class="column items-center">
|
|
<VnSubToolbar class="bg-vn-dark justify-end">
|
|
<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 DESC', 'id DESC']"
|
|
:limit="20"
|
|
auto-load
|
|
>
|
|
<template #body="{ rows }">
|
|
<div class="q-pa-md">
|
|
<QTable
|
|
v-model:selected="selectedRows"
|
|
:columns="columns"
|
|
:rows="rows"
|
|
flat
|
|
row-key="id"
|
|
selection="multiple"
|
|
:rows-per-page-options="[0]"
|
|
hide-pagination
|
|
:pagination="{ sortBy: 'ID', descending: true }"
|
|
:no-data-label="t('globals.noResults')"
|
|
>
|
|
<template #body-cell-worker="{ row }">
|
|
<QTd>
|
|
<VnSelectFilter
|
|
: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>
|
|
</VnSelectFilter>
|
|
</QTd>
|
|
</template>
|
|
<template #body-cell-agency="{ row }">
|
|
<QTd>
|
|
<VnSelectFilter
|
|
: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>
|
|
<VnSelectFilter
|
|
: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">
|
|
<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">
|
|
<VnInputTime
|
|
v-model="row.started"
|
|
:label="t('Hour started')"
|
|
: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">
|
|
<VnInputTime
|
|
v-model="row.finished"
|
|
autofocus
|
|
:label="t('Hour finished')"
|
|
:rules="validate('route.finished')"
|
|
:is-clearable="false"
|
|
hide-bottom-space
|
|
dense
|
|
@update:model-value="updateRoute(row)"
|
|
/>
|
|
</QTd>
|
|
</template>
|
|
<template #body-cell-actions="props">
|
|
<QTd :props="props">
|
|
<div class="flex items-center no-wrap table-actions">
|
|
<QIcon
|
|
name="vn:ticketAdd"
|
|
size="xs"
|
|
color="primary"
|
|
class="cursor-pointer"
|
|
@click="openTicketsDialog(props?.row?.id)"
|
|
>
|
|
<QTooltip>{{ t('Add ticket') }}</QTooltip>
|
|
</QIcon>
|
|
<QIcon
|
|
name="preview"
|
|
size="xs"
|
|
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>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.table-input-cell {
|
|
min-width: 150px;
|
|
}
|
|
|
|
.route-list {
|
|
width: 100%;
|
|
max-height: 100%;
|
|
}
|
|
|
|
.table-actions {
|
|
gap: 12px;
|
|
}
|
|
</style>
|
|
<i18n>
|
|
en:
|
|
newRoute: New Route
|
|
es:
|
|
ID: ID
|
|
Worker: Trabajador
|
|
Agency: Agencia
|
|
Vehicle: Vehículo
|
|
Date: Fecha
|
|
Description: Descripción
|
|
Hour started: Hora inicio
|
|
Hour finished: Hora fin
|
|
newRoute: Nueva Ruta
|
|
Clone Selected Routes: Clonar rutas seleccionadas
|
|
Select the starting date: Seleccione la fecha de inicio
|
|
Stating date: Fecha de inicio
|
|
Cancel: Cancelar
|
|
Clone: Clonar
|
|
Mark as served: Marcar como servidas
|
|
Download selected routes as PDF: Descargar rutas seleccionadas como PDF
|
|
Add ticket: Añadir tickets
|
|
Preview: Vista previa
|
|
Summary: Resumen
|
|
</i18n>
|