Compare commits

..

14 Commits

Author SHA1 Message Date
Spythere f28600a7fa Merge do wersji 1.16.0
Wersja 1.16.0
2023-06-12 01:33:45 +02:00
Spythere d59ead87e6 bump v1.16.0 2023-06-12 01:21:49 +02:00
Spythere 34d91bc800 poprawki w pokazywaniu statystyk 2023-06-12 01:19:31 +02:00
Spythere cf9991d8a0 layout filtrów dzienników 2023-06-12 00:51:17 +02:00
Spythere 4ffb79d62b poprawki pobierania danych 2023-06-11 21:47:50 +02:00
Spythere d9f5edb4fe poprawki tłumaczeń 2023-06-11 21:47:22 +02:00
Spythere 1b2112430a feature: długości szlaków po kliknięciu 2023-06-08 23:35:57 +02:00
Spythere 0a972a23ef fix: asynchroniczność pobierania danych z API 2023-06-04 13:35:53 +02:00
Spythere 6d52724d06 zapamiętywanie stanu statystyk dnia 2023-06-04 12:19:46 +02:00
Spythere 99415c35d3 rozbudowane filtry dziennika RJ 2023-06-04 12:06:15 +02:00
Spythere c3f687d439 hotfixy dzienników 2023-06-04 01:45:58 +02:00
Spythere 266edfd6e6 reorder typów danych 2023-06-04 00:33:43 +02:00
Spythere d32d5ad91b poprawki dzienników 2023-06-03 18:55:44 +02:00
Spythere c3481470cb optymalizacja zapytań; filtr scenerii pocz. 2023-06-03 15:49:15 +02:00
28 changed files with 1289 additions and 1121 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.15.1", "version": "1.16.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+18 -4
View File
@@ -95,13 +95,18 @@
<script setup lang="ts"> <script setup lang="ts">
import axios from 'axios'; import axios from 'axios';
import { computed, reactive, ref } from 'vue'; import { computed, onActivated, onDeactivated, onMounted, reactive, ref } from 'vue';
import { DataStatus } from '../../scripts/enums/DataStatus'; import { DataStatus } from '../../scripts/enums/DataStatus';
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';
import StorageManager from '../../scripts/managers/storageManager';
const intervalId = ref(-1); const intervalId = ref(-1);
const emit = defineEmits<{
(e: 'toggleStatsOpen', value: boolean): void;
}>();
const data = reactive({ const data = reactive({
statsStatus: DataStatus.Loading, statsStatus: DataStatus.Loading,
@@ -158,17 +163,26 @@ async function fetchDailyTimetableStats() {
function startFetchingDailyStats() { function startFetchingDailyStats() {
fetchDailyTimetableStats(); fetchDailyTimetableStats();
if (intervalId.value != -1) return;
intervalId.value = setInterval(fetchDailyTimetableStats, 60000); intervalId.value = setInterval(fetchDailyTimetableStats, 60000);
} }
function stopFetchingDailyStats() { function stopFetchingDailyStats() {
clearInterval(intervalId.value); clearInterval(intervalId.value);
intervalId.value = -1;
} }
defineExpose({ onActivated(() => {
startFetchingDailyStats, startFetchingDailyStats();
stopFetchingDailyStats, emit('toggleStatsOpen', true);
}); });
onDeactivated(() => {
stopFetchingDailyStats();
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+40 -30
View File
@@ -49,15 +49,6 @@
</button> </button>
</div> </div>
</div> </div>
<div class="search_actions">
<button class="btn--action" @click="onResetButtonClick">
{{ $t('options.reset-button') }}
</button>
<button class="btn--action" @click="onSearchButtonConfirm">
{{ $t('options.search-button') }}
</button>
</div>
</div> </div>
<h1 class="option-title">{{ $t('options.sort-title') }}</h1> <h1 class="option-title">{{ $t('options.sort-title') }}</h1>
@@ -74,15 +65,31 @@
</div> </div>
<h1 class="option-title" v-if="filters.length != 0">{{ $t('options.filter-title') }}</h1> <h1 class="option-title" v-if="filters.length != 0">{{ $t('options.filter-title') }}</h1>
<div class="options_filters">
<button <div class="options_filter-sections" v-if="filters.length != 0 && filterList">
v-for="filter in filters" <section class="filter-section" v-for="section in JournalFilterSection">
class="filter-option btn--option" <p>{{ $t(`options.filter-section-${section}`) }}</p>
:class="{ checked: journalFilterActive.id === filter.id }"
:id="filter.id" <div class="options_filters">
@click="onFilterChange(filter)" <button
> v-for="filter in filterList.filter((f) => f.filterSection == section)"
{{ $t(`options.filter-${filter.id}`) }} class="filter-option btn--option"
:class="{ checked: filter.isActive }"
:id="filter.id"
@click="onFilterChange(filter)"
>
{{ $t(`options.filter-${filter.id}`) }}
</button>
</div>
</section>
</div>
<div class="options_actions">
<button class="btn--action" @click="onResetButtonClick">
{{ $t('options.reset-button') }}
</button>
<button class="btn--action" @click="onSearchButtonConfirm">
{{ $t('options.search-button') }}
</button> </button>
</div> </div>
</div> </div>
@@ -100,9 +107,10 @@ import { DataStatus } from '../../scripts/enums/DataStatus';
import { DriverStatsAPIData } from '../../scripts/interfaces/api/DriverStatsAPIData'; import { DriverStatsAPIData } from '../../scripts/interfaces/api/DriverStatsAPIData';
import { URLs } from '../../scripts/utils/apiURLs'; import { URLs } from '../../scripts/utils/apiURLs';
import { useStore } from '../../store/store'; import { useStore } from '../../store/store';
import { JournalTimetableFilter } from '../../types/Journal/JournalTimetablesTypes';
import ActionButton from '../Global/ActionButton.vue'; import ActionButton from '../Global/ActionButton.vue';
import SelectBox from '../Global/SelectBox.vue'; import SelectBox from '../Global/SelectBox.vue';
import { JournalFilterSection } from '../../scripts/enums/JournalFilterType';
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
export default defineComponent({ export default defineComponent({
components: { SelectBox, ActionButton }, components: { SelectBox, ActionButton },
@@ -116,7 +124,7 @@ export default defineComponent({
}, },
filters: { filters: {
type: Array as PropType<JournalTimetableFilter[]>, type: Array as PropType<JournalFilter[]>,
default: [], default: [],
}, },
@@ -132,13 +140,14 @@ export default defineComponent({
optionsType: { optionsType: {
type: String, type: String,
required: true required: true,
} },
}, },
data() { data() {
return { return {
showOptions: false, showOptions: false,
JournalFilterSection,
driverSuggestions: [] as string[], driverSuggestions: [] as string[],
dispatcherSuggestions: [] as string[], dispatcherSuggestions: [] as string[],
@@ -154,7 +163,8 @@ export default defineComponent({
return { return {
searchersValues: inject('searchersValues') as { [key: string]: string }, searchersValues: inject('searchersValues') as { [key: string]: string },
sorterActive: inject('sorterActive') as { id: string | number; dir: number }, sorterActive: inject('sorterActive') as { id: string | number; dir: number },
journalFilterActive: inject('journalFilterActive') as JournalTimetableFilter, // journalFilterActive: inject('journalFilterActive') as JournalFilter,
filterList: inject('filterList') as JournalFilter[] | undefined,
}; };
}, },
@@ -174,7 +184,8 @@ export default defineComponent({
watch: { watch: {
async driverStatsName(value: string) { async driverStatsName(value: string) {
await this.fetchDriverStats(); await this.fetchDriverStats();
this.store.currentStatsTab = value ? 'driver' : 'daily';
// if (value) this.store.currentStatsTab = 'driver';
}, },
async 'searchersValues.search-driver'(value: string | undefined) { async 'searchersValues.search-driver'(value: string | undefined) {
@@ -249,18 +260,17 @@ 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;
this.$emit('onSearchConfirm'); this.$emit('onSearchConfirm');
}, },
onFilterChange(filter: JournalTimetableFilter) { onFilterChange(filter: JournalFilter) {
this.journalFilterActive = filter; // this.journalFilterActive = filter;
this.filterList?.filter((f) => f.filterSection === filter.filterSection).forEach((f) => (f.isActive = false));
filter.isActive = true;
this.$emit('onSearchConfirm'); this.$emit('onSearchConfirm');
}, },
+26 -20
View File
@@ -1,20 +1,22 @@
<template> <template>
<div class="journal-stats" v-show="!store.isOffline"> <div class="journal-stats" v-if="!store.isOffline">
<div class="tabs"> <div class="tabs">
<button <button
v-for="tab in data.tabs" v-for="tab in data.tabs"
class="btn--filled" class="btn--filled"
:data-selected="tab.name == store.currentStatsTab && areStatsOpen" :data-selected="tab.name == store.currentStatsTab && areStatsOpen"
:data-inactive="tab.inactive" :data-inactive="tab.inactive"
:data-disabled="tab.inactive"
:disabled="tab.inactive"
@click="onTabButtonClick(tab.name)" @click="onTabButtonClick(tab.name)"
> >
{{ $t(tab.titlePath) }} {{ $t(tab.titlePath) }}
</button> </button>
</div> </div>
<div class="stats-tab" v-show="areStatsOpen"> <div class="stats-tab" v-show="areStatsOpen">
<keep-alive> <keep-alive>
<JournalDailyStats v-if="store.currentStatsTab == 'daily'" ref="dailyStatsComp" /> <JournalDailyStats v-if="store.currentStatsTab == 'daily'" @toggleStatsOpen="toggleStatsOpen" />
<JournalDriverStats v-else-if="store.currentStatsTab == 'driver'" /> <JournalDriverStats v-else-if="store.currentStatsTab == 'driver'" />
</keep-alive> </keep-alive>
</div> </div>
@@ -22,22 +24,21 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, KeepAlive, onActivated, onDeactivated, reactive, Ref, ref, watch } from 'vue'; import { computed, KeepAlive, onMounted, reactive, Ref, ref, watch } from 'vue';
import { useStore } from '../../store/store'; import { useStore } from '../../store/store';
import JournalDailyStats from './DailyStats.vue'; import JournalDailyStats from './DailyStats.vue';
import JournalDriverStats from './JournalDriverStats.vue'; import JournalDriverStats from './JournalDriverStats.vue';
import StorageManager from '../../scripts/managers/storageManager';
// Types // Types
type TStatTab = 'daily' | 'driver'; type TStatTab = 'daily' | 'driver';
// Variables // Variables
const store = useStore(); const store = useStore();
const dailyStatsComp: Ref<InstanceType<typeof JournalDailyStats> | null> = ref(null);
const lastDailyStatsOpen = ref(false); const lastDailyStatsOpen = ref(false);
const areStatsOpen = ref(false); const areStatsOpen = ref(false);
const lastClickedTab = ref('daily'); const lastClickedTab: Ref<'daily' | 'driver' | null> = ref(null);
let data = reactive({ let data = reactive({
tabs: [ tabs: [
@@ -57,30 +58,35 @@ let data = reactive({
function onTabButtonClick(tab: TStatTab) { function onTabButtonClick(tab: TStatTab) {
if (lastClickedTab.value == tab || !areStatsOpen.value) areStatsOpen.value = !areStatsOpen.value; if (lastClickedTab.value == tab || !areStatsOpen.value) areStatsOpen.value = !areStatsOpen.value;
if (tab == 'daily') lastDailyStatsOpen.value = areStatsOpen.value; if (tab == 'daily') {
StorageManager.setBooleanValue('dailyStatsOpen', areStatsOpen.value);
lastDailyStatsOpen.value = areStatsOpen.value;
}
store.currentStatsTab = tab; store.currentStatsTab = tab;
lastClickedTab.value = tab; lastClickedTab.value = tab;
if (areStatsOpen.value == false) store.currentStatsTab = null;
} }
onActivated(() => { function toggleStatsOpen(open: boolean) {
dailyStatsComp.value?.startFetchingDailyStats(); areStatsOpen.value = open;
}); }
onDeactivated(() => {
dailyStatsComp.value?.stopFetchingDailyStats();
});
watch( watch(
computed(() => store.driverStatsData), computed(() => store.driverStatsData),
(statsData) => { (statsData) => {
data.tabs[1].inactive = statsData ? false : true; store.currentStatsTab = statsData ? 'driver' : lastClickedTab.value;
areStatsOpen.value = statsData ? true : lastClickedTab.value !== null;
lastClickedTab.value = statsData ? 'driver' : 'daily';
if (statsData) areStatsOpen.value = true;
if (!statsData) areStatsOpen.value = lastDailyStatsOpen.value;
} }
); );
onMounted(() => {
if (StorageManager.getBooleanValue('dailyStatsOpen')) {
areStatsOpen.value = true;
store.currentStatsTab = 'daily';
}
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -4,9 +4,11 @@
<b>{{ $t('scenery.one-way-routes') }}</b> <b>{{ $t('scenery.one-way-routes') }}</b>
<ul class="routes-list"> <ul class="routes-list">
<li v-for="route in station.generalInfo.routes.oneWay"> <li v-for="route in station.generalInfo.routes.oneWay" @click="setActiveShowLength(route.name)">
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"> {{ route.name }}</span> <span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"> {{ route.name }}</span>
<span v-if="route.speed" class="speed">{{ route.speed }}</span> <span v-if="route.speed" class="speed">
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
</span>
<span v-if="route.SBL" class="sbl">SBL</span> <span v-if="route.SBL" class="sbl">SBL</span>
</li> </li>
</ul> </ul>
@@ -16,9 +18,11 @@
<b>{{ $t('scenery.two-way-routes') }}</b> <b>{{ $t('scenery.two-way-routes') }}</b>
<ul class="routes-list"> <ul class="routes-list">
<li v-for="route in station.generalInfo.routes.twoWay"> <li v-for="(route, i) in station.generalInfo.routes.twoWay" @click="setActiveShowLength(route.name)">
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{ route.name }}</span> <span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{ route.name }}</span>
<span v-if="route.speed" class="speed">{{ route.speed }}</span> <span v-if="route.speed" class="speed">
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
</span>
<span v-if="route.SBL" class="sbl">SBL</span> <span v-if="route.SBL" class="sbl">SBL</span>
</li> </li>
</ul> </ul>
@@ -37,6 +41,19 @@ export default defineComponent({
default: {}, default: {},
}, },
}, },
methods: {
setActiveShowLength(name: string) {
if (this.activeShowLength.includes(name)) this.activeShowLength.splice(this.activeShowLength.indexOf(name), 1);
else this.activeShowLength.push(name);
},
},
data() {
return {
activeShowLength: [] as string[],
};
},
}); });
</script> </script>
@@ -66,6 +83,11 @@ ul.routes-list {
li { li {
margin: 0.5em 0.25em; margin: 0.5em 0.25em;
cursor: pointer;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
span { span {
padding: 0.2em 0.25em; padding: 0.2em 0.25em;
@@ -100,7 +122,6 @@ ul.routes-list {
&:only-child { &:only-child {
border-radius: 0.5em; border-radius: 0.5em;
} }
} }
} }
+1 -1
View File
@@ -82,10 +82,10 @@
import { defineComponent, inject, PropType } from 'vue'; import { defineComponent, inject, PropType } from 'vue';
import imageMixin from '../../mixins/imageMixin'; import imageMixin from '../../mixins/imageMixin';
import keyMixin from '../../mixins/keyMixin'; import keyMixin from '../../mixins/keyMixin';
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes';
import ActionButton from '../Global/ActionButton.vue'; import ActionButton from '../Global/ActionButton.vue';
import SelectBox from '../Global/SelectBox.vue'; import SelectBox from '../Global/SelectBox.vue';
import { TrainFilterSection } from '../../scripts/enums/TrainFilterType'; import { TrainFilterSection } from '../../scripts/enums/TrainFilterType';
import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter';
export default defineComponent({ export default defineComponent({
components: { SelectBox, ActionButton }, components: { SelectBox, ActionButton },
@@ -1,28 +1,46 @@
import { JournalFilterType } from "../../scripts/enums/JournalFilterType"; import { JournalFilterSection, JournalFilterType } from '../../scripts/enums/JournalFilterType';
import { JournalTimetableFilter } from "../../types/Journal/JournalTimetablesTypes"; import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
export const journalTimetableFilters: JournalTimetableFilter[] = [ export const journalTimetableFilters: JournalFilter[] = [
{ {
id: JournalFilterType.all, id: JournalFilterType.ALL,
filterSection: 'timetable-status', filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: true, isActive: true,
}, },
{ {
id: JournalFilterType.active, id: JournalFilterType.ACTIVE,
filterSection: 'timetable-status', filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: false, isActive: false,
}, },
{ {
id: JournalFilterType.fulfilled, id: JournalFilterType.FULFILLED,
filterSection: 'timetable-status', filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: false, isActive: false,
}, },
{ {
id: JournalFilterType.abandoned, id: JournalFilterType.ABANDONED,
filterSection: 'timetable-status', filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: false,
},
{
id: JournalFilterType.TWR_SKR,
filterSection: JournalFilterSection.TWRSKR,
isActive: true,
},
{
id: JournalFilterType.TWR,
filterSection: JournalFilterSection.TWRSKR,
isActive: false,
},
{
id: JournalFilterType.SKR,
filterSection: JournalFilterSection.TWRSKR,
isActive: false, isActive: false,
}, },
]; ];
+1 -1
View File
@@ -1,5 +1,5 @@
import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType'; import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType';
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes'; import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter';
export const trainFilters: TrainFilter[] = [ export const trainFilters: TrainFilter[] = [
{ {
+10 -4
View File
@@ -99,19 +99,21 @@
"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-timetables-date": "Timetable date (CEST / GMT+2)", "search-issuedFrom": "Origin scenery name",
"search-dispatchers-date": "Service date (CEST / GMT+2)", "search-timetables-date": "Timetable date (UTC+2 / CEST)",
"search-dispatchers-date": "Service date (UTC+2 / CEST)",
"search-date": "Date (UTC+2 / CEST)",
"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",
@@ -121,6 +123,7 @@
"filter-withComments": "COMMENTS", "filter-withComments": "COMMENTS",
"filter-twr": "HIGH RISK CARGO", "filter-twr": "HIGH RISK CARGO",
"filter-skr": "EXCEEDED GAUGE", "filter-skr": "EXCEEDED GAUGE",
"filter-twr-skr": "ALL TYPES",
"filter-common": "NO WARNINGS", "filter-common": "NO WARNINGS",
"filter-passenger": "PASSENGER", "filter-passenger": "PASSENGER",
"filter-freight": "FREIGHT", "filter-freight": "FREIGHT",
@@ -131,6 +134,9 @@
"filter-reset": "RESET FILTERS", "filter-reset": "RESET FILTERS",
"filter-clear": "CLEAR FILTERS", "filter-clear": "CLEAR FILTERS",
"filter-section-timetable-status": "TIMETABLE STATUS",
"filter-section-twrskr": "WARNINGS",
"filter-all": "ALL ENTRIES", "filter-all": "ALL ENTRIES",
"filter-abandoned": "ABANDONED", "filter-abandoned": "ABANDONED",
"filter-fulfilled": "FULFILLED", "filter-fulfilled": "FULFILLED",
+10 -4
View File
@@ -99,11 +99,13 @@
"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-timetables-date": "Data rozkładu jazdy (czas polski)", "search-issuedFrom": "Sceneria początkowa",
"search-dispatchers-date": "Data służby (czas polski)", "search-timetables-date": "Data rozkładu jazdy (UTC+2 / CEST)",
"search-dispatchers-date": "Data służby (UTC+2 / CEST)",
"search-date": "Data (UTC+2 / CEST)",
"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",
@@ -122,6 +124,7 @@
"filter-noComments": "BEZ UWAG", "filter-noComments": "BEZ UWAG",
"filter-twr": "WYS. RYZYKA", "filter-twr": "WYS. RYZYKA",
"filter-skr": "SKRAJNIA", "filter-skr": "SKRAJNIA",
"filter-twr-skr": "WSZYSTKIE",
"filter-common": "ZWYKŁE", "filter-common": "ZWYKŁE",
"filter-passenger": "PASAŻERSKIE", "filter-passenger": "PASAŻERSKIE",
"filter-freight": "TOWAROWE", "filter-freight": "TOWAROWE",
@@ -132,6 +135,9 @@
"filter-reset": "ZRESETUJ FILTRY", "filter-reset": "ZRESETUJ FILTRY",
"filter-clear": "WYŁĄCZ FILTRY", "filter-clear": "WYŁĄCZ FILTRY",
"filter-section-timetable-status": "STATUS ROZKŁADU JAZDY",
"filter-section-twrskr": "UWAGI",
"filter-all": "WSZYSTKIE", "filter-all": "WSZYSTKIE",
"filter-abandoned": "PORZUCONE", "filter-abandoned": "PORZUCONE",
"filter-fulfilled": "WYPEŁNIONE", "filter-fulfilled": "WYPEŁNIONE",
@@ -1,49 +1,49 @@
import Filter from "../../scripts/interfaces/Filter"; import Filter from "../../interfaces/Filter";
export const filterInitStates: Filter = { export const filterInitStates: Filter = {
default: false, default: false,
notDefault: false, notDefault: false,
real: false, real: false,
fictional: false, fictional: false,
SPK: false, SPK: false,
SCS: false, SCS: false,
SPE: false, SPE: false,
SUP: false, SUP: false,
noSUP: false, noSUP: false,
ręczne: false, ręczne: false,
'ręczne+SPK': false, 'ręczne+SPK': false,
'ręczne+SCS': false, 'ręczne+SCS': false,
mechaniczne: false, mechaniczne: false,
'mechaniczne+SPK': false, 'mechaniczne+SPK': false,
'mechaniczne+SCS': false, 'mechaniczne+SCS': false,
współczesna: false, współczesna: false,
kształtowa: false, kształtowa: false,
historyczna: false, historyczna: false,
mieszana: false, mieszana: false,
SBL: false, SBL: false,
PBL: false, PBL: false,
minLevel: 0, minLevel: 0,
maxLevel: 20, maxLevel: 20,
minOneWayCatenary: 0, minOneWayCatenary: 0,
minOneWay: 0, minOneWay: 0,
minTwoWayCatenary: 0, minTwoWayCatenary: 0,
minTwoWay: 0, minTwoWay: 0,
'include-selected': false, 'include-selected': false,
'no-1track': false, 'no-1track': false,
'no-2track': false, 'no-2track': false,
free: true, free: true,
occupied: false, occupied: false,
ending: false, ending: false,
nonPublic: false, nonPublic: false,
unavailable: true, unavailable: true,
abandoned: true, abandoned: true,
afkStatus: false, afkStatus: false,
endingStatus: false, endingStatus: false,
noSpaceStatus: false, noSpaceStatus: false,
unavailableStatus: false, unavailableStatus: false,
unsignedStatus: false, unsignedStatus: false,
authors: '', authors: '',
onlineFromHours: 0, onlineFromHours: 0,
}; };
+14 -6
View File
@@ -1,6 +1,14 @@
export const enum JournalFilterType { export const enum JournalFilterType {
active = "active", ACTIVE = 'active',
fulfilled = "fulfilled", FULFILLED = 'fulfilled',
abandoned = "abandoned", ABANDONED = 'abandoned',
all = "all" ALL = 'all',
} TWR = 'twr',
SKR = 'skr',
TWR_SKR = 'twr-skr',
}
export enum JournalFilterSection {
TIMETABLE_STATUS = 'timetable-status',
TWRSKR = 'twrskr',
}
@@ -1,4 +1,4 @@
import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType'; import { TrainFilterSection, TrainFilterType } from '../../enums/TrainFilterType'
export interface TrainFilter { export interface TrainFilter {
id: TrainFilterType; id: TrainFilterType;
@@ -0,0 +1,21 @@
import { JournalTimetableSorter } from '../../types/JournalTimetablesTypes';
export interface TimetablesQueryParams {
driverName?: string;
trainNo?: string;
authorName?: string;
timestampFrom?: number;
timestampTo?: number;
issuedFrom?: string;
countFrom?: number;
countLimit?: number;
fulfilled?: number;
terminated?: number;
twr?: number;
skr?: number;
sortBy?: JournalTimetableSorter['id'];
}
+90 -90
View File
@@ -1,90 +1,90 @@
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { DataStatus } from '../../enums/DataStatus'; import { DataStatus } from '../../enums/DataStatus';
import StationAPIData from '../api/StationAPIData'; import StationAPIData from '../api/StationAPIData';
import { TrainAPIData } from '../api/TrainAPIData'; import { TrainAPIData } from '../api/TrainAPIData';
import Station from '../Station'; import Station from '../Station';
import Train from '../Train'; import Train from '../Train';
import { DispatcherStatsAPIData } from '../api/DispatcherStatsAPIData'; import { DispatcherStatsAPIData } from '../api/DispatcherStatsAPIData';
import { DriverStatsAPIData } from '../api/DriverStatsAPIData'; import { DriverStatsAPIData } from '../api/DriverStatsAPIData';
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault'; export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
export interface StoreState { export interface StoreState {
stationList: Station[]; stationList: Station[];
trainList: Train[]; trainList: Train[];
apiData: APIData; apiData: APIData;
lastDispatcherStatuses: { hash: string; statusTimestamp: number; statusID: string }[]; lastDispatcherStatuses: { hash: string; statusTimestamp: number; statusID: string }[];
sceneryData: any[][]; sceneryData: any[][];
region: { id: string; value: string }; region: { id: string; value: string };
trainCount: number; trainCount: number;
stationCount: number; stationCount: number;
webSocket?: Socket; webSocket?: Socket;
isOffline: boolean; isOffline: boolean;
dispatcherStatsName: string; dispatcherStatsName: string;
dispatcherStatsData?: DispatcherStatsAPIData; dispatcherStatsData?: DispatcherStatsAPIData;
driverStatsName: string; driverStatsName: string;
driverStatsData?: DriverStatsAPIData; driverStatsData?: DriverStatsAPIData;
driverStatsStatus: DataStatus; driverStatsStatus: DataStatus;
chosenModalTrainId?: string; chosenModalTrainId?: string;
currentStatsTab: 'daily' | 'driver'; currentStatsTab: 'daily' | 'driver' | null;
dataStatuses: { dataStatuses: {
connection: DataStatus; connection: DataStatus;
sceneries: DataStatus; sceneries: DataStatus;
timetables: DataStatus; timetables: DataStatus;
dispatchers: DataStatus; dispatchers: DataStatus;
trains: DataStatus; trains: DataStatus;
}; };
listenerLaunched: boolean; listenerLaunched: boolean;
blockScroll: boolean; blockScroll: boolean;
} }
export interface APIData { export interface APIData {
stations?: StationAPIData[]; stations?: StationAPIData[];
dispatchers?: string[][]; dispatchers?: string[][];
trains?: TrainAPIData[]; trains?: TrainAPIData[];
connectedSocketCount: number; connectedSocketCount: number;
} }
export interface StationRoutesInfo { export interface StationRoutesInfo {
routeName: string; routeName: string;
isElectric: boolean; isElectric: boolean;
isInternal: boolean; isInternal: boolean;
isRouteSBL: boolean; isRouteSBL: boolean;
routeLength: number; routeLength: number;
routeSpeed: number; routeSpeed: number;
routeTracks: number; routeTracks: number;
} }
export interface StationJSONData { export interface StationJSONData {
name: string; name: string;
abbr: string; abbr: string;
url: string; url: string;
lines: string; lines: string;
project: string; project: string;
projectUrl: string; projectUrl: string;
reqLevel: number; reqLevel: number;
signalType: string; signalType: string;
controlType: string; controlType: string;
SUP: boolean; SUP: boolean;
// routes: string; // routes: string;
routesInfo: StationRoutesInfo[]; routesInfo: StationRoutesInfo[];
checkpoints: string | null; checkpoints: string | null;
authors?: string; authors?: string;
availability: Availability; availability: Availability;
} }
+3 -3
View File
@@ -1,4 +1,4 @@
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes'; import { TrainFilter } from '../interfaces/Trains/TrainFilter';
import { TrainFilterType } from '../enums/TrainFilterType'; import { TrainFilterType } from '../enums/TrainFilterType';
import Train from '../interfaces/Train'; import Train from '../interfaces/Train';
import TrainStop from '../interfaces/TrainStop'; import TrainStop from '../interfaces/TrainStop';
@@ -44,7 +44,7 @@ function filterTrainList(trainList: Train[], searchedTrain: string, searchedDriv
return !train.timetableData?.SKR; return !train.timetableData?.SKR;
case TrainFilterType.common: case TrainFilterType.common:
return train.timetableData?.SKR || train.timetableData?.TWR; return train.timetableData?.SKR || train.timetableData?.TWR;
case TrainFilterType.passenger: case TrainFilterType.passenger:
return !/^[AMRE]\D{2}$/.test(train.timetableData?.category || ''); return !/^[AMRE]\D{2}$/.test(train.timetableData?.category || '');
@@ -81,7 +81,7 @@ function sortTrainList(trainList: Train[], sorterActive: { id: string; dir: numb
if (a.mass > b.mass) return sorterActive.dir; if (a.mass > b.mass) return sorterActive.dir;
return -sorterActive.dir; return -sorterActive.dir;
case 'distance': case 'routeDistance':
if ((a.timetableData?.routeDistance || -1) > (b.timetableData?.routeDistance || -1)) return sorterActive.dir; if ((a.timetableData?.routeDistance || -1) > (b.timetableData?.routeDistance || -1)) return sorterActive.dir;
return -sorterActive.dir; return -sorterActive.dir;
@@ -0,0 +1,25 @@
import { JournalFilterType } from '../../scripts/enums/JournalFilterType';
export type JournalTimetableSearchKey =
| 'search-driver'
| 'search-train'
| 'search-date'
| 'search-dispatcher'
| 'search-issuedFrom';
export type JournalTimetableSorterKey = 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
export type JournalTimetableSearchType = {
[key in JournalTimetableSearchKey]: string;
};
export interface JournalFilter {
id: JournalFilterType;
filterSection: string;
isActive: boolean;
}
export interface JournalTimetableSorter {
id: JournalTimetableSorterKey;
dir: 'asc' | 'desc';
}
@@ -1,156 +1,156 @@
import { HeadIdsTypes } from '../../scripts/data/stationHeaderNames'; import { HeadIdsTypes } from '../data/stationHeaderNames';
import Filter from '../../scripts/interfaces/Filter'; import Filter from '../interfaces/Filter';
import Station from '../../scripts/interfaces/Station'; import Station from '../interfaces/Station';
export const sortStations = (a: Station, b: Station, sorter: { headerName: HeadIdsTypes; dir: number }) => { export const sortStations = (a: Station, b: Station, sorter: { headerName: HeadIdsTypes; dir: number }) => {
let diff = 0; let diff = 0;
switch (sorter.headerName) { switch (sorter.headerName) {
case 'station': case 'station':
return sorter.dir == 1 ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name); return sorter.dir == 1 ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
case 'min-lvl': case 'min-lvl':
diff = (a.generalInfo?.reqLevel || 0) - (b.generalInfo?.reqLevel || 0); diff = (a.generalInfo?.reqLevel || 0) - (b.generalInfo?.reqLevel || 0);
break; break;
case 'status': case 'status':
diff = (a.onlineInfo?.statusTimestamp || 0) - (b.onlineInfo?.statusTimestamp || 0); diff = (a.onlineInfo?.statusTimestamp || 0) - (b.onlineInfo?.statusTimestamp || 0);
break; break;
case 'dispatcher': case 'dispatcher':
if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') > (b.onlineInfo?.dispatcherName.toLowerCase() || '')) if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') > (b.onlineInfo?.dispatcherName.toLowerCase() || ''))
return sorter.dir; return sorter.dir;
if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') < (b.onlineInfo?.dispatcherName.toLowerCase() || '')) if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') < (b.onlineInfo?.dispatcherName.toLowerCase() || ''))
return -sorter.dir; return -sorter.dir;
break; break;
case 'dispatcher-lvl': case 'dispatcher-lvl':
diff = (a.onlineInfo?.dispatcherExp || 0) - (b.onlineInfo?.dispatcherExp || 0); diff = (a.onlineInfo?.dispatcherExp || 0) - (b.onlineInfo?.dispatcherExp || 0);
break; break;
case 'user': case 'user':
diff = (b.onlineInfo ? b.onlineInfo.currentUsers : -1) - (a.onlineInfo ? a.onlineInfo.currentUsers : -1); diff = (b.onlineInfo ? b.onlineInfo.currentUsers : -1) - (a.onlineInfo ? a.onlineInfo.currentUsers : -1);
break; break;
case 'spawn': case 'spawn':
diff = (a.onlineInfo ? a.onlineInfo.spawns.length : -1) - (b.onlineInfo ? b.onlineInfo.spawns.length : -1); diff = (a.onlineInfo ? a.onlineInfo.spawns.length : -1) - (b.onlineInfo ? b.onlineInfo.spawns.length : -1);
break; break;
case 'timetableConfirmed': case 'timetableConfirmed':
diff = diff =
(a.onlineInfo?.scheduledTrains (a.onlineInfo?.scheduledTrains
? a.onlineInfo.scheduledTrains.filter((train) => train.stopInfo.confirmed).length ? a.onlineInfo.scheduledTrains.filter((train) => train.stopInfo.confirmed).length
: -1) - : -1) -
(b.onlineInfo?.scheduledTrains (b.onlineInfo?.scheduledTrains
? b.onlineInfo.scheduledTrains.filter((train) => train.stopInfo.confirmed).length ? b.onlineInfo.scheduledTrains.filter((train) => train.stopInfo.confirmed).length
: -1); : -1);
break; break;
case 'timetableUnconfirmed': case 'timetableUnconfirmed':
diff = diff =
(a.onlineInfo?.scheduledTrains (a.onlineInfo?.scheduledTrains
? a.onlineInfo.scheduledTrains.filter((train) => !train.stopInfo.confirmed).length ? a.onlineInfo.scheduledTrains.filter((train) => !train.stopInfo.confirmed).length
: -1) - : -1) -
(b.onlineInfo?.scheduledTrains (b.onlineInfo?.scheduledTrains
? b.onlineInfo.scheduledTrains.filter((train) => !train.stopInfo.confirmed).length ? b.onlineInfo.scheduledTrains.filter((train) => !train.stopInfo.confirmed).length
: -1); : -1);
break; break;
case 'timetableAll': case 'timetableAll':
diff = diff =
(a.onlineInfo?.scheduledTrains ? a.onlineInfo.scheduledTrains.length : -1) - (a.onlineInfo?.scheduledTrains ? a.onlineInfo.scheduledTrains.length : -1) -
(b.onlineInfo?.scheduledTrains ? b.onlineInfo.scheduledTrains.length : -1); (b.onlineInfo?.scheduledTrains ? b.onlineInfo.scheduledTrains.length : -1);
break; break;
default: default:
break; break;
} }
if (diff != 0) return Math.sign(diff) * sorter.dir; if (diff != 0) return Math.sign(diff) * sorter.dir;
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
}; };
export const filterStations = (station: Station, filters: Filter) => { export const filterStations = (station: Station, filters: Filter) => {
if (!station.onlineInfo && filters['free']) return false; if (!station.onlineInfo && filters['free']) return false;
if (station.onlineInfo) { if (station.onlineInfo) {
const { statusID, statusTimestamp } = station.onlineInfo; const { statusID, statusTimestamp } = station.onlineInfo;
const isEnding = statusID == 'ending' && filters['endingStatus']; const isEnding = statusID == 'ending' && filters['endingStatus'];
const isNotSigned = (statusID == 'not-signed' || statusID == 'unavailable') && filters['unavailableStatus']; const isNotSigned = (statusID == 'not-signed' || statusID == 'unavailable') && filters['unavailableStatus'];
const isAFK = statusID == 'brb' && filters['afkStatus']; const isAFK = statusID == 'brb' && filters['afkStatus'];
const isNoSpace = statusID == 'no-space' && filters['noSpaceStatus']; const isNoSpace = statusID == 'no-space' && filters['noSpaceStatus'];
const isOccupied = station.onlineInfo && filters['occupied']; const isOccupied = station.onlineInfo && filters['occupied'];
const isOnlineInBounds = const isOnlineInBounds =
(filters['onlineFromHours'] < 8 && (filters['onlineFromHours'] < 8 &&
statusTimestamp > 0 && statusTimestamp > 0 &&
statusTimestamp <= Date.now() + filters['onlineFromHours'] * 3600000) || statusTimestamp <= Date.now() + filters['onlineFromHours'] * 3600000) ||
(filters['onlineFromHours'] > 0 && statusTimestamp <= 0) || (filters['onlineFromHours'] > 0 && statusTimestamp <= 0) ||
(filters['onlineFromHours'] == 8 && statusID != 'no-limit'); (filters['onlineFromHours'] == 8 && statusID != 'no-limit');
if (isEnding || isOnlineInBounds || isNotSigned || isAFK || isNoSpace || isOccupied) return false; if (isEnding || isOnlineInBounds || isNotSigned || isAFK || isNoSpace || isOccupied) return false;
} }
if ((station.generalInfo?.availability == 'nonPublic' || !station.generalInfo) && filters['nonPublic']) return false; if ((station.generalInfo?.availability == 'nonPublic' || !station.generalInfo) && filters['nonPublic']) return false;
if (station.generalInfo) { if (station.generalInfo) {
const { routes, availability, controlType, lines, reqLevel, signalType, SUP, authors } = station.generalInfo; const { routes, availability, controlType, lines, reqLevel, signalType, SUP, authors } = station.generalInfo;
if (availability == 'unavailable' && filters['unavailable'] && !station.onlineInfo) return false; if (availability == 'unavailable' && filters['unavailable'] && !station.onlineInfo) return false;
if (availability == 'abandoned' && filters['abandoned'] && !station.onlineInfo) return false; if (availability == 'abandoned' && filters['abandoned'] && !station.onlineInfo) return false;
if (availability == 'default' && filters['default']) return false; if (availability == 'default' && filters['default']) return false;
if ( if (
availability != 'default' && availability != 'default' &&
filters['notDefault'] && filters['notDefault'] &&
!(availability == 'abandoned' || availability == 'unavailable') !(availability == 'abandoned' || availability == 'unavailable')
) )
return false; return false;
if (filters['real'] && lines) return false; if (filters['real'] && lines) return false;
if (filters['fictional'] && !lines) return false; if (filters['fictional'] && !lines) return false;
const otherAvailability = const otherAvailability =
availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned'; availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned';
if (reqLevel + (otherAvailability ? 1 : 0) < filters['minLevel']) return false; if (reqLevel + (otherAvailability ? 1 : 0) < filters['minLevel']) return false;
if (reqLevel + (otherAvailability ? 1 : 0) > filters['maxLevel']) return false; if (reqLevel + (otherAvailability ? 1 : 0) > filters['maxLevel']) return false;
if ( if (
filters['no-1track'] && filters['no-1track'] &&
(routes.oneWayCatenaryRouteNames.length != 0 || routes.oneWayNoCatenaryRouteNames.length != 0) (routes.oneWayCatenaryRouteNames.length != 0 || routes.oneWayNoCatenaryRouteNames.length != 0)
) )
return false; return false;
if ( if (
filters['no-2track'] && filters['no-2track'] &&
(routes.twoWayCatenaryRouteNames.length != 0 || routes.twoWayNoCatenaryRouteNames.length != 0) (routes.twoWayCatenaryRouteNames.length != 0 || routes.twoWayNoCatenaryRouteNames.length != 0)
) )
return false; return false;
if (routes.oneWayCatenaryRouteNames.length < filters['minOneWayCatenary']) return false; if (routes.oneWayCatenaryRouteNames.length < filters['minOneWayCatenary']) return false;
if (routes.oneWayNoCatenaryRouteNames.length < filters['minOneWay']) return false; if (routes.oneWayNoCatenaryRouteNames.length < filters['minOneWay']) return false;
if (routes.twoWayCatenaryRouteNames.length < filters['minTwoWayCatenary']) return false; if (routes.twoWayCatenaryRouteNames.length < filters['minTwoWayCatenary']) return false;
if (routes.twoWayNoCatenaryRouteNames.length < filters['minTwoWay']) return false; if (routes.twoWayNoCatenaryRouteNames.length < filters['minTwoWay']) return false;
if (filters[controlType]) return false; if (filters[controlType]) return false;
if (filters[signalType]) return false; if (filters[signalType]) return false;
if (filters['SUP'] && SUP) return false; if (filters['SUP'] && SUP) return false;
if (filters['noSUP'] && !SUP) return false; if (filters['noSUP'] && !SUP) return false;
if (filters['SBL'] && routes.sblRouteNames.length > 0) return false; if (filters['SBL'] && routes.sblRouteNames.length > 0) return false;
if (filters['PBL'] && routes.sblRouteNames.length == 0) return false; if (filters['PBL'] && routes.sblRouteNames.length == 0) return false;
if ( if (
filters['authors'].length > 3 && filters['authors'].length > 3 &&
!authors?.map((a) => a.toLocaleLowerCase()).includes(filters['authors'].toLocaleLowerCase()) !authors?.map((a) => a.toLocaleLowerCase()).includes(filters['authors'].toLocaleLowerCase())
) )
return false; return false;
} }
return true; return true;
}; };
+9
View File
@@ -0,0 +1,9 @@
import { defineStore } from 'pinia';
export const useJournalFiltersStore = defineStore('journalFiltersStore', {
state: () => ({
timetableFilters: {
},
}),
});
+99 -99
View File
@@ -1,99 +1,99 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import inputData from '../data/options.json'; import inputData from '../data/options.json';
import Station from '../scripts/interfaces/Station'; import Station from '../scripts/interfaces/Station';
import StorageManager from '../scripts/managers/storageManager'; import StorageManager from '../scripts/managers/storageManager';
import { useStore } from './store'; import { useStore } from './store';
import { filterInitStates } from './constants/initFilterStates'; import { filterInitStates } from '../scripts/constants/stores/initFilterStates';
import { filterStations, sortStations } from './utils/filterUtils'; import { filterStations, sortStations } from '../scripts/utils/filterUtils';
import { HeadIdsTypes } from '../scripts/data/stationHeaderNames'; import { HeadIdsTypes } from '../scripts/data/stationHeaderNames';
export const useStationFiltersStore = defineStore('stationFiltersStore', { export const useStationFiltersStore = defineStore('stationFiltersStore', {
state() { state() {
return { return {
inputs: inputData, inputs: inputData,
filters: { ...filterInitStates }, filters: { ...filterInitStates },
sorterActive: { headerName: 'station' as HeadIdsTypes, dir: 1 }, sorterActive: { headerName: 'station' as HeadIdsTypes, dir: 1 },
store: useStore(), store: useStore(),
lastClickedFilterId: '', lastClickedFilterId: '',
}; };
}, },
getters: { getters: {
areFiltersAtDefault(state) { areFiltersAtDefault(state) {
return Object.keys(state.filters).every((f) => state.filters[f] === filterInitStates[f]); return Object.keys(state.filters).every((f) => state.filters[f] === filterInitStates[f]);
}, },
}, },
actions: { actions: {
getFilteredStationList(stationList: Station[], region: string): Station[] { getFilteredStationList(stationList: Station[], region: string): Station[] {
return stationList return stationList
.map((station) => { .map((station) => {
if (station.onlineInfo && station.onlineInfo.region != region) { if (station.onlineInfo && station.onlineInfo.region != region) {
delete station.onlineInfo; delete station.onlineInfo;
} }
return station; return station;
}) })
.filter((station) => filterStations(station, this.filters)) .filter((station) => filterStations(station, this.filters))
.sort((a, b) => sortStations(a, b, this.sorterActive)); .sort((a, b) => sortStations(a, b, this.sorterActive));
}, },
setupFilters() { setupFilters() {
if (!StorageManager.isRegistered('options_saved')) return; if (!StorageManager.isRegistered('options_saved')) return;
this.inputs.options.forEach((option) => { this.inputs.options.forEach((option) => {
if (!StorageManager.isRegistered(option.name)) return; if (!StorageManager.isRegistered(option.name)) return;
const savedValue = StorageManager.getBooleanValue(option.name); const savedValue = StorageManager.getBooleanValue(option.name);
this.filters[option.name] = savedValue; this.filters[option.name] = savedValue;
option.value = !savedValue; option.value = !savedValue;
}); });
this.inputs.sliders.forEach((slider) => { this.inputs.sliders.forEach((slider) => {
if (!StorageManager.isRegistered(slider.name)) return; if (!StorageManager.isRegistered(slider.name)) return;
const savedValue = StorageManager.getNumericValue(slider.name); const savedValue = StorageManager.getNumericValue(slider.name);
this.filters[slider.name] = savedValue; this.filters[slider.name] = savedValue;
slider.value = savedValue; slider.value = savedValue;
}); });
}, },
changeFilterValue(filter: { name: string; value: any }) { changeFilterValue(filter: { name: string; value: any }) {
this.filters[filter.name] = filter.value; this.filters[filter.name] = filter.value;
if (StorageManager.isRegistered('options_saved')) StorageManager.setValue(filter.name, filter.value); if (StorageManager.isRegistered('options_saved')) StorageManager.setValue(filter.name, filter.value);
}, },
resetFilters() { resetFilters() {
this.filters = { ...filterInitStates }; this.filters = { ...filterInitStates };
this.inputs.options.forEach((option) => { this.inputs.options.forEach((option) => {
option.value = option.defaultValue; option.value = option.defaultValue;
StorageManager.setBooleanValue(option.name, !option.defaultValue); StorageManager.setBooleanValue(option.name, !option.defaultValue);
}); });
this.inputs.sliders.forEach((slider) => { this.inputs.sliders.forEach((slider) => {
slider.value = slider.defaultValue; slider.value = slider.defaultValue;
StorageManager.setNumericValue(slider.name, slider.defaultValue); StorageManager.setNumericValue(slider.name, slider.defaultValue);
}); });
}, },
resetSectionOptions(section: string) { resetSectionOptions(section: string) {
this.inputs.options.forEach((option) => { this.inputs.options.forEach((option) => {
if (option.section != section) return; if (option.section != section) return;
option.value = option.defaultValue; option.value = option.defaultValue;
this.filters[option.id] = !option.defaultValue; this.filters[option.id] = !option.defaultValue;
StorageManager.setBooleanValue(option.name, !option.defaultValue); StorageManager.setBooleanValue(option.name, !option.defaultValue);
}); });
}, },
changeSorter(headerName: HeadIdsTypes) { changeSorter(headerName: HeadIdsTypes) {
if (headerName == this.sorterActive.headerName) this.sorterActive.dir = -1 * this.sorterActive.dir; if (headerName == this.sorterActive.headerName) this.sorterActive.dir = -1 * this.sorterActive.dir;
else this.sorterActive.dir = 1; else this.sorterActive.dir = 1;
this.sorterActive.headerName = headerName; this.sorterActive.headerName = headerName;
}, },
}, },
}); });
+1 -1
View File
@@ -54,7 +54,7 @@ export const useStore = defineStore('store', {
trains: DataStatus.Loading, trains: DataStatus.Loading,
}, },
currentStatsTab: 'daily', currentStatsTab: null,
blockScroll: false, blockScroll: false,
listenerLaunched: false, listenerLaunched: false,
+21 -18
View File
@@ -16,7 +16,7 @@
height: 7px; height: 7px;
background-color: lightgreen; background-color: lightgreen;
border-radius: 50%; border-radius: 50%;
margin-left: 10px; margin-left: 10px;
} }
@@ -82,12 +82,16 @@ h1.option-title {
padding: 0.25em 0.25em 0 0; padding: 0.25em 0.25em 0 0;
} }
.options_filter-sections section {
margin: 0.5em 0;
}
.options_filters { .options_filters {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5em; gap: 0.5em;
margin: 0.5em 0 0 0; margin: 0.25em 0;
} }
.sort-option, .sort-option,
@@ -118,17 +122,6 @@ h1.option-title {
margin: 0.5em auto; margin: 0.5em auto;
} }
.search_actions {
display: flex;
gap: 0.5em;
margin: 1em 0;
width: 100%;
button {
width: 100%;
}
}
.search-box { .search-box {
.search-exit { .search-exit {
position: absolute; position: absolute;
@@ -139,6 +132,17 @@ h1.option-title {
} }
} }
.options_actions {
display: flex;
gap: 0.5em;
width: 100%;
margin-top: 1em;
button {
width: 100%;
}
}
@include smallScreen() { @include smallScreen() {
h1 { h1 {
text-align: center; text-align: center;
@@ -155,13 +159,12 @@ h1.option-title {
max-width: 100%; max-width: 100%;
} }
.filter-option,
.sort-option {
margin: 0.25em 0.25em;
}
.options_filters, .options_filters,
.options_sorters { .options_sorters {
justify-content: center; justify-content: center;
} }
.filter-section {
text-align: center;
}
} }
@@ -1,18 +0,0 @@
import { JournalFilterType } from '../../scripts/enums/JournalFilterType';
export type JournalTimetableSearchKey = 'search-driver' | 'search-train' | 'search-date' | 'search-dispatcher';
export type JournalTimetableSearchType = {
[key in JournalTimetableSearchKey]: string;
};
export interface JournalTimetableFilter {
id: JournalFilterType;
filterSection: string;
isActive: boolean;
}
export interface JournalTimetableSorter {
id: 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
dir: -1 | 1;
}
+290 -290
View File
@@ -1,290 +1,290 @@
<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="['timestampFrom', 'duration']" :sorter-option-ids="['timestampFrom', 'duration']"
:data-status="dataStatus" :data-status="dataStatus"
:current-options-active="currentOptionsActive" :current-options-active="currentOptionsActive"
optionsType="dispatchers" optionsType="dispatchers"
/> />
<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 class="journal_warning" v-else-if="historyList.length == 0"> <div class="journal_warning" v-else-if="historyList.length == 0">
{{ $t('app.no-result') }} {{ $t('app.no-result') }}
</div> </div>
<div v-else> <div v-else>
<JournalDispatchersList :dispatcherHistory="computedHistoryList" /> <JournalDispatchersList :dispatcherHistory="computedHistoryList" />
<button <button
class="btn btn--option btn--load-data" class="btn btn--option btn--load-data"
v-if="!scrollNoMoreData && scrollDataLoaded && computedHistoryList.length > 15" v-if="!scrollNoMoreData && scrollDataLoaded && computedHistoryList.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"> <div class="journal_warning" v-if="scrollNoMoreData">
{{ $t('journal.no-further-data') }} {{ $t('journal.no-further-data') }}
</div> </div>
<div class="journal_warning" v-else-if="!scrollDataLoaded"> <div class="journal_warning" v-else-if="!scrollDataLoaded">
{{ $t('journal.loading-further-data') }} {{ $t('journal.loading-further-data') }}
</div> </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 ActionButton from '../components/Global/ActionButton.vue'; import ActionButton from '../components/Global/ActionButton.vue';
import JournalOptions from '../components/JournalView/JournalOptions.vue'; import JournalOptions from '../components/JournalView/JournalOptions.vue';
import DispatcherStats from '../components/JournalView/DispatcherStats.vue'; import DispatcherStats from '../components/JournalView/DispatcherStats.vue';
import SearchBox from '../components/Global/SearchBox.vue'; import SearchBox from '../components/Global/SearchBox.vue';
import Loading from '../components/Global/Loading.vue'; import Loading from '../components/Global/Loading.vue';
import { URLs } from '../scripts/utils/apiURLs'; import { URLs } from '../scripts/utils/apiURLs';
import { DataStatus } from '../scripts/enums/DataStatus'; import { DataStatus } from '../scripts/enums/DataStatus';
import { useStore } from '../store/store'; import { useStore } from '../store/store';
import JournalDispatchersList from '../components/JournalView/JournalDispatchersList.vue'; import JournalDispatchersList from '../components/JournalView/JournalDispatchersList.vue';
import { JournalDispatcherSearcher, JournalDispatcherSorter } from '../types/Journal/JournalDispatcherTypes'; import { JournalDispatcherSearcher, JournalDispatcherSorter } from '../scripts/types/JournalDispatcherTypes';
import { DispatcherHistory } from '../scripts/interfaces/api/DispatchersAPIData'; import { DispatcherHistory } from '../scripts/interfaces/api/DispatchersAPIData';
import JournalHeader from '../components/JournalView/JournalHeader.vue'; import JournalHeader from '../components/JournalView/JournalHeader.vue';
import { LocationQuery } from 'vue-router'; import { LocationQuery } from 'vue-router';
const DISPATCHERS_API_URL = `${URLs.stacjownikAPI}/api/getDispatchers`; const DISPATCHERS_API_URL = `${URLs.stacjownikAPI}/api/getDispatchers`;
export default defineComponent({ export default defineComponent({
components: { components: {
SearchBox, SearchBox,
ActionButton, ActionButton,
JournalOptions, JournalOptions,
DispatcherStats, DispatcherStats,
Loading, Loading,
JournalDispatchersList, JournalDispatchersList,
JournalHeader, JournalHeader,
}, },
name: 'JournalDispatchers', name: 'JournalDispatchers',
props: { props: {
sceneryName: { sceneryName: {
type: String, type: String,
required: false, required: false,
}, },
dispatcherName: { dispatcherName: {
type: String, type: String,
required: false, required: false,
}, },
}, },
data: () => ({ data: () => ({
currentQuery: '', currentQuery: '',
currentQueryArray: [] as string[], currentQueryArray: [] as string[],
scrollDataLoaded: true, scrollDataLoaded: true,
scrollNoMoreData: false, scrollNoMoreData: false,
showReturnButton: false, showReturnButton: false,
statsCardOpen: false, statsCardOpen: false,
currentOptionsActive: false, currentOptionsActive: false,
dataStatus: DataStatus.Loading, dataStatus: DataStatus.Loading,
DataStatus, DataStatus,
historyList: [] as DispatcherHistory[], historyList: [] as DispatcherHistory[],
}), }),
setup() { setup() {
const sorterActive: JournalDispatcherSorter = reactive({ id: 'timestampFrom', dir: -1 }); const sorterActive: JournalDispatcherSorter = reactive({ id: 'timestampFrom', dir: -1 });
const journalFilterActive = ref({}); const journalFilterActive = ref({});
const searchersValues = reactive({ const searchersValues = reactive({
'search-dispatcher': '', 'search-dispatcher': '',
'search-station': '', 'search-station': '',
'search-date': '', 'search-date': '',
} as JournalDispatcherSearcher); } as JournalDispatcherSearcher);
const countFromIndex = ref(0); const countFromIndex = ref(0);
const countLimit = 15; const countLimit = 15;
provide('sorterActive', sorterActive); provide('sorterActive', sorterActive);
provide('journalFilterActive', journalFilterActive); provide('journalFilterActive', journalFilterActive);
provide('searchersValues', searchersValues); provide('searchersValues', searchersValues);
provide('filterList', reactive([]));
const scrollElement: Ref<HTMLElement | null> = ref(null);
const scrollElement: Ref<HTMLElement | null> = ref(null);
return {
store: useStore(), return {
store: useStore(),
sorterActive,
searchersValues, sorterActive,
searchersValues,
countFromIndex,
countLimit, countFromIndex,
countLimit,
scrollElement,
maxCount: ref(15), scrollElement,
}; maxCount: ref(15),
}, };
},
watch: {
currentQueryArray(q: string[]) { watch: {
this.currentOptionsActive = currentQueryArray(q: string[]) {
q.length > 2 || q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1] != 'timestampFrom'); this.currentOptionsActive =
}, q.length > 2 || q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1] != 'timestampFrom');
}, },
},
computed: {
computedHistoryList() { computed: {
return this.historyList.filter( computedHistoryList() {
(doc) => doc.isOnline || (doc.currentDuration && doc.currentDuration > 10 * 60000) return this.historyList.filter(
); (doc) => doc.isOnline || (doc.currentDuration && doc.currentDuration > 10 * 60000)
}, );
}, },
},
beforeRouteUpdate(to, _) {
this.handleQueries(to.query); beforeRouteUpdate(to, _) {
this.fetchHistoryData(); this.handleQueries(to.query);
}, this.fetchHistoryData();
},
activated() {
this.handleQueries(this.$route.query); activated() {
this.fetchHistoryData(); this.handleQueries(this.$route.query);
}, this.fetchHistoryData();
},
methods: {
handleScroll(e: Event) { methods: {
const listElement = e.target as HTMLElement; handleScroll(e: Event) {
const scrollTop = listElement.scrollTop; const listElement = e.target as HTMLElement;
const elementHeight = listElement.scrollHeight - listElement.offsetHeight; const scrollTop = listElement.scrollTop;
const elementHeight = listElement.scrollHeight - listElement.offsetHeight;
if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return;
if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return;
if (scrollTop > elementHeight * 0.85) this.addHistoryData();
}, if (scrollTop > elementHeight * 0.85) this.addHistoryData();
},
handleQueries(query: LocationQuery) {
if ('sceneryName' in query) this.searchersValues['search-station'] = `${query.sceneryName}`; handleQueries(query: LocationQuery) {
if ('dispatcherName' in query) this.searchersValues['search-dispatcher'] = `${query.dispatcherName}`; if ('sceneryName' in query) this.searchersValues['search-station'] = `${query.sceneryName}`;
}, if ('dispatcherName' in query) this.searchersValues['search-dispatcher'] = `${query.dispatcherName}`;
},
setSearchers(date: string, station: string, dispatcher: string) {
this.searchersValues['search-date'] = date; setSearchers(date: string, station: string, dispatcher: string) {
this.searchersValues['search-station'] = station; this.searchersValues['search-date'] = date;
this.searchersValues['search-dispatcher'] = dispatcher; this.searchersValues['search-station'] = station;
}, this.searchersValues['search-dispatcher'] = dispatcher;
},
resetOptions() {
this.setSearchers('', '', ''); resetOptions() {
this.sorterActive.id = 'timestampFrom'; this.setSearchers('', '', '');
this.sorterActive.id = 'timestampFrom';
this.fetchHistoryData();
}, this.fetchHistoryData();
},
async addHistoryData() {
this.scrollDataLoaded = false; async addHistoryData() {
this.scrollDataLoaded = false;
const countFrom = this.historyList.length;
const countFrom = this.historyList.length;
const responseData: DispatcherHistory[] = await (
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}&countFrom=${countFrom}`) const responseData: DispatcherHistory[] = await (
).data; await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}&countFrom=${countFrom}`)
).data;
if (!responseData) return;
if (!responseData) return;
if (responseData.length == 0) {
this.scrollNoMoreData = true; if (responseData.length == 0) {
return; this.scrollNoMoreData = true;
} return;
}
this.historyList.push(...responseData);
this.scrollDataLoaded = true; this.historyList.push(...responseData);
}, this.scrollDataLoaded = true;
},
async fetchHistoryData() {
const queries: string[] = []; async fetchHistoryData() {
const queries: string[] = [];
const dispatcher = this.searchersValues['search-dispatcher'].trim();
const station = this.searchersValues['search-station'].trim(); const dispatcher = this.searchersValues['search-dispatcher'].trim();
const dateString = this.searchersValues['search-date'].trim(); const station = this.searchersValues['search-station'].trim();
const dateString = this.searchersValues['search-date'].trim();
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 (dispatcher) queries.push(`dispatcherName=${dispatcher}`);
if (station) queries.push(`stationName=${station}`); if (dispatcher) queries.push(`dispatcherName=${dispatcher}`);
if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`); 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'); // Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
else if (this.sorterActive.id == 'duration') queries.push('sortBy=currentDuration'); if (this.sorterActive.id == 'timestampFrom') queries.push('sortBy=timestampFrom');
else queries.push('sortBy=timestampFrom'); else if (this.sorterActive.id == 'duration') queries.push('sortBy=currentDuration');
else queries.push('sortBy=timestampFrom');
queries.push('countLimit=30');
queries.push('countLimit=30');
if (this.currentQuery != queries.join('&')) this.dataStatus = DataStatus.Loading;
if (this.currentQuery != queries.join('&')) this.dataStatus = DataStatus.Loading;
this.currentQuery = queries.join('&');
this.currentQueryArray = queries; this.currentQuery = queries.join('&');
this.currentQueryArray = queries;
try {
const responseData: DispatcherHistory[] = await ( try {
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}`) const responseData: DispatcherHistory[] = await (
).data; await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}`)
).data;
if (!responseData) {
this.dataStatus = DataStatus.Error; if (!responseData) {
return; this.dataStatus = DataStatus.Error;
} return;
}
if (!responseData) return;
if (!responseData) return;
// Response data exists
this.historyList = responseData; // Response data exists
this.historyList = responseData;
// Stats display
this.store.dispatcherStatsName = // Stats display
this.historyList.length > 0 && this.searchersValues['search-dispatcher'].trim() this.store.dispatcherStatsName =
? this.historyList[0].dispatcherName this.historyList.length > 0 && this.searchersValues['search-dispatcher'].trim()
: ''; ? this.historyList[0].dispatcherName
: '';
this.dataStatus = DataStatus.Loaded;
} catch (error) { this.dataStatus = DataStatus.Loaded;
this.dataStatus = DataStatus.Error; } catch (error) {
} this.dataStatus = DataStatus.Error;
}
this.scrollNoMoreData = false;
this.scrollDataLoaded = true; this.scrollNoMoreData = false;
}, this.scrollDataLoaded = true;
}, },
}); },
</script> });
</script>
<style lang="scss" scoped>
@import '../styles/JournalSection.scss'; <style lang="scss" scoped>
</style> @import '../styles/JournalSection.scss';
</style>
+344 -305
View File
@@ -1,305 +1,344 @@
<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 imageMixin from '../mixins/imageMixin';
import Loading from '../components/Global/Loading.vue'; import dateMixin from '../mixins/dateMixin';
import { JournalTimetableSorter } from '../types/Journal/JournalTimetablesTypes'; import routerMixin from '../mixins/routerMixin';
import dateMixin from '../mixins/dateMixin'; import modalTrainMixin from '../mixins/modalTrainMixin';
import routerMixin from '../mixins/routerMixin';
import { DataStatus } from '../scripts/enums/DataStatus'; import DriverStats from '../components/JournalView/JournalDriverStats.vue';
import { JournalFilterType } from '../scripts/enums/JournalFilterType'; import JournalOptions from '../components/JournalView/JournalOptions.vue';
import { TimetableHistory } from '../scripts/interfaces/api/TimetablesAPIData'; import JournalStats from '../components/JournalView/JournalStats.vue';
import { URLs } from '../scripts/utils/apiURLs'; import JournalHeader from '../components/JournalView/JournalHeader.vue';
import { useStore } from '../store/store'; import JournalTimetablesList from '../components/JournalView/JournalTimetablesList.vue';
import JournalOptions from '../components/JournalView/JournalOptions.vue'; import Loading from '../components/Global/Loading.vue';
import { JournalTimetableSearchType } from '../types/Journal/JournalTimetablesTypes';
import modalTrainMixin from '../mixins/modalTrainMixin'; import { DataStatus } from '../scripts/enums/DataStatus';
import imageMixin from '../mixins/imageMixin'; import { TimetableHistory } from '../scripts/interfaces/api/TimetablesAPIData';
import JournalTimetablesList from '../components/JournalView/JournalTimetablesList.vue'; import { URLs } from '../scripts/utils/apiURLs';
import { journalTimetableFilters } from '../constants/Journal/JournalTimetablesConsts'; import { useStore } from '../store/store';
import JournalStats from '../components/JournalView/JournalStats.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`; import {
JournalFilter,
export default defineComponent({ JournalTimetableSearchType,
components: { DriverStats, Loading, JournalOptions, JournalTimetablesList, JournalStats, JournalHeader }, JournalTimetableSorter,
mixins: [dateMixin, routerMixin, modalTrainMixin, imageMixin], } from '../scripts/types/JournalTimetablesTypes';
import { journalTimetableFilters } from '../constants/Journal/JournalTimetablesConsts';
name: 'JournalTimetables',
const TIMETABLES_API_URL = `${URLs.stacjownikAPI}/api/getTimetables`;
props: {
timetableId: { export default defineComponent({
type: String, components: { DriverStats, Loading, JournalOptions, JournalTimetablesList, JournalStats, JournalHeader },
}, mixins: [dateMixin, routerMixin, modalTrainMixin, imageMixin],
},
name: 'JournalTimetables',
data: () => ({
currentQuery: '', props: {
currentQueryArray: [] as string[], timetableId: {
type: String,
scrollDataLoaded: true, },
scrollNoMoreData: false, },
showReturnButton: false, data: () => ({
statsCardOpen: false, currentQueryParams: {} as TimetablesQueryParams,
currentOptionsActive: false,
scrollDataLoaded: true,
timetableHistory: [] as TimetableHistory[], scrollNoMoreData: false,
journalTimetableFilters,
showReturnButton: false,
dataStatus: DataStatus.Loading, statsCardOpen: false,
dataErrorMessage: '', currentOptionsActive: false,
DataStatus, timetableHistory: [] as TimetableHistory[],
}), journalTimetableFilters,
setup() { dataStatus: DataStatus.Loading,
const sorterActive: JournalTimetableSorter = reactive({ id: 'timetableId', dir: 1 }); dataErrorMessage: '',
const journalFilterActive = ref(journalTimetableFilters[0]);
DataStatus,
const searchersValues = reactive({ }),
'search-train': '',
'search-driver': '', setup() {
'search-dispatcher': '', const sorterActive: JournalTimetableSorter = reactive({ id: 'timetableId', dir: 'desc' });
'search-date': '', // const journalFilterActive = ref(journalTimetableFilters[0]);
} as JournalTimetableSearchType); const initFilters: readonly JournalFilter[] = JSON.parse(JSON.stringify(journalTimetableFilters));
const filterList: JournalFilter[] = reactive(JSON.parse(JSON.stringify(initFilters)));
const countFromIndex = ref(0);
const countLimit = 15; const searchersValues = reactive({
'search-train': '',
provide('searchersValues', searchersValues); 'search-driver': '',
provide('sorterActive', sorterActive); 'search-dispatcher': '',
provide('journalFilterActive', journalFilterActive); 'search-issuedFrom': '',
'search-date': '',
const scrollElement: Ref<HTMLElement | null> = ref(null); } as JournalTimetableSearchType);
return { const countFromIndex = ref(0);
sorterActive, const countLimit = 15;
journalFilterActive,
searchersValues, provide('searchersValues', searchersValues);
provide('sorterActive', sorterActive);
countFromIndex, provide('filterList', filterList);
countLimit,
const scrollElement: Ref<HTMLElement | null> = ref(null);
scrollElement,
return {
store: useStore(), sorterActive,
}; searchersValues,
}, filterList,
initFilters,
watch: {
currentQueryArray(q: string[]) { countFromIndex,
this.currentOptionsActive = q.length >= 2 || q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1]); countLimit,
},
}, scrollElement,
// Handle route updates for route-links store: useStore(),
beforeRouteUpdate(to, _) { };
this.handleQueries(to.query); },
this.fetchHistoryData();
}, watch: {
currentQueryParams(q: TimetablesQueryParams) {
activated() { this.currentOptionsActive = Object.values(q).some((v) => v !== undefined);
this.handleQueries(this.$route.query); },
this.fetchHistoryData(); },
},
// Handle route updates for route-links
beforeRouteUpdate(to, _) {
methods: { this.handleQueries(to.query);
handleScroll(e: Event) { this.fetchHistoryData();
const listElement = e.target as HTMLElement; },
const scrollTop = listElement.scrollTop;
const elementHeight = listElement.scrollHeight - listElement.offsetHeight; activated() {
this.handleQueries(this.$route.query);
if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return; this.fetchHistoryData();
},
if (scrollTop > elementHeight * 0.85) this.addHistoryData();
}, methods: {
handleScroll(e: Event) {
handleQueries(query: LocationQuery) { const listElement = e.target as HTMLElement;
if ('timetableId' in query) this.searchersValues['search-train'] = `#${query.timetableId}`; const scrollTop = listElement.scrollTop;
}, const elementHeight = listElement.scrollHeight - listElement.offsetHeight;
setSearchers(date: string, driver: string, train: string, dispatcher: string) { if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return;
this.searchersValues['search-date'] = date;
this.searchersValues['search-driver'] = driver; if (scrollTop > elementHeight * 0.85) this.addHistoryData();
this.searchersValues['search-train'] = train; },
this.searchersValues['search-dispatcher'] = dispatcher;
}, handleQueries(query: LocationQuery) {
if ('timetableId' in query) this.searchersValues['search-train'] = `#${query.timetableId}`;
resetOptions() { },
this.setSearchers('', '', '', '');
setSearchers(date: string, driver: string, train: string, dispatcher: string, issuedFrom: string) {
this.journalFilterActive = this.journalTimetableFilters[0]; this.searchersValues['search-date'] = date;
this.sorterActive.id = 'timetableId'; this.searchersValues['search-driver'] = driver;
this.searchersValues['search-train'] = train;
this.fetchHistoryData(); this.searchersValues['search-dispatcher'] = dispatcher;
}, this.searchersValues['search-issuedFrom'] = issuedFrom;
},
async addHistoryData() {
this.scrollDataLoaded = false; resetOptions() {
this.setSearchers('', '', '', '', '');
const countFrom = this.timetableHistory.length;
this.sorterActive.id = 'timetableId';
const responseData: TimetableHistory[] = await (
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}&countFrom=${countFrom}`) this.filterList.forEach(
).data; (f) => (f.isActive = this.initFilters.find((initFilter) => initFilter.id == f.id)?.isActive || false)
);
if (!responseData) return;
this.fetchHistoryData();
if (responseData.length == 0) { },
this.scrollNoMoreData = true;
return; async addHistoryData() {
} this.scrollDataLoaded = false;
this.timetableHistory.push(...responseData); this.currentQueryParams['countFrom'] = this.timetableHistory.length;
this.scrollDataLoaded = true;
}, const responseData: TimetableHistory[] = await (
await axios.get(`${TIMETABLES_API_URL}`, {
async fetchHistoryData() { params: { ...this.currentQueryParams },
// if(this.dataStatus == DataStatus.Loading) return; })
).data;
const queries: string[] = [];
if (!responseData) return;
const driverName = this.searchersValues['search-driver'].trim();
const trainNo = this.searchersValues['search-train'].trim(); if (responseData.length == 0) {
const authorName = this.searchersValues['search-dispatcher'].trim(); this.scrollNoMoreData = true;
const dateString = this.searchersValues['search-date'].trim(); return;
}
const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined; this.timetableHistory.push(...responseData);
this.scrollDataLoaded = true;
if (driverName) queries.push(`driverName=${driverName}`); },
if (trainNo)
queries.push(trainNo.startsWith('#') ? `timetableId=${trainNo.replace('#', '')}` : `trainNo=${trainNo}`); async fetchHistoryData() {
if (authorName) queries.push(`authorName=${authorName}`); const driverName = this.searchersValues['search-driver'].trim() || undefined;
if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`); const trainNo = this.searchersValues['search-train'].trim() || undefined;
const authorName = this.searchersValues['search-dispatcher'].trim() || undefined;
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance']; const dateString = this.searchersValues['search-date'].trim() || undefined;
if (this.sorterActive.id == 'distance') queries.push('sortBy=routeDistance'); const issuedFrom = this.searchersValues['search-issuedFrom'].trim() || undefined;
else if (this.sorterActive.id == 'total-stops') queries.push('sortBy=allStopsCount');
else if (this.sorterActive.id == 'beginDate') queries.push('sortBy=beginDate'); const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
// else queries.push('sortBy=timetableId'); const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
queries.push('countLimit=15'); const queryParams: TimetablesQueryParams = {};
switch (this.journalFilterActive.id) { this.filterList
case JournalFilterType.abandoned: .filter((f) => f.isActive)
queries.push('fulfilled=0', 'terminated=1'); .forEach((f) => {
break; switch (f.id) {
case JournalFilterType.ABANDONED:
case JournalFilterType.active: queryParams['fulfilled'] = 0;
queries.push('terminated=0'); queryParams['terminated'] = 1;
break; break;
case JournalFilterType.fulfilled: case JournalFilterType.ACTIVE:
queries.push('fulfilled=1'); queryParams['fulfilled'] = undefined;
break; queryParams['terminated'] = 0;
break;
default:
break; case JournalFilterType.FULFILLED:
} queryParams['terminated'] = undefined;
queryParams['fulfilled'] = 1;
if (this.currentQuery != queries.join('&')) this.dataStatus = DataStatus.Loading; break;
this.currentQuery = queries.join('&'); case JournalFilterType.ALL:
this.currentQueryArray = queries; queryParams['terminated'] = undefined;
queryParams['fulfilled'] = undefined;
try { break;
const responseData: TimetableHistory[] = await (
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}`) case JournalFilterType.TWR_SKR:
).data; queryParams['twr'] = undefined;
queryParams['skr'] = undefined;
if (!responseData) { break;
this.dataStatus = DataStatus.Error;
this.dataErrorMessage = 'Brak danych!'; case JournalFilterType.TWR:
return; queryParams['twr'] = 1;
} queryParams['skr'] = undefined;
break;
if (!responseData) return;
case JournalFilterType.SKR:
// Response data exists queryParams['twr'] = undefined;
this.timetableHistory = responseData; queryParams['skr'] = 1;
break;
// Stats display
this.store.driverStatsName = default:
this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim() break;
? this.timetableHistory[0].driverName }
: ''; });
this.dataStatus = DataStatus.Loaded; queryParams['driverName'] = driverName;
} catch (error) { queryParams['trainNo'] = trainNo;
this.dataStatus = DataStatus.Error;
this.dataErrorMessage = 'Ups! Coś poszło nie tak!'; queryParams['countFrom'] = undefined;
} queryParams['countLimit'] = undefined;
this.scrollNoMoreData = false; queryParams['authorName'] = authorName;
this.scrollDataLoaded = true; queryParams['timestampFrom'] = timestampFrom;
}, queryParams['timestampTo'] = timestampTo;
}, queryParams['issuedFrom'] = issuedFrom;
}); queryParams['sortBy'] = this.sorterActive.id != 'timetableId' ? this.sorterActive.id : undefined;
</script>
if (JSON.stringify(this.currentQueryParams) != JSON.stringify(queryParams)) this.dataStatus = DataStatus.Loading;
<style lang="scss" scoped>
@import '../styles/JournalSection.scss'; this.currentQueryParams = queryParams;
</style>
try {
const responseData: TimetableHistory[] = await (
await axios.get(`${TIMETABLES_API_URL}`, {
params: this.currentQueryParams,
})
).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!';
}
this.scrollNoMoreData = false;
this.scrollDataLoaded = true;
},
},
});
</script>
<style lang="scss" scoped>
@import '../styles/JournalSection.scss';
</style>
+1 -1
View File
@@ -1,4 +1,4 @@
<template> <template>
<div class="scenery-view"> <div class="scenery-view">
<div class="scenery-offline" v-if="!stationInfo && isComponentVisible && store.dataStatuses.sceneries == 2"> <div class="scenery-offline" v-if="!stationInfo && isComponentVisible && store.dataStatuses.sceneries == 2">
<div>{{ $t('scenery.no-scenery') }}</div> <div>{{ $t('scenery.no-scenery') }}</div>
+3 -3
View File
@@ -2,7 +2,7 @@
<section class="trains-view"> <section class="trains-view">
<div class="trains_wrapper"> <div class="trains_wrapper">
<TrainOptions <TrainOptions
:sorter-option-ids="['distance', 'id', 'progress', 'delay', 'mass', 'speed', 'length']" :sorter-option-ids="['routeDistance', 'id', 'progress', 'delay', 'mass', 'speed', 'length']"
:current-options-active="currentOptionsActive" :current-options-active="currentOptionsActive"
/> />
@@ -21,7 +21,7 @@ import modalTrainMixin from '../mixins/modalTrainMixin';
import Train from '../scripts/interfaces/Train'; import Train from '../scripts/interfaces/Train';
import { filteredTrainList } from '../scripts/managers/trainFilterManager'; import { filteredTrainList } from '../scripts/managers/trainFilterManager';
import { useStore } from '../store/store'; import { useStore } from '../store/store';
import { TrainFilter } from '../types/Trains/TrainOptionsTypes'; import { TrainFilter } from '../scripts/interfaces/Trains/TrainFilter';
export default defineComponent({ export default defineComponent({
components: { components: {
@@ -57,7 +57,7 @@ export default defineComponent({
const store = useStore(); const store = useStore();
const initTrainFilters = [...trainFilters.map((f) => ({ ...f }))]; const initTrainFilters = [...trainFilters.map((f) => ({ ...f }))];
const sorterActive = reactive({ id: 'distance', dir: -1 }); const sorterActive = reactive({ id: 'routeDistance', dir: -1 });
const filterList = reactive([...trainFilters]) as TrainFilter[]; const filterList = reactive([...trainFilters]) as TrainFilter[];
const currentOptionsActive = ref(false); const currentOptionsActive = ref(false);