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
+1 -1
View File
@@ -10,7 +10,7 @@
<main class="app_main"> <main class="app_main">
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<keep-alive> <keep-alive exclude="JournalView">
<component :is="Component" :key="$route.name" /> <component :is="Component" :key="$route.name" />
</keep-alive> </keep-alive>
</router-view> </router-view>
+1 -1
View File
@@ -1,5 +1,5 @@
<template> <template>
<div class="journal-stats" v-if="store.driverStatsData?._sum.routeDistance != null"> <div class="journal-stats" v-if="store.driverStatsData">
<h1> <h1>
{{ $t('journal.stats-title') }} <span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span> {{ $t('journal.stats-title') }} <span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
</h1> </h1>
@@ -0,0 +1,46 @@
<template>
<section class="journal-header">
<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>
</section>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({});
</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>
+44 -27
View File
@@ -4,8 +4,9 @@
<button <button
v-for="tab in data.tabs" v-for="tab in data.tabs"
class="btn--filled" class="btn--filled"
:data-disabled="tab.disabled"
:disabled="tab.disabled" :disabled="tab.disabled"
:data-disabled="tab.disabled"
:data-selected="tab.name == store.currentStatsTab"
@click="onTabButtonClick(tab.name, tab.disabled)" @click="onTabButtonClick(tab.name, tab.disabled)"
> >
{{ tab.title }} {{ tab.title }}
@@ -19,7 +20,7 @@
<script setup lang="ts"> <script setup lang="ts">
import axios from 'axios'; import axios from 'axios';
import { computed, onActivated, reactive, Ref, ref, watch } from 'vue'; import { computed, onActivated, onDeactivated, reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { ITimetablesDailyStats, ITimetablesDailyStatsResponse } from '../../scripts/interfaces/api/StatsAPIData'; import { ITimetablesDailyStats, ITimetablesDailyStatsResponse } from '../../scripts/interfaces/api/StatsAPIData';
import { URLs } from '../../scripts/utils/apiURLs'; import { URLs } from '../../scripts/utils/apiURLs';
@@ -32,6 +33,7 @@ type TStatTab = 'daily' | 'driver';
// Variables // Variables
const store = useStore(); const store = useStore();
const intervalId = ref(-1);
let data = reactive({ let data = reactive({
tabs: [ tabs: [
@@ -64,6 +66,35 @@ function onTabButtonClick(tab: TStatTab, disabled: boolean) {
if (!disabled) store.currentStatsTab = tab; if (!disabled) store.currentStatsTab = tab;
} }
async function fetchDailyTimetableStats() {
console.log('test');
try {
const {
distanceAvg,
distanceSum,
maxTimetable,
totalTimetables,
mostActiveDispatcher,
}: ITimetablesDailyStatsResponse = await (
await axios.get(`${URLs.stacjownikAPI}/api/getDailyTimetableStats`)
).data;
data.stats = {
totalTimetables,
distanceSum,
distanceAvg,
timetableAuthor: maxTimetable?.authorName || '',
timetableDriver: maxTimetable?.driverName || '',
timetableId: maxTimetable?.timetableId || 0,
timetableRouteDistance: maxTimetable?.routeDistance || 0,
dispatcherName: mostActiveDispatcher?.name || '',
dispatcherTimetablesCount: mostActiveDispatcher?.count || 0,
};
} catch (error) {
console.error('Ups! Wystąpił błąd podczas pobierania statystyk rozkładów jazdy...');
}
}
// Translation // Translation
const { t } = useI18n(); const { t } = useI18n();
@@ -101,37 +132,19 @@ watch(
} }
); );
onActivated(async () => { onActivated(() => {
try { fetchDailyTimetableStats();
const { intervalId.value = setInterval(fetchDailyTimetableStats, 60000);
distanceAvg, });
distanceSum,
maxTimetable,
totalTimetables,
mostActiveDispatcher,
}: ITimetablesDailyStatsResponse = await (
await axios.get(`${URLs.stacjownikAPI}/api/getDailyTimetableStats`)
).data;
data.stats = { onDeactivated(() => {
totalTimetables, clearInterval(intervalId.value);
distanceSum,
distanceAvg,
timetableAuthor: maxTimetable?.authorName || '',
timetableDriver: maxTimetable?.driverName || '',
timetableId: maxTimetable?.timetableId || 0,
timetableRouteDistance: maxTimetable?.routeDistance || 0,
dispatcherName: mostActiveDispatcher?.name || '',
dispatcherTimetablesCount: mostActiveDispatcher?.count || 0,
};
} catch (error) {
console.error('Ups! Wystąpił błąd podczas pobierania statystyk rozkładów jazdy...');
}
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/JournalStats.scss'; @import '../../styles/JournalStats.scss';
@import '../../styles/variables.scss';
.tabs { .tabs {
display: flex; display: flex;
@@ -141,6 +154,10 @@ onActivated(async () => {
font-weight: bold; font-weight: bold;
border-radius: 0.4em 0.4em 0 0; border-radius: 0.4em 0.4em 0 0;
padding: 0.5em 0.75em; padding: 0.5em 0.75em;
&[data-selected='true'] {
color: $accentCol;
}
} }
} }
+17 -29
View File
@@ -1,6 +1,6 @@
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import JournalDispatchersVue from '../components/JournalView/JournalDispatchers.vue'; import JournalDispatchersVue from '../views/JournalDispatchers.vue';
import JournalTimetablesVue from '../components/JournalView/JournalTimetables.vue'; import JournalTimetablesVue from '../views/JournalTimetables.vue';
const routes: Array<RouteRecordRaw> = [ const routes: Array<RouteRecordRaw> = [
{ {
@@ -19,34 +19,22 @@ const routes: Array<RouteRecordRaw> = [
name: 'SceneryView', name: 'SceneryView',
component: () => import('../views/SceneryView.vue'), component: () => import('../views/SceneryView.vue'),
}, },
{ {
path: '/journal', path: '/journal/timetables',
name: 'JournalView', name: 'JournalTimetables',
component: () => import('../views/JournalView.vue'), component: JournalTimetablesVue,
children: [ props: (route) => ({
{ trainNo: route.query.trainNo,
path: '', driverName: route.query.driverName,
name: 'JournalTimetables', timetableId: route.query.timetableId,
component: JournalTimetablesVue, }),
alias: '/timetables', },
}, {
{ path: '/journal/dispatchers',
path: 'dispatchers', name: 'JournalDispatchers',
name: 'JournalDispatchers', component: JournalDispatchersVue,
component: JournalDispatchersVue, props: (route) => ({ sceneryName: route.query.sceneryName, dispatcherName: route.query.dispatcherName }),
props: (route) => ({ sceneryName: route.query.sceneryName, dispatcherName: route.query.dispatcherName }),
},
{
path: 'timetables',
name: 'JournalTimetables',
component: JournalTimetablesVue,
props: (route) => ({
trainNo: route.query.trainNo,
driverName: route.query.driverName,
timetableId: route.query.timetableId,
}),
},
],
}, },
{ {
path: '/:catchAll(.*)', path: '/:catchAll(.*)',
+2
View File
@@ -30,6 +30,8 @@
max-width: 1350px; max-width: 1350px;
width: 100%; width: 100%;
margin: 0 auto;
padding: 1em 0; padding: 1em 0;
} }
@@ -1,264 +1,267 @@
<template> <template>
<section class="journal-timetables"> <section class="journal-timetables">
<div class="journal_wrapper"> <JournalHeader />
<JournalOptions
@on-search-confirm="searchHistory" <div class="journal_wrapper">
@on-options-reset="resetOptions" <JournalOptions
:sorter-option-ids="['timestampFrom', 'duration']" @on-search-confirm="searchHistory"
:data-status="dataStatus" @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"> --> <div class="list_wrapper" @scroll="handleScroll">
<Loading v-if="dataStatus == DataStatus.Initialized || dataStatus == DataStatus.Loading" /> <!-- <transition name="warning" mode="out-in"> -->
<!-- <div :key="dataStatus"> -->
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error"> <Loading v-if="dataStatus == DataStatus.Initialized || dataStatus == DataStatus.Loading" />
{{ $t('app.error') }}
</div> <div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.error') }}
<div class="journal_warning" v-else-if="historyList.length == 0"> </div>
{{ $t('app.no-result') }}
</div> <div class="journal_warning" v-else-if="historyList.length == 0">
{{ $t('app.no-result') }}
<div v-else> </div>
<JournalDispatchersList :dispatcherHistory="computedHistoryList" />
<div v-else>
<button <JournalDispatchersList :dispatcherHistory="computedHistoryList" />
class="btn btn--option btn--load-data"
v-if="!scrollNoMoreData && scrollDataLoaded && computedHistoryList.length > 15" <button
@click="addHistoryData" class="btn btn--option btn--load-data"
> v-if="!scrollNoMoreData && scrollDataLoaded && computedHistoryList.length > 15"
{{ $t('journal.load-data') }} @click="addHistoryData"
</button> >
</div> {{ $t('journal.load-data') }}
<!-- </div> </button>
</transition> --> </div>
<!-- </div>
<div class="journal_warning" v-if="scrollNoMoreData"> </transition> -->
{{ $t('journal.no-further-data') }}
</div> <div class="journal_warning" v-if="scrollNoMoreData">
{{ $t('journal.no-further-data') }}
<div class="journal_warning" v-else-if="!scrollDataLoaded"> </div>
{{ $t('journal.loading-further-data') }}
</div> <div class="journal_warning" v-else-if="!scrollDataLoaded">
</div> {{ $t('journal.loading-further-data') }}
</div> </div>
</section> </div>
</template> </div>
</section>
<script lang="ts"> </template>
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
import axios from 'axios'; <script lang="ts">
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
import ActionButton from '../../components/Global/ActionButton.vue'; import axios from 'axios';
import JournalOptions from '../../components/JournalView/JournalOptions.vue';
import DispatcherStats from '../../components/JournalView/DispatcherStats.vue'; import ActionButton from '../components/Global/ActionButton.vue';
import SearchBox from '../Global/SearchBox.vue'; import JournalOptions from '../components/JournalView/JournalOptions.vue';
import DispatcherStats from '../components/JournalView/DispatcherStats.vue';
import Loading from '../Global/Loading.vue'; import SearchBox from '../components/Global/SearchBox.vue';
import { URLs } from '../../scripts/utils/apiURLs';
import { DataStatus } from '../../scripts/enums/DataStatus'; import Loading from '../components/Global/Loading.vue';
import { useStore } from '../../store/store'; import { URLs } from '../scripts/utils/apiURLs';
import JournalDispatchersList from './JournalDispatchersList.vue'; import { DataStatus } from '../scripts/enums/DataStatus';
import { JournalDispatcherSearcher, JournalDispatcherSorter } from '../../types/Journal/JournalDispatcherTypes'; import { useStore } from '../store/store';
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData'; import JournalDispatchersList from '../components/JournalView/JournalDispatchersList.vue';
import { JournalTimetableFilter } from '../../types/Journal/JournalTimetablesTypes'; import { JournalDispatcherSearcher, JournalDispatcherSorter } from '../types/Journal/JournalDispatcherTypes';
import { DispatcherHistory } from '../scripts/interfaces/api/DispatchersAPIData';
const DISPATCHERS_API_URL = `${URLs.stacjownikAPI}/api/getDispatchers`; import { JournalTimetableFilter } from '../types/Journal/JournalTimetablesTypes';
import JournalHeader from '../components/JournalView/JournalHeader.vue';
export default defineComponent({
components: { SearchBox, ActionButton, JournalOptions, DispatcherStats, Loading, JournalDispatchersList }, const DISPATCHERS_API_URL = `${URLs.stacjownikAPI}/api/getDispatchers`;
name: 'JournalDispatchers',
export default defineComponent({
props: { components: { SearchBox, ActionButton, JournalOptions, DispatcherStats, Loading, JournalDispatchersList, JournalHeader },
sceneryName: { name: 'JournalDispatchers',
type: String,
required: false, props: {
}, sceneryName: {
type: String,
dispatcherName: { required: false,
type: String, },
required: false,
}, dispatcherName: {
}, type: String,
required: false,
data: () => ({ },
currentQuery: '', },
scrollDataLoaded: true,
scrollNoMoreData: false, data: () => ({
currentQuery: '',
showReturnButton: false, scrollDataLoaded: true,
statsCardOpen: false, scrollNoMoreData: false,
dataStatus: DataStatus.Initialized, showReturnButton: false,
DataStatus, statsCardOpen: false,
historyList: [] as DispatcherHistory[], dataStatus: DataStatus.Initialized,
}), DataStatus,
setup() { historyList: [] as DispatcherHistory[],
const sorterActive: JournalDispatcherSorter = reactive({ id: 'timestampFrom', dir: -1 }); }),
const journalFilterActive = ref({});
setup() {
const searchersValues = reactive({ const sorterActive: JournalDispatcherSorter = reactive({ id: 'timestampFrom', dir: -1 });
'search-dispatcher': '', const journalFilterActive = ref({});
'search-station': '',
'search-date': '', const searchersValues = reactive({
} as JournalDispatcherSearcher); 'search-dispatcher': '',
'search-station': '',
const countFromIndex = ref(0); 'search-date': '',
const countLimit = 15; } as JournalDispatcherSearcher);
provide('sorterActive', sorterActive); const countFromIndex = ref(0);
provide('journalFilterActive', journalFilterActive); const countLimit = 15;
provide('searchersValues', searchersValues);
provide('sorterActive', sorterActive);
const scrollElement: Ref<HTMLElement | null> = ref(null); provide('journalFilterActive', journalFilterActive);
provide('searchersValues', searchersValues);
return {
store: useStore(), const scrollElement: Ref<HTMLElement | null> = ref(null);
sorterActive, return {
searchersValues, store: useStore(),
countFromIndex, sorterActive,
countLimit, searchersValues,
scrollElement, countFromIndex,
maxCount: ref(15), countLimit,
};
}, scrollElement,
maxCount: ref(15),
computed: { };
computedHistoryList() { },
return this.historyList.filter(
(doc) => doc.isOnline || (doc.currentDuration && doc.currentDuration > 10 * 60000) 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() || ''; activated() {
this.searchHistory(); 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();
} mounted() {
}, if (!this.sceneryName && !this.dispatcherName) {
this.searchHistory();
methods: { }
handleScroll(e: Event) { },
const listElement = e.target as HTMLElement;
const scrollTop = listElement.scrollTop; methods: {
const elementHeight = listElement.scrollHeight - listElement.offsetHeight; handleScroll(e: Event) {
const listElement = e.target as HTMLElement;
if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return; const scrollTop = listElement.scrollTop;
const elementHeight = listElement.scrollHeight - listElement.offsetHeight;
if (scrollTop > elementHeight * 0.85) this.addHistoryData();
}, if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return;
resetOptions() { if (scrollTop > elementHeight * 0.85) this.addHistoryData();
this.searchersValues['search-station'] = ''; },
this.searchersValues['search-dispatcher'] = '';
this.sorterActive.id = 'timestampFrom'; resetOptions() {
this.searchersValues['search-station'] = '';
this.searchHistory(); this.searchersValues['search-dispatcher'] = '';
}, this.sorterActive.id = 'timestampFrom';
searchHistory() { this.searchHistory();
this.fetchHistoryData({ },
searchers: this.searchersValues,
}); searchHistory() {
this.fetchHistoryData({
this.scrollNoMoreData = false; searchers: this.searchersValues,
this.scrollDataLoaded = true; });
},
this.scrollNoMoreData = false;
async addHistoryData() { this.scrollDataLoaded = true;
this.scrollDataLoaded = false; },
const countFrom = this.historyList.length; async addHistoryData() {
this.scrollDataLoaded = false;
const responseData: DispatcherHistory[] = await (
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}&countFrom=${countFrom}`) const countFrom = this.historyList.length;
).data;
const responseData: DispatcherHistory[] = await (
if (!responseData) return; await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}&countFrom=${countFrom}`)
).data;
if (responseData.length == 0) {
this.scrollNoMoreData = true; if (!responseData) return;
return;
} if (responseData.length == 0) {
this.scrollNoMoreData = true;
this.historyList.push(...responseData); return;
this.scrollDataLoaded = true; }
},
this.historyList.push(...responseData);
async fetchHistoryData( this.scrollDataLoaded = true;
props: { },
searchers?: JournalDispatcherSearcher;
filter?: JournalTimetableFilter; async fetchHistoryData(
} = {} props: {
) { searchers?: JournalDispatcherSearcher;
this.dataStatus = DataStatus.Loading; filter?: JournalTimetableFilter;
} = {}
const queries: string[] = []; ) {
this.dataStatus = DataStatus.Loading;
const dispatcher = props.searchers?.['search-dispatcher'].trim();
const station = props.searchers?.['search-station'].trim(); const queries: string[] = [];
const dateString = props.searchers?.['search-date'].trim();
const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined; const dispatcher = props.searchers?.['search-dispatcher'].trim();
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined; const station = props.searchers?.['search-station'].trim();
const dateString = props.searchers?.['search-date'].trim();
if (dispatcher) queries.push(`dispatcherName=${dispatcher}`); const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
if (station) queries.push(`stationName=${station}`); const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`);
if (dispatcher) queries.push(`dispatcherName=${dispatcher}`);
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance']; if (station) queries.push(`stationName=${station}`);
if (this.sorterActive.id == 'timestampFrom') queries.push('sortBy=timestampFrom'); if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`);
else if (this.sorterActive.id == 'duration') queries.push('sortBy=currentDuration');
else queries.push('sortBy=timestampFrom'); // Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
if (this.sorterActive.id == 'timestampFrom') queries.push('sortBy=timestampFrom');
queries.push('countLimit=30'); else if (this.sorterActive.id == 'duration') queries.push('sortBy=currentDuration');
else queries.push('sortBy=timestampFrom');
this.currentQuery = queries.join('&');
queries.push('countLimit=30');
try {
const responseData: DispatcherHistory[] = await ( this.currentQuery = queries.join('&');
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}`)
).data; try {
const responseData: DispatcherHistory[] = await (
if (!responseData) { await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}`)
this.dataStatus = DataStatus.Error; ).data;
return;
} if (!responseData) {
this.dataStatus = DataStatus.Error;
if (!responseData) return; return;
}
// Response data exists
this.historyList = responseData; if (!responseData) return;
// Stats display // Response data exists
this.store.dispatcherStatsName = this.historyList = responseData;
this.historyList.length > 0 && this.searchersValues['search-dispatcher'].trim()
? this.historyList[0].dispatcherName // Stats display
: ''; this.store.dispatcherStatsName =
this.historyList.length > 0 && this.searchersValues['search-dispatcher'].trim()
this.dataStatus = DataStatus.Loaded; ? this.historyList[0].dispatcherName
} catch (error) { : '';
this.dataStatus = DataStatus.Error;
} this.dataStatus = DataStatus.Loaded;
}, } catch (error) {
}, this.dataStatus = DataStatus.Error;
}); }
</script> },
},
<style lang="scss" scoped> });
@import '../../styles/JournalSection.scss'; </script>
</style>
<style lang="scss" scoped>
@import '../styles/JournalSection.scss';
</style>
@@ -1,286 +1,291 @@
<template> <template>
<section class="journal-timetables"> <section class="journal-timetables">
<div class="journal_wrapper"> <JournalHeader />
<TimetablesStats />
<div class="journal_wrapper">
<JournalOptions <TimetablesStats />
@on-search-confirm="searchHistory"
@on-options-reset="resetOptions" <JournalOptions
:sorter-option-ids="['timetableId', 'beginDate', 'distance', 'total-stops']" @on-search-confirm="searchHistory"
:filters="journalTimetableFilters" @on-options-reset="resetOptions"
:data-status="dataStatus" :sorter-option-ids="['timetableId', 'beginDate', 'distance', 'total-stops']"
/> :filters="journalTimetableFilters"
:data-status="dataStatus"
<!-- <DriverStats /> --> />
<!-- <button @click="statsCardOpen = true">Stats</button> -->
<!-- <DriverStats /> -->
<div class="list_wrapper" @scroll="handleScroll"> <!-- <button @click="statsCardOpen = true">Stats</button> -->
<!-- <transition name="warning" mode="out-in"> -->
<!-- <div :key="dataStatus"> --> <div class="list_wrapper" @scroll="handleScroll">
<Loading v-if="dataStatus == DataStatus.Initialized || dataStatus == DataStatus.Loading" /> <!-- <transition name="warning" mode="out-in"> -->
<!-- <div :key="dataStatus"> -->
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error"> <Loading v-if="dataStatus == DataStatus.Initialized || dataStatus == DataStatus.Loading" />
{{ $t('app.error') }}
</div> <div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.error') }}
<div v-else-if="timetableHistory.length == 0" class="journal_warning"> </div>
{{ $t('app.no-result') }}
</div> <div v-else-if="timetableHistory.length == 0" class="journal_warning">
{{ $t('app.no-result') }}
<div v-else> </div>
<JournalTimetablesList :timetableHistory="timetableHistory" />
<div v-else>
<button <JournalTimetablesList :timetableHistory="timetableHistory" />
class="btn btn--option btn--load-data"
v-if="!scrollNoMoreData && scrollDataLoaded && timetableHistory.length >= 15" <button
@click="addHistoryData" class="btn btn--option btn--load-data"
> v-if="!scrollNoMoreData && scrollDataLoaded && timetableHistory.length >= 15"
{{ $t('journal.load-data') }} @click="addHistoryData"
</button> >
</div> {{ $t('journal.load-data') }}
<!-- </div> --> </button>
<!-- </transition> --> </div>
<!-- </div> -->
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div> <!-- </transition> -->
<div class="journal_warning" v-else-if="!scrollDataLoaded">{{ $t('journal.loading-further-data') }}</div>
</div> <div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
</div> <div class="journal_warning" v-else-if="!scrollDataLoaded">{{ $t('journal.loading-further-data') }}</div>
</section> </div>
</template> </div>
</section>
<script lang="ts"> </template>
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
import axios from 'axios'; <script lang="ts">
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
import DriverStats from './DriverStats.vue'; import axios from 'axios';
import Loading from '../Global/Loading.vue';
import { JournalTimetableFilter, JournalTimetableSorter } from '../../types/Journal/JournalTimetablesTypes'; import DriverStats from '../components/JournalView/DriverStats.vue';
import dateMixin from '../../mixins/dateMixin'; import Loading from '../components/Global/Loading.vue';
import routerMixin from '../../mixins/routerMixin'; import { JournalTimetableFilter, JournalTimetableSorter } from '../types/Journal/JournalTimetablesTypes';
import { DataStatus } from '../../scripts/enums/DataStatus'; import dateMixin from '../mixins/dateMixin';
import { JournalFilterType } from '../../scripts/enums/JournalFilterType'; import routerMixin from '../mixins/routerMixin';
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData'; import { DataStatus } from '../scripts/enums/DataStatus';
import { URLs } from '../../scripts/utils/apiURLs'; import { JournalFilterType } from '../scripts/enums/JournalFilterType';
import { useStore } from '../../store/store'; import { TimetableHistory } from '../scripts/interfaces/api/TimetablesAPIData';
import JournalOptions from './JournalOptions.vue'; import { URLs } from '../scripts/utils/apiURLs';
import { JorunalTimetableSearchType } from '../../types/Journal/JournalTimetablesTypes'; import { useStore } from '../store/store';
import modalTrainMixin from '../../mixins/modalTrainMixin'; import JournalOptions from '../components/JournalView/JournalOptions.vue';
import imageMixin from '../../mixins/imageMixin'; import { JorunalTimetableSearchType } from '../types/Journal/JournalTimetablesTypes';
import JournalTimetablesList from './JournalTimetablesList.vue'; import modalTrainMixin from '../mixins/modalTrainMixin';
import { journalTimetableFilters } from '../../constants/Journal/JournalTimetablesConsts'; import imageMixin from '../mixins/imageMixin';
import TimetablesStats from './TimetablesStats.vue'; import JournalTimetablesList from '../components/JournalView/JournalTimetablesList.vue';
import { journalTimetableFilters } from '../constants/Journal/JournalTimetablesConsts';
const TIMETABLES_API_URL = `${URLs.stacjownikAPI}/api/getTimetables`; import TimetablesStats from '../components/JournalView/TimetablesStats.vue';
import JournalHeader from '../components/JournalView/JournalHeader.vue';
export default defineComponent({
components: { DriverStats, Loading, JournalOptions, JournalTimetablesList, TimetablesStats }, const TIMETABLES_API_URL = `${URLs.stacjownikAPI}/api/getTimetables`;
mixins: [dateMixin, routerMixin, modalTrainMixin, imageMixin],
export default defineComponent({
name: 'JournalTimetables', components: { DriverStats, Loading, JournalOptions, JournalTimetablesList, TimetablesStats, JournalHeader },
mixins: [dateMixin, routerMixin, modalTrainMixin, imageMixin],
props: {
timetableId: { name: 'JournalTimetables',
type: String,
}, props: {
}, timetableId: {
type: String,
data: () => ({ },
currentQuery: '', },
scrollDataLoaded: true,
scrollNoMoreData: false, data: () => ({
currentQuery: '',
showReturnButton: false, scrollDataLoaded: true,
statsCardOpen: false, scrollNoMoreData: false,
timetableHistory: [] as TimetableHistory[], showReturnButton: false,
journalTimetableFilters, statsCardOpen: false,
dataStatus: DataStatus.Initialized, timetableHistory: [] as TimetableHistory[],
dataErrorMessage: '', journalTimetableFilters,
DataStatus, dataStatus: DataStatus.Initialized,
}), dataErrorMessage: '',
setup() { DataStatus,
const sorterActive: JournalTimetableSorter = reactive({ id: 'timetableId', dir: 1 }); }),
const journalFilterActive = ref(journalTimetableFilters[0]);
setup() {
const searchersValues = reactive({ const sorterActive: JournalTimetableSorter = reactive({ id: 'timetableId', dir: 1 });
'search-train': '', const journalFilterActive = ref(journalTimetableFilters[0]);
'search-driver': '',
'search-dispatcher': '', const searchersValues = reactive({
'search-date': '', 'search-train': '',
} as JorunalTimetableSearchType); 'search-driver': '',
'search-dispatcher': '',
const countFromIndex = ref(0); 'search-date': '',
const countLimit = 15; } as JorunalTimetableSearchType);
provide('searchersValues', searchersValues); const countFromIndex = ref(0);
provide('sorterActive', sorterActive); const countLimit = 15;
provide('journalFilterActive', journalFilterActive);
provide('searchersValues', searchersValues);
const scrollElement: Ref<HTMLElement | null> = ref(null); provide('sorterActive', sorterActive);
provide('journalFilterActive', journalFilterActive);
return {
sorterActive, const scrollElement: Ref<HTMLElement | null> = ref(null);
journalFilterActive,
searchersValues, return {
sorterActive,
countFromIndex, journalFilterActive,
countLimit, searchersValues,
scrollElement, countFromIndex,
store: useStore(), countLimit,
};
}, scrollElement,
store: useStore(),
activated() { };
if (this.timetableId) { },
this.searchersValues['search-train'] = `#${this.timetableId}`;
this.searchHistory(); activated() {
} if (this.timetableId) {
}, this.searchersValues['search-train'] = `#${this.timetableId}`;
this.searchHistory();
mounted() { }
if (!this.timetableId) this.searchHistory(); },
},
mounted() {
methods: { console.log('mounted');
handleScroll(e: Event) {
const listElement = e.target as HTMLElement; if (!this.timetableId) this.searchHistory();
const scrollTop = listElement.scrollTop; },
const elementHeight = listElement.scrollHeight - listElement.offsetHeight;
methods: {
if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return; handleScroll(e: Event) {
const listElement = e.target as HTMLElement;
if (scrollTop > elementHeight * 0.85) this.addHistoryData(); const scrollTop = listElement.scrollTop;
}, const elementHeight = listElement.scrollHeight - listElement.offsetHeight;
resetOptions() { if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return;
this.searchersValues['search-date'] = '';
this.searchersValues['search-driver'] = ''; if (scrollTop > elementHeight * 0.85) this.addHistoryData();
this.searchersValues['search-train'] = ''; },
this.searchersValues['search-dispatcher'] = '';
resetOptions() {
this.journalFilterActive = this.journalTimetableFilters[0]; this.searchersValues['search-date'] = '';
this.sorterActive.id = 'timetableId'; this.searchersValues['search-driver'] = '';
this.searchersValues['search-train'] = '';
this.searchHistory(); this.searchersValues['search-dispatcher'] = '';
},
this.journalFilterActive = this.journalTimetableFilters[0];
searchHistory() { this.sorterActive.id = 'timetableId';
this.fetchHistoryData({
searchers: this.searchersValues, this.searchHistory();
filter: this.journalFilterActive, },
});
searchHistory() {
this.scrollNoMoreData = false; this.fetchHistoryData({
this.scrollDataLoaded = true; searchers: this.searchersValues,
}, filter: this.journalFilterActive,
});
async addHistoryData() {
this.scrollDataLoaded = false; this.scrollNoMoreData = false;
this.scrollDataLoaded = true;
const countFrom = this.timetableHistory.length; },
const responseData: TimetableHistory[] = await ( async addHistoryData() {
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}&countFrom=${countFrom}`) this.scrollDataLoaded = false;
).data;
const countFrom = this.timetableHistory.length;
if (!responseData) return;
const responseData: TimetableHistory[] = await (
if (responseData.length == 0) { await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}&countFrom=${countFrom}`)
this.scrollNoMoreData = true; ).data;
return;
} if (!responseData) return;
this.timetableHistory.push(...responseData); if (responseData.length == 0) {
this.scrollDataLoaded = true; this.scrollNoMoreData = true;
}, return;
}
async fetchHistoryData(
props: { this.timetableHistory.push(...responseData);
searchers?: JorunalTimetableSearchType; this.scrollDataLoaded = true;
filter?: JournalTimetableFilter; },
} = {}
) { async fetchHistoryData(
this.dataStatus = DataStatus.Loading; props: {
searchers?: JorunalTimetableSearchType;
const queries: string[] = []; filter?: JournalTimetableFilter;
} = {}
const driverName = props.searchers?.['search-driver'].trim(); ) {
const trainNo = props.searchers?.['search-train'].trim(); this.dataStatus = DataStatus.Loading;
const authorName = props.searchers?.['search-dispatcher'].trim();
const queries: string[] = [];
const dateString = props.searchers?.['search-date'].trim();
const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined; const driverName = props.searchers?.['search-driver'].trim();
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined; const trainNo = props.searchers?.['search-train'].trim();
const authorName = props.searchers?.['search-dispatcher'].trim();
if (driverName) queries.push(`driverName=${driverName}`);
if (trainNo) const dateString = props.searchers?.['search-date'].trim();
queries.push(trainNo.startsWith('#') ? `timetableId=${trainNo.replace('#', '')}` : `trainNo=${trainNo}`); const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
if (authorName) queries.push(`authorName=${authorName}`); const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`);
if (driverName) queries.push(`driverName=${driverName}`);
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance']; if (trainNo)
if (this.sorterActive.id == 'distance') queries.push('sortBy=routeDistance'); queries.push(trainNo.startsWith('#') ? `timetableId=${trainNo.replace('#', '')}` : `trainNo=${trainNo}`);
else if (this.sorterActive.id == 'total-stops') queries.push('sortBy=allStopsCount'); if (authorName) queries.push(`authorName=${authorName}`);
else if (this.sorterActive.id == 'beginDate') queries.push('sortBy=beginDate'); if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`);
else queries.push('sortBy=timetableId');
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
queries.push('countLimit=15'); if (this.sorterActive.id == 'distance') queries.push('sortBy=routeDistance');
else if (this.sorterActive.id == 'total-stops') queries.push('sortBy=allStopsCount');
switch (props.filter?.id) { else if (this.sorterActive.id == 'beginDate') queries.push('sortBy=beginDate');
case JournalFilterType.abandoned: else queries.push('sortBy=timetableId');
queries.push('fulfilled=0', 'terminated=1');
break; queries.push('countLimit=15');
case JournalFilterType.active: switch (props.filter?.id) {
queries.push('terminated=0'); case JournalFilterType.abandoned:
break; queries.push('fulfilled=0', 'terminated=1');
break;
case JournalFilterType.fulfilled:
queries.push('fulfilled=1'); case JournalFilterType.active:
break; queries.push('terminated=0');
break;
default:
break; case JournalFilterType.fulfilled:
} queries.push('fulfilled=1');
break;
this.currentQuery = queries.join('&');
default:
try { break;
const responseData: TimetableHistory[] = await ( }
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}`)
).data; this.currentQuery = queries.join('&');
if (!responseData) { try {
this.dataStatus = DataStatus.Error; const responseData: TimetableHistory[] = await (
this.dataErrorMessage = 'Brak danych!'; await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}`)
return; ).data;
}
if (!responseData) {
if (!responseData) return; this.dataStatus = DataStatus.Error;
this.dataErrorMessage = 'Brak danych!';
// Response data exists return;
this.timetableHistory = responseData; }
// Stats display if (!responseData) return;
this.store.driverStatsName =
this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim() // Response data exists
? this.timetableHistory[0].driverName this.timetableHistory = responseData;
: '';
// Stats display
this.dataStatus = DataStatus.Loaded; this.store.driverStatsName =
} catch (error) { this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim()
this.dataStatus = DataStatus.Error; ? this.timetableHistory[0].driverName
this.dataErrorMessage = 'Ups! Coś poszło nie tak!'; : '';
}
}, this.dataStatus = DataStatus.Loaded;
}, } catch (error) {
}); this.dataStatus = DataStatus.Error;
</script> this.dataErrorMessage = 'Ups! Coś poszło nie tak!';
}
<style lang="scss" scoped> },
@import '../../styles/JournalSection.scss'; },
</style> });
</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>