optymalizacja zapytań; filtr scenerii pocz.

This commit is contained in:
2023-06-03 15:49:15 +02:00
parent 44ebf53798
commit c3481470cb
6 changed files with 332 additions and 318 deletions
@@ -132,8 +132,8 @@ export default defineComponent({
optionsType: { optionsType: {
type: String, type: String,
required: true required: true,
} },
}, },
data() { data() {
@@ -249,10 +249,6 @@ export default defineComponent({
}); });
}, },
focusEnd() {
console.log('focus end');
},
onSorterChange(item: { id: string | number; value: string }) { onSorterChange(item: { id: string | number; value: string }) {
this.sorterActive.id = item.id; this.sorterActive.id = item.id;
this.sorterActive.dir = -1; this.sorterActive.dir = -1;
+3 -2
View File
@@ -99,19 +99,20 @@
"search-dispatcher": "Dispatcher name", "search-dispatcher": "Dispatcher name",
"search-station": "Scenery name", "search-station": "Scenery name",
"search-author": "Timetable author name", "search-author": "Timetable author name",
"search-issuedFrom": "Origin scenery name",
"search-timetables-date": "Timetable date (CEST / GMT+2)", "search-timetables-date": "Timetable date (CEST / GMT+2)",
"search-dispatchers-date": "Service date (CEST / GMT+2)", "search-dispatchers-date": "Service date (CEST / GMT+2)",
"sort-mass": "mass", "sort-mass": "mass",
"sort-speed": "speed", "sort-speed": "speed",
"sort-length": "length", "sort-length": "length",
"sort-distance": "distance", "sort-routeDistance": "route distance",
"sort-timetable": "train no.", "sort-timetable": "train no.",
"sort-progress": "route progress", "sort-progress": "route progress",
"sort-delay": "current delay", "sort-delay": "current delay",
"sort-id": "timetable id", "sort-id": "timetable id",
"sort-total-stops": "total stops", "sort-allStopsCount": "total stops",
"sort-beginDate": "date", "sort-beginDate": "date",
"sort-timetableId": "timetable ID", "sort-timetableId": "timetable ID",
"sort-timestampFrom": "date", "sort-timestampFrom": "date",
+3 -2
View File
@@ -99,11 +99,12 @@
"search-dispatcher": "Nick dyżurnego", "search-dispatcher": "Nick dyżurnego",
"search-station": "Nazwa scenerii", "search-station": "Nazwa scenerii",
"search-author": "Nick autora rozkładu jazdy", "search-author": "Nick autora rozkładu jazdy",
"search-issuedFrom": "Sceneria początkowa",
"search-timetables-date": "Data rozkładu jazdy (czas polski)", "search-timetables-date": "Data rozkładu jazdy (czas polski)",
"search-dispatchers-date": "Data służby (czas polski)", "search-dispatchers-date": "Data służby (czas polski)",
"sort-distance": "kilometraż", "sort-routeDistance": "kilometraż",
"sort-total-stops": "stacje", "sort-allStopsCount": "stacje",
"sort-beginDate": "data", "sort-beginDate": "data",
"sort-timetableId": "ID rozkładu", "sort-timetableId": "ID rozkładu",
"sort-timestampFrom": "data", "sort-timestampFrom": "data",
@@ -0,0 +1,18 @@
import { JournalTimetableSorter } from '../../../types/Journal/JournalTimetablesTypes';
export interface TimetablesQueryParams {
driverName?: string;
trainNo?: string;
authorName?: string;
timestampFrom?: number;
timestampTo?: number;
issuedFrom?: string;
countFrom?: number;
countLimit?: number;
fulfilled?: number;
terminated?: number;
sortBy?: JournalTimetableSorter['id'];
}
+3 -3
View File
@@ -1,6 +1,6 @@
import { JournalFilterType } from '../../scripts/enums/JournalFilterType'; import { JournalFilterType } from '../../scripts/enums/JournalFilterType';
export type JournalTimetableSearchKey = 'search-driver' | 'search-train' | 'search-date' | 'search-dispatcher'; export type JournalTimetableSearchKey = 'search-driver' | 'search-train' | 'search-date' | 'search-dispatcher' | 'search-issuedFrom';
export type JournalTimetableSearchType = { export type JournalTimetableSearchType = {
[key in JournalTimetableSearchKey]: string; [key in JournalTimetableSearchKey]: string;
@@ -13,6 +13,6 @@ export interface JournalTimetableFilter {
} }
export interface JournalTimetableSorter { export interface JournalTimetableSorter {
id: 'timetableId' | 'beginDate' | 'distance' | 'total-stops'; id: 'timetableId' | 'beginDate' | 'routeDistance' | 'allStopsCount';
dir: -1 | 1; dir: 'asc' | 'desc';
} }
+303 -305
View File
@@ -1,305 +1,303 @@
<template> <template>
<section class="journal-timetables"> <section class="journal-timetables">
<JournalHeader /> <JournalHeader />
<div class="journal_wrapper"> <div class="journal_wrapper">
<JournalOptions <JournalOptions
@on-search-confirm="fetchHistoryData" @on-search-confirm="fetchHistoryData"
@on-options-reset="resetOptions" @on-options-reset="resetOptions"
@on-refresh-data="fetchHistoryData" @on-refresh-data="fetchHistoryData"
:sorter-option-ids="['timetableId', 'beginDate', 'distance', 'total-stops']" :sorter-option-ids="['timetableId', 'beginDate', 'routeDistance', 'allStopsCount']"
:filters="journalTimetableFilters" :filters="journalTimetableFilters"
:currentOptionsActive="currentOptionsActive" :currentOptionsActive="currentOptionsActive"
:data-status="dataStatus" :data-status="dataStatus"
optionsType="timetables" optionsType="timetables"
/> />
<JournalStats /> <JournalStats />
<div class="list_wrapper" @scroll="handleScroll"> <div class="list_wrapper" @scroll="handleScroll">
<transition name="status-anim" mode="out-in"> <transition name="status-anim" mode="out-in">
<div :key="dataStatus"> <div :key="dataStatus">
<div class="journal_warning" v-if="store.isOffline"> <div class="journal_warning" v-if="store.isOffline">
{{ $t('app.offline') }} {{ $t('app.offline') }}
</div> </div>
<Loading v-else-if="dataStatus == DataStatus.Loading" /> <Loading v-else-if="dataStatus == DataStatus.Loading" />
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error"> <div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.error') }} {{ $t('app.error') }}
</div> </div>
<div v-else-if="timetableHistory.length == 0" class="journal_warning"> <div v-else-if="timetableHistory.length == 0" class="journal_warning">
{{ $t('app.no-result') }} {{ $t('app.no-result') }}
</div> </div>
<div v-else> <div v-else>
<JournalTimetablesList :timetableHistory="timetableHistory" /> <JournalTimetablesList :timetableHistory="timetableHistory" />
<button <button
class="btn btn--option btn--load-data" class="btn btn--option btn--load-data"
v-if="!scrollNoMoreData && scrollDataLoaded && timetableHistory.length >= 15" v-if="!scrollNoMoreData && scrollDataLoaded && timetableHistory.length >= 15"
@click="addHistoryData" @click="addHistoryData"
> >
{{ $t('journal.load-data') }} {{ $t('journal.load-data') }}
</button> </button>
</div> </div>
</div> </div>
</transition> </transition>
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div> <div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
<div class="journal_warning" v-else-if="!scrollDataLoaded">{{ $t('journal.loading-further-data') }}</div> <div class="journal_warning" v-else-if="!scrollDataLoaded">{{ $t('journal.loading-further-data') }}</div>
</div> </div>
</div> </div>
</section> </section>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, provide, reactive, Ref, ref } from 'vue'; import { defineComponent, provide, reactive, Ref, ref } from 'vue';
import axios from 'axios'; import axios from 'axios';
import DriverStats from '../components/JournalView/JournalDriverStats.vue'; import DriverStats from '../components/JournalView/JournalDriverStats.vue';
import Loading from '../components/Global/Loading.vue'; import Loading from '../components/Global/Loading.vue';
import { JournalTimetableSorter } from '../types/Journal/JournalTimetablesTypes'; import { JournalTimetableSorter } from '../types/Journal/JournalTimetablesTypes';
import dateMixin from '../mixins/dateMixin'; import dateMixin from '../mixins/dateMixin';
import routerMixin from '../mixins/routerMixin'; import routerMixin from '../mixins/routerMixin';
import { DataStatus } from '../scripts/enums/DataStatus'; import { DataStatus } from '../scripts/enums/DataStatus';
import { JournalFilterType } from '../scripts/enums/JournalFilterType'; import { TimetableHistory } from '../scripts/interfaces/api/TimetablesAPIData';
import { TimetableHistory } from '../scripts/interfaces/api/TimetablesAPIData'; import { URLs } from '../scripts/utils/apiURLs';
import { URLs } from '../scripts/utils/apiURLs'; import { useStore } from '../store/store';
import { useStore } from '../store/store'; import JournalOptions from '../components/JournalView/JournalOptions.vue';
import JournalOptions from '../components/JournalView/JournalOptions.vue'; import { JournalTimetableSearchType } from '../types/Journal/JournalTimetablesTypes';
import { JournalTimetableSearchType } from '../types/Journal/JournalTimetablesTypes'; import modalTrainMixin from '../mixins/modalTrainMixin';
import modalTrainMixin from '../mixins/modalTrainMixin'; import imageMixin from '../mixins/imageMixin';
import imageMixin from '../mixins/imageMixin'; import JournalTimetablesList from '../components/JournalView/JournalTimetablesList.vue';
import JournalTimetablesList from '../components/JournalView/JournalTimetablesList.vue'; import { journalTimetableFilters } from '../constants/Journal/JournalTimetablesConsts';
import { journalTimetableFilters } from '../constants/Journal/JournalTimetablesConsts'; import JournalStats from '../components/JournalView/JournalStats.vue';
import JournalStats from '../components/JournalView/JournalStats.vue'; import JournalHeader from '../components/JournalView/JournalHeader.vue';
import JournalHeader from '../components/JournalView/JournalHeader.vue'; import { LocationQuery } from 'vue-router';
import { LocationQuery } from 'vue-router'; import { TimetablesQueryParams } from '../scripts/interfaces/api/TimetablesQueryParams';
import { JournalFilterType } from '../scripts/enums/JournalFilterType';
const TIMETABLES_API_URL = `${URLs.stacjownikAPI}/api/getTimetables`;
const TIMETABLES_API_URL = `${URLs.stacjownikAPI}/api/getTimetables`;
export default defineComponent({
components: { DriverStats, Loading, JournalOptions, JournalTimetablesList, JournalStats, JournalHeader }, export default defineComponent({
mixins: [dateMixin, routerMixin, modalTrainMixin, imageMixin], components: { DriverStats, Loading, JournalOptions, JournalTimetablesList, JournalStats, JournalHeader },
mixins: [dateMixin, routerMixin, modalTrainMixin, imageMixin],
name: 'JournalTimetables',
name: 'JournalTimetables',
props: {
timetableId: { props: {
type: String, timetableId: {
}, type: String,
}, },
},
data: () => ({
currentQuery: '', data: () => ({
currentQueryArray: [] as string[], currentQueryParams: {} as TimetablesQueryParams,
scrollDataLoaded: true, scrollDataLoaded: true,
scrollNoMoreData: false, scrollNoMoreData: false,
showReturnButton: false, showReturnButton: false,
statsCardOpen: false, statsCardOpen: false,
currentOptionsActive: false, currentOptionsActive: false,
timetableHistory: [] as TimetableHistory[], timetableHistory: [] as TimetableHistory[],
journalTimetableFilters, journalTimetableFilters,
dataStatus: DataStatus.Loading, dataStatus: DataStatus.Loading,
dataErrorMessage: '', dataErrorMessage: '',
DataStatus, DataStatus,
}), }),
setup() { setup() {
const sorterActive: JournalTimetableSorter = reactive({ id: 'timetableId', dir: 1 }); const sorterActive: JournalTimetableSorter = reactive({ id: 'timetableId', dir: 'desc' });
const journalFilterActive = ref(journalTimetableFilters[0]); const journalFilterActive = ref(journalTimetableFilters[0]);
const searchersValues = reactive({ const searchersValues = reactive({
'search-train': '', 'search-train': '',
'search-driver': '', 'search-driver': '',
'search-dispatcher': '', 'search-dispatcher': '',
'search-date': '', 'search-issuedFrom': '',
} as JournalTimetableSearchType); 'search-date': '',
} as JournalTimetableSearchType);
const countFromIndex = ref(0);
const countLimit = 15; const countFromIndex = ref(0);
const countLimit = 15;
provide('searchersValues', searchersValues);
provide('sorterActive', sorterActive); provide('searchersValues', searchersValues);
provide('journalFilterActive', journalFilterActive); provide('sorterActive', sorterActive);
provide('journalFilterActive', journalFilterActive);
const scrollElement: Ref<HTMLElement | null> = ref(null);
const scrollElement: Ref<HTMLElement | null> = ref(null);
return {
sorterActive, return {
journalFilterActive, sorterActive,
searchersValues, journalFilterActive,
searchersValues,
countFromIndex,
countLimit, countFromIndex,
countLimit,
scrollElement,
scrollElement,
store: useStore(),
}; store: useStore(),
}, };
},
watch: {
currentQueryArray(q: string[]) { watch: {
this.currentOptionsActive = q.length >= 2 || q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1]); currentQueryArray(q: string[]) {
}, this.currentOptionsActive = q.length >= 2 || q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1]);
}, },
},
// Handle route updates for route-links
beforeRouteUpdate(to, _) { // Handle route updates for route-links
this.handleQueries(to.query); beforeRouteUpdate(to, _) {
this.fetchHistoryData(); this.handleQueries(to.query);
}, this.fetchHistoryData();
activated() { console.log('test');
this.handleQueries(this.$route.query); },
this.fetchHistoryData();
}, activated() {
this.handleQueries(this.$route.query);
this.fetchHistoryData();
methods: { },
handleScroll(e: Event) {
const listElement = e.target as HTMLElement; methods: {
const scrollTop = listElement.scrollTop; handleScroll(e: Event) {
const elementHeight = listElement.scrollHeight - listElement.offsetHeight; const listElement = e.target as HTMLElement;
const scrollTop = listElement.scrollTop;
if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return; const elementHeight = listElement.scrollHeight - listElement.offsetHeight;
if (scrollTop > elementHeight * 0.85) this.addHistoryData(); if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return;
},
if (scrollTop > elementHeight * 0.85) this.addHistoryData();
handleQueries(query: LocationQuery) { },
if ('timetableId' in query) this.searchersValues['search-train'] = `#${query.timetableId}`;
}, handleQueries(query: LocationQuery) {
if ('timetableId' in query) this.searchersValues['search-train'] = `#${query.timetableId}`;
setSearchers(date: string, driver: string, train: string, dispatcher: string) { },
this.searchersValues['search-date'] = date;
this.searchersValues['search-driver'] = driver; setSearchers(date: string, driver: string, train: string, dispatcher: string) {
this.searchersValues['search-train'] = train; this.searchersValues['search-date'] = date;
this.searchersValues['search-dispatcher'] = dispatcher; this.searchersValues['search-driver'] = driver;
}, this.searchersValues['search-train'] = train;
this.searchersValues['search-dispatcher'] = dispatcher;
resetOptions() { },
this.setSearchers('', '', '', '');
resetOptions() {
this.journalFilterActive = this.journalTimetableFilters[0]; this.setSearchers('', '', '', '');
this.sorterActive.id = 'timetableId';
this.journalFilterActive = this.journalTimetableFilters[0];
this.fetchHistoryData(); this.sorterActive.id = 'timetableId';
},
this.fetchHistoryData();
async addHistoryData() { },
this.scrollDataLoaded = false;
async addHistoryData() {
const countFrom = this.timetableHistory.length; this.scrollDataLoaded = false;
const responseData: TimetableHistory[] = await ( this.currentQueryParams['countFrom'] = this.timetableHistory.length;
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}&countFrom=${countFrom}`)
).data; const responseData: TimetableHistory[] = await (
await axios.get(`${TIMETABLES_API_URL}`, {
if (!responseData) return; params: { ...this.currentQueryParams },
})
if (responseData.length == 0) { ).data;
this.scrollNoMoreData = true;
return; if (!responseData) return;
}
if (responseData.length == 0) {
this.timetableHistory.push(...responseData); this.scrollNoMoreData = true;
this.scrollDataLoaded = true; return;
}, }
async fetchHistoryData() { this.timetableHistory.push(...responseData);
// if(this.dataStatus == DataStatus.Loading) return; this.scrollDataLoaded = true;
},
const queries: string[] = [];
async fetchHistoryData() {
const driverName = this.searchersValues['search-driver'].trim(); const driverName = this.searchersValues['search-driver'].trim() || undefined;
const trainNo = this.searchersValues['search-train'].trim(); const trainNo = this.searchersValues['search-train'].trim() || undefined;
const authorName = this.searchersValues['search-dispatcher'].trim(); const authorName = this.searchersValues['search-dispatcher'].trim() || undefined;
const dateString = this.searchersValues['search-date'].trim(); const dateString = this.searchersValues['search-date'].trim() || undefined;
const issuedFrom = this.searchersValues['search-issuedFrom'].trim() || undefined;
const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined; const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
if (driverName) queries.push(`driverName=${driverName}`);
if (trainNo) switch (this.journalFilterActive.id) {
queries.push(trainNo.startsWith('#') ? `timetableId=${trainNo.replace('#', '')}` : `trainNo=${trainNo}`); case JournalFilterType.abandoned:
if (authorName) queries.push(`authorName=${authorName}`); this.currentQueryParams['fulfilled'] = 0;
if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`); this.currentQueryParams['terminated'] = 1;
break;
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
if (this.sorterActive.id == 'distance') queries.push('sortBy=routeDistance'); case JournalFilterType.active:
else if (this.sorterActive.id == 'total-stops') queries.push('sortBy=allStopsCount'); this.currentQueryParams['terminated'] = 0;
else if (this.sorterActive.id == 'beginDate') queries.push('sortBy=beginDate'); break;
// else queries.push('sortBy=timetableId');
case JournalFilterType.fulfilled:
queries.push('countLimit=15'); this.currentQueryParams['fulfilled'] = 1;
break;
switch (this.journalFilterActive.id) {
case JournalFilterType.abandoned: default:
queries.push('fulfilled=0', 'terminated=1'); break;
break; }
case JournalFilterType.active: this.currentQueryParams['driverName'] = driverName;
queries.push('terminated=0'); this.currentQueryParams['trainNo'] = trainNo;
break;
this.currentQueryParams['countFrom'] = undefined;
case JournalFilterType.fulfilled: this.currentQueryParams['countLimit'] = undefined;
queries.push('fulfilled=1');
break; this.currentQueryParams['authorName'] = authorName;
this.currentQueryParams['timestampFrom'] = timestampFrom;
default: this.currentQueryParams['timestampTo'] = timestampTo;
break; this.currentQueryParams['issuedFrom'] = issuedFrom;
} this.currentQueryParams['sortBy'] = this.sorterActive.id != 'timetableId' ? this.sorterActive.id : undefined;
if (this.currentQuery != queries.join('&')) this.dataStatus = DataStatus.Loading; this.dataStatus = DataStatus.Loading;
this.currentQuery = queries.join('&'); try {
this.currentQueryArray = queries; const responseData: TimetableHistory[] = await (
await axios.get(`${TIMETABLES_API_URL}`, {
try { params: this.currentQueryParams,
const responseData: TimetableHistory[] = await ( })
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}`) ).data;
).data;
if (!responseData) {
if (!responseData) { this.dataStatus = DataStatus.Error;
this.dataStatus = DataStatus.Error; this.dataErrorMessage = 'Brak danych!';
this.dataErrorMessage = 'Brak danych!'; return;
return; }
}
if (!responseData) return;
if (!responseData) return;
// Response data exists
// Response data exists this.timetableHistory = responseData;
this.timetableHistory = responseData;
// Stats display
// Stats display this.store.driverStatsName =
this.store.driverStatsName = this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim()
this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim() ? this.timetableHistory[0].driverName
? this.timetableHistory[0].driverName : '';
: '';
this.dataStatus = DataStatus.Loaded;
this.dataStatus = DataStatus.Loaded; } catch (error) {
} catch (error) { this.dataStatus = DataStatus.Error;
this.dataStatus = DataStatus.Error; this.dataErrorMessage = 'Ups! Coś poszło nie tak!';
this.dataErrorMessage = 'Ups! Coś poszło nie tak!'; }
}
this.scrollNoMoreData = false;
this.scrollNoMoreData = false; this.scrollDataLoaded = true;
this.scrollDataLoaded = true; },
}, },
}, });
}); </script>
</script>
<style lang="scss" scoped>
<style lang="scss" scoped> @import '../styles/JournalSection.scss';
@import '../styles/JournalSection.scss'; </style>
</style>