Fix: bug routingu dzienników

This commit is contained in:
2022-12-18 03:01:13 +01:00
parent 726b859f5c
commit 6e07897ac0
9 changed files with 669 additions and 685 deletions
+267
View File
@@ -0,0 +1,267 @@
<template>
<section class="journal-timetables">
<JournalHeader />
<div class="journal_wrapper">
<JournalOptions
@on-search-confirm="searchHistory"
@on-options-reset="resetOptions"
:sorter-option-ids="['timestampFrom', 'duration']"
:data-status="dataStatus"
/>
<div class="list_wrapper" @scroll="handleScroll">
<!-- <transition name="warning" mode="out-in"> -->
<!-- <div :key="dataStatus"> -->
<Loading v-if="dataStatus == DataStatus.Initialized || dataStatus == DataStatus.Loading" />
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.error') }}
</div>
<div class="journal_warning" v-else-if="historyList.length == 0">
{{ $t('app.no-result') }}
</div>
<div v-else>
<JournalDispatchersList :dispatcherHistory="computedHistoryList" />
<button
class="btn btn--option btn--load-data"
v-if="!scrollNoMoreData && scrollDataLoaded && computedHistoryList.length > 15"
@click="addHistoryData"
>
{{ $t('journal.load-data') }}
</button>
</div>
<!-- </div>
</transition> -->
<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>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
import axios from 'axios';
import ActionButton from '../components/Global/ActionButton.vue';
import JournalOptions from '../components/JournalView/JournalOptions.vue';
import DispatcherStats from '../components/JournalView/DispatcherStats.vue';
import SearchBox from '../components/Global/SearchBox.vue';
import Loading from '../components/Global/Loading.vue';
import { URLs } from '../scripts/utils/apiURLs';
import { DataStatus } from '../scripts/enums/DataStatus';
import { useStore } from '../store/store';
import JournalDispatchersList from '../components/JournalView/JournalDispatchersList.vue';
import { JournalDispatcherSearcher, JournalDispatcherSorter } from '../types/Journal/JournalDispatcherTypes';
import { DispatcherHistory } from '../scripts/interfaces/api/DispatchersAPIData';
import { JournalTimetableFilter } from '../types/Journal/JournalTimetablesTypes';
import JournalHeader from '../components/JournalView/JournalHeader.vue';
const DISPATCHERS_API_URL = `${URLs.stacjownikAPI}/api/getDispatchers`;
export default defineComponent({
components: { SearchBox, ActionButton, JournalOptions, DispatcherStats, Loading, JournalDispatchersList, JournalHeader },
name: 'JournalDispatchers',
props: {
sceneryName: {
type: String,
required: false,
},
dispatcherName: {
type: String,
required: false,
},
},
data: () => ({
currentQuery: '',
scrollDataLoaded: true,
scrollNoMoreData: false,
showReturnButton: false,
statsCardOpen: false,
dataStatus: DataStatus.Initialized,
DataStatus,
historyList: [] as DispatcherHistory[],
}),
setup() {
const sorterActive: JournalDispatcherSorter = reactive({ id: 'timestampFrom', dir: -1 });
const journalFilterActive = ref({});
const searchersValues = reactive({
'search-dispatcher': '',
'search-station': '',
'search-date': '',
} as JournalDispatcherSearcher);
const countFromIndex = ref(0);
const countLimit = 15;
provide('sorterActive', sorterActive);
provide('journalFilterActive', journalFilterActive);
provide('searchersValues', searchersValues);
const scrollElement: Ref<HTMLElement | null> = ref(null);
return {
store: useStore(),
sorterActive,
searchersValues,
countFromIndex,
countLimit,
scrollElement,
maxCount: ref(15),
};
},
computed: {
computedHistoryList() {
return this.historyList.filter(
(doc) => doc.isOnline || (doc.currentDuration && doc.currentDuration > 10 * 60000)
);
},
},
activated() {
if (this.sceneryName || this.dispatcherName) {
this.searchersValues['search-station'] = this.sceneryName?.toString() || '';
this.searchersValues['search-dispatcher'] = this.dispatcherName?.toString() || '';
this.searchHistory();
}
},
mounted() {
if (!this.sceneryName && !this.dispatcherName) {
this.searchHistory();
}
},
methods: {
handleScroll(e: Event) {
const listElement = e.target as HTMLElement;
const scrollTop = listElement.scrollTop;
const elementHeight = listElement.scrollHeight - listElement.offsetHeight;
if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return;
if (scrollTop > elementHeight * 0.85) this.addHistoryData();
},
resetOptions() {
this.searchersValues['search-station'] = '';
this.searchersValues['search-dispatcher'] = '';
this.sorterActive.id = 'timestampFrom';
this.searchHistory();
},
searchHistory() {
this.fetchHistoryData({
searchers: this.searchersValues,
});
this.scrollNoMoreData = false;
this.scrollDataLoaded = true;
},
async addHistoryData() {
this.scrollDataLoaded = false;
const countFrom = this.historyList.length;
const responseData: DispatcherHistory[] = await (
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}&countFrom=${countFrom}`)
).data;
if (!responseData) return;
if (responseData.length == 0) {
this.scrollNoMoreData = true;
return;
}
this.historyList.push(...responseData);
this.scrollDataLoaded = true;
},
async fetchHistoryData(
props: {
searchers?: JournalDispatcherSearcher;
filter?: JournalTimetableFilter;
} = {}
) {
this.dataStatus = DataStatus.Loading;
const queries: string[] = [];
const dispatcher = props.searchers?.['search-dispatcher'].trim();
const station = props.searchers?.['search-station'].trim();
const dateString = props.searchers?.['search-date'].trim();
const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
if (dispatcher) queries.push(`dispatcherName=${dispatcher}`);
if (station) queries.push(`stationName=${station}`);
if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`);
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
if (this.sorterActive.id == 'timestampFrom') queries.push('sortBy=timestampFrom');
else if (this.sorterActive.id == 'duration') queries.push('sortBy=currentDuration');
else queries.push('sortBy=timestampFrom');
queries.push('countLimit=30');
this.currentQuery = queries.join('&');
try {
const responseData: DispatcherHistory[] = await (
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}`)
).data;
if (!responseData) {
this.dataStatus = DataStatus.Error;
return;
}
if (!responseData) return;
// Response data exists
this.historyList = responseData;
// Stats display
this.store.dispatcherStatsName =
this.historyList.length > 0 && this.searchersValues['search-dispatcher'].trim()
? this.historyList[0].dispatcherName
: '';
this.dataStatus = DataStatus.Loaded;
} catch (error) {
this.dataStatus = DataStatus.Error;
}
},
},
});
</script>
<style lang="scss" scoped>
@import '../styles/JournalSection.scss';
</style>
+291
View File
@@ -0,0 +1,291 @@
<template>
<section class="journal-timetables">
<JournalHeader />
<div class="journal_wrapper">
<TimetablesStats />
<JournalOptions
@on-search-confirm="searchHistory"
@on-options-reset="resetOptions"
:sorter-option-ids="['timetableId', 'beginDate', 'distance', 'total-stops']"
:filters="journalTimetableFilters"
:data-status="dataStatus"
/>
<!-- <DriverStats /> -->
<!-- <button @click="statsCardOpen = true">Stats</button> -->
<div class="list_wrapper" @scroll="handleScroll">
<!-- <transition name="warning" mode="out-in"> -->
<!-- <div :key="dataStatus"> -->
<Loading v-if="dataStatus == DataStatus.Initialized || dataStatus == DataStatus.Loading" />
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.error') }}
</div>
<div v-else-if="timetableHistory.length == 0" class="journal_warning">
{{ $t('app.no-result') }}
</div>
<div v-else>
<JournalTimetablesList :timetableHistory="timetableHistory" />
<button
class="btn btn--option btn--load-data"
v-if="!scrollNoMoreData && scrollDataLoaded && timetableHistory.length >= 15"
@click="addHistoryData"
>
{{ $t('journal.load-data') }}
</button>
</div>
<!-- </div> -->
<!-- </transition> -->
<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>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
import axios from 'axios';
import DriverStats from '../components/JournalView/DriverStats.vue';
import Loading from '../components/Global/Loading.vue';
import { JournalTimetableFilter, JournalTimetableSorter } from '../types/Journal/JournalTimetablesTypes';
import dateMixin from '../mixins/dateMixin';
import routerMixin from '../mixins/routerMixin';
import { DataStatus } from '../scripts/enums/DataStatus';
import { JournalFilterType } from '../scripts/enums/JournalFilterType';
import { TimetableHistory } from '../scripts/interfaces/api/TimetablesAPIData';
import { URLs } from '../scripts/utils/apiURLs';
import { useStore } from '../store/store';
import JournalOptions from '../components/JournalView/JournalOptions.vue';
import { JorunalTimetableSearchType } from '../types/Journal/JournalTimetablesTypes';
import modalTrainMixin from '../mixins/modalTrainMixin';
import imageMixin from '../mixins/imageMixin';
import JournalTimetablesList from '../components/JournalView/JournalTimetablesList.vue';
import { journalTimetableFilters } from '../constants/Journal/JournalTimetablesConsts';
import TimetablesStats from '../components/JournalView/TimetablesStats.vue';
import JournalHeader from '../components/JournalView/JournalHeader.vue';
const TIMETABLES_API_URL = `${URLs.stacjownikAPI}/api/getTimetables`;
export default defineComponent({
components: { DriverStats, Loading, JournalOptions, JournalTimetablesList, TimetablesStats, JournalHeader },
mixins: [dateMixin, routerMixin, modalTrainMixin, imageMixin],
name: 'JournalTimetables',
props: {
timetableId: {
type: String,
},
},
data: () => ({
currentQuery: '',
scrollDataLoaded: true,
scrollNoMoreData: false,
showReturnButton: false,
statsCardOpen: false,
timetableHistory: [] as TimetableHistory[],
journalTimetableFilters,
dataStatus: DataStatus.Initialized,
dataErrorMessage: '',
DataStatus,
}),
setup() {
const sorterActive: JournalTimetableSorter = reactive({ id: 'timetableId', dir: 1 });
const journalFilterActive = ref(journalTimetableFilters[0]);
const searchersValues = reactive({
'search-train': '',
'search-driver': '',
'search-dispatcher': '',
'search-date': '',
} as JorunalTimetableSearchType);
const countFromIndex = ref(0);
const countLimit = 15;
provide('searchersValues', searchersValues);
provide('sorterActive', sorterActive);
provide('journalFilterActive', journalFilterActive);
const scrollElement: Ref<HTMLElement | null> = ref(null);
return {
sorterActive,
journalFilterActive,
searchersValues,
countFromIndex,
countLimit,
scrollElement,
store: useStore(),
};
},
activated() {
if (this.timetableId) {
this.searchersValues['search-train'] = `#${this.timetableId}`;
this.searchHistory();
}
},
mounted() {
console.log('mounted');
if (!this.timetableId) this.searchHistory();
},
methods: {
handleScroll(e: Event) {
const listElement = e.target as HTMLElement;
const scrollTop = listElement.scrollTop;
const elementHeight = listElement.scrollHeight - listElement.offsetHeight;
if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return;
if (scrollTop > elementHeight * 0.85) this.addHistoryData();
},
resetOptions() {
this.searchersValues['search-date'] = '';
this.searchersValues['search-driver'] = '';
this.searchersValues['search-train'] = '';
this.searchersValues['search-dispatcher'] = '';
this.journalFilterActive = this.journalTimetableFilters[0];
this.sorterActive.id = 'timetableId';
this.searchHistory();
},
searchHistory() {
this.fetchHistoryData({
searchers: this.searchersValues,
filter: this.journalFilterActive,
});
this.scrollNoMoreData = false;
this.scrollDataLoaded = true;
},
async addHistoryData() {
this.scrollDataLoaded = false;
const countFrom = this.timetableHistory.length;
const responseData: TimetableHistory[] = await (
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}&countFrom=${countFrom}`)
).data;
if (!responseData) return;
if (responseData.length == 0) {
this.scrollNoMoreData = true;
return;
}
this.timetableHistory.push(...responseData);
this.scrollDataLoaded = true;
},
async fetchHistoryData(
props: {
searchers?: JorunalTimetableSearchType;
filter?: JournalTimetableFilter;
} = {}
) {
this.dataStatus = DataStatus.Loading;
const queries: string[] = [];
const driverName = props.searchers?.['search-driver'].trim();
const trainNo = props.searchers?.['search-train'].trim();
const authorName = props.searchers?.['search-dispatcher'].trim();
const dateString = props.searchers?.['search-date'].trim();
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)
queries.push(trainNo.startsWith('#') ? `timetableId=${trainNo.replace('#', '')}` : `trainNo=${trainNo}`);
if (authorName) queries.push(`authorName=${authorName}`);
if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`);
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
if (this.sorterActive.id == 'distance') queries.push('sortBy=routeDistance');
else if (this.sorterActive.id == 'total-stops') queries.push('sortBy=allStopsCount');
else if (this.sorterActive.id == 'beginDate') queries.push('sortBy=beginDate');
else queries.push('sortBy=timetableId');
queries.push('countLimit=15');
switch (props.filter?.id) {
case JournalFilterType.abandoned:
queries.push('fulfilled=0', 'terminated=1');
break;
case JournalFilterType.active:
queries.push('terminated=0');
break;
case JournalFilterType.fulfilled:
queries.push('fulfilled=1');
break;
default:
break;
}
this.currentQuery = queries.join('&');
try {
const responseData: TimetableHistory[] = await (
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}`)
).data;
if (!responseData) {
this.dataStatus = DataStatus.Error;
this.dataErrorMessage = 'Brak danych!';
return;
}
if (!responseData) return;
// Response data exists
this.timetableHistory = responseData;
// Stats display
this.store.driverStatsName =
this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim()
? this.timetableHistory[0].driverName
: '';
this.dataStatus = DataStatus.Loaded;
} catch (error) {
this.dataStatus = DataStatus.Error;
this.dataErrorMessage = 'Ups! Coś poszło nie tak!';
}
},
},
});
</script>
<style lang="scss" scoped>
@import '../styles/JournalSection.scss';
</style>
-77
View File
@@ -1,77 +0,0 @@
<template>
<section class="journal-view">
<div class="journal-type-options">
<router-link class="router-link" active-class="route-active" to="/journal/timetables" exact>
{{ $t('journal.section-timetables') }}
</router-link>
&nbsp;&bull;&nbsp;
<router-link class="router-link" active-class="route-active" to="/journal/dispatchers">
{{ $t('journal.section-dispatchers') }}
</router-link>
</div>
<div class="journal-section">
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" :key="$route.path" />
</keep-alive>
</router-view>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import JournalDispatchers from '../components/JournalView/JournalDispatchers.vue';
import JournalTimetables from '../components/JournalView/JournalTimetables.vue';
export default defineComponent({
components: { JournalDispatchers, JournalTimetables },
setup() {
const journalTypeChosen = ref('journalTimetables');
return {
activeJournalComponent: journalTypeChosen,
};
},
methods: {
changeJournalType(type: string) {
this.activeJournalComponent = type;
},
},
activated() {
const query = this.$route.query;
if (query.view == 'dispatchers') this.activeJournalComponent = 'journalDispatchers';
},
});
</script>
<style lang="scss" scoped>
.journal-type-options {
display: flex;
justify-content: center;
background-color: #2c2c2c;
max-width: 18em;
font-size: 1.2em;
margin: 0 auto;
border-radius: 0 0 0.5em 0.5em;
padding: 0.1em 0;
}
.journal-section > section {
height: 100%;
display: flex;
justify-content: center;
}
.router-link.active {
color: gold;
}
</style>