mirror of
https://github.com/Spythere/stacjownik.git
synced 2026-05-03 13:28:11 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f28600a7fa | |||
| d59ead87e6 | |||
| 34d91bc800 | |||
| cf9991d8a0 | |||
| 4ffb79d62b | |||
| d9f5edb4fe | |||
| 1b2112430a | |||
| 0a972a23ef | |||
| 6d52724d06 | |||
| 99415c35d3 | |||
| c3f687d439 | |||
| 266edfd6e6 | |||
| d32d5ad91b | |||
| c3481470cb | |||
| 57e88b9abc | |||
| 44ebf53798 | |||
| 145dc72b6b | |||
| b7f3761940 | |||
| ea7c49dfb3 | |||
| 5d6785813a | |||
| a0054aed14 | |||
| 471e6f5216 | |||
| a617eef00e | |||
| 38e700ecd6 |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "stacjownik",
|
"name": "stacjownik",
|
||||||
"version": "1.14.2",
|
"version": "1.16.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -16,6 +16,12 @@
|
|||||||
style="cursor: pointer"
|
style="cursor: pointer"
|
||||||
>
|
>
|
||||||
<span class="text--grayed">#{{ timetable.id }}</span>
|
<span class="text--grayed">#{{ timetable.id }}</span>
|
||||||
|
|
||||||
|
<span class="badges" v-if="timetable.skr || timetable.twr">
|
||||||
|
<span class="train-badge twr" v-if="timetable.twr" :title="$t('general.TWR')">TWR</span>
|
||||||
|
<span class="train-badge skr" v-if="timetable.skr" :title="$t('general.SKR')">SKR</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
<strong class="text--primary">
|
<strong class="text--primary">
|
||||||
{{ timetable.trainCategoryCode }}
|
{{ timetable.trainCategoryCode }}
|
||||||
@@ -342,6 +348,7 @@ hr {
|
|||||||
|
|
||||||
.general-train {
|
.general-train {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
}
|
}
|
||||||
@@ -381,6 +388,13 @@ ul.stock-list {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badges {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
|
||||||
|
// badge.scss
|
||||||
|
}
|
||||||
|
|
||||||
.stock-history {
|
.stock-history {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -418,6 +432,10 @@ ul.stock-list {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.general-train {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.info-route {
|
.info-route {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,16 @@
|
|||||||
|
|
||||||
<table v-else-if="sceneryHistoryList.length">
|
<table v-else-if="sceneryHistoryList.length">
|
||||||
<thead>
|
<thead>
|
||||||
<th>{{ $t('scenery.timetables-history-id') }}</th>
|
<th>{{ $t('scenery.timetables-history-id') }}</th>
|
||||||
<th>{{ $t('scenery.timetables-history-number')}}</th>
|
<th>{{ $t('scenery.timetables-history-number') }}</th>
|
||||||
<th>{{ $t('scenery.timetables-history-route')}}</th>
|
<th>{{ $t('scenery.timetables-history-route') }}</th>
|
||||||
<th>{{ $t('scenery.timetables-history-driver')}}</th>
|
<th>{{ $t('scenery.timetables-history-driver') }}</th>
|
||||||
<th>{{ $t('scenery.timetables-history-author')}}</th>
|
<th>{{ $t('scenery.timetables-history-author') }}</th>
|
||||||
<th>{{ $t('scenery.timetables-history-date')}}</th>
|
<th>{{ $t('scenery.timetables-history-date') }}</th>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="historyItem in sceneryHistoryList" @click="test">
|
<tr v-for="historyItem in sceneryHistoryList">
|
||||||
<td>
|
<td>
|
||||||
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">#{{ historyItem.id }}</router-link>
|
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">#{{ historyItem.id }}</router-link>
|
||||||
</td>
|
</td>
|
||||||
@@ -40,31 +40,6 @@
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="list-warning" v-else>{{ $t('scenery.history-list-empty') }}</div>
|
<div class="list-warning" v-else>{{ $t('scenery.history-list-empty') }}</div>
|
||||||
|
|
||||||
<!-- <ul class="history-list" v-else>
|
|
||||||
<li class="list-item" v-for="historyItem in sceneryHistoryList">
|
|
||||||
<div>
|
|
||||||
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
|
|
||||||
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">
|
|
||||||
<span class="text--grayed"> #{{ historyItem.id }} </span>
|
|
||||||
<b class="text--primary"> {{ historyItem.trainCategoryCode }} {{ historyItem.trainNo }}</b>
|
|
||||||
<div>{{ historyItem.driverName }}</div>
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>{{ historyItem.route.replace('|', ' -> ') }}</div>
|
|
||||||
<div>
|
|
||||||
{{ $t('scenery.timetable-author-title') }}:
|
|
||||||
<b v-if="historyItem.authorName">{{ historyItem.authorName }}</b>
|
|
||||||
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</li>
|
|
||||||
</ul> -->
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -99,19 +74,15 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
async fetchAPIData(countFrom = 0, countLimit = 15) {
|
async fetchAPIData(countFrom = 0, countLimit = 15) {
|
||||||
try {
|
try {
|
||||||
const requestString = `${URLs.stacjownikAPI}/api/getSceneryTimetables?name=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
const requestString = `${URLs.stacjownikAPI}/api/getIssuedTimetables?name=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
||||||
const historyAPIData: SceneryTimetableHistory = await (await axios.get(requestString)).data;
|
const historyAPIData: SceneryTimetableHistory = await (await axios.get(requestString)).data;
|
||||||
|
|
||||||
this.sceneryHistoryList = historyAPIData.sceneryTimetables;
|
this.sceneryHistoryList = historyAPIData.timetables;
|
||||||
this.dataStatus = DataStatus.Loaded;
|
this.dataStatus = DataStatus.Loaded;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
test() {
|
|
||||||
console.log('test');
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
components: { Loading },
|
components: { Loading },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span>
|
<span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span>
|
||||||
|
|
||||||
<span class="timetable_warnings" v-if="train.timetableData?.TWR || train.timetableData?.SKR">
|
<span class="timetable_warnings" v-if="train.timetableData?.TWR || train.timetableData?.SKR">
|
||||||
<span class="train-badge twr" v-if="train.timetableData?.TWR">TWR</span>
|
<span class="train-badge twr" v-if="train.timetableData?.TWR" :title="$t('general.TWR')">TWR</span>
|
||||||
<span class="train-badge skr" v-if="train.timetableData?.SKR">SKR</span>
|
<span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')">SKR</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<strong>
|
<strong>
|
||||||
@@ -118,7 +118,6 @@ export default defineComponent({
|
|||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
@import '../../styles/badge.scss';
|
@import '../../styles/badge.scss';
|
||||||
|
|
||||||
|
|
||||||
.image-warning {
|
.image-warning {
|
||||||
height: 1em;
|
height: 1em;
|
||||||
|
|
||||||
@@ -182,26 +181,6 @@ export default defineComponent({
|
|||||||
gap: 0.25em;
|
gap: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.train-badge {
|
|
||||||
padding: 0.1em 0.2em;
|
|
||||||
border-radius: 0.2em;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
font-size: 0.9em;
|
|
||||||
|
|
||||||
&.twr {
|
|
||||||
background-color: var(--clr-twr);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.skr {
|
|
||||||
background-color: var(--clr-skr);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.offline {
|
|
||||||
background-color: #9c362b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.train-driver {
|
.train-driver {
|
||||||
&.supporter {
|
&.supporter {
|
||||||
color: orange;
|
color: orange;
|
||||||
@@ -218,9 +197,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
.timetable_warnings {
|
.timetable_warnings {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.2em;
|
gap: 0.25em;
|
||||||
|
|
||||||
color: black;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.timetable_progress {
|
.timetable_progress {
|
||||||
|
|||||||
@@ -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,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[] = [
|
||||||
{
|
{
|
||||||
|
|||||||
+13
-5
@@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"general": {
|
"general": {
|
||||||
"and": " and ",
|
"and": " and ",
|
||||||
"refresh": "REFRESH"
|
"refresh": "REFRESH",
|
||||||
|
"TWR": "High risk freight train",
|
||||||
|
"SKR": "Train with exceeded gauge"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"sceneries": "SCENERIES",
|
"sceneries": "SCENERIES",
|
||||||
@@ -97,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",
|
||||||
@@ -119,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",
|
||||||
@@ -129,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",
|
||||||
|
|||||||
+13
-5
@@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"general": {
|
"general": {
|
||||||
"and": " oraz ",
|
"and": " oraz ",
|
||||||
"refresh": "ODŚWIEŻ"
|
"refresh": "ODŚWIEŻ",
|
||||||
|
"TWR": "Towar niebezpieczny wysokiego ryzyka",
|
||||||
|
"SKR": "Przekroczona skrajnia"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"sceneries": "SCENERIE",
|
"sceneries": "SCENERIE",
|
||||||
@@ -97,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",
|
||||||
@@ -120,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",
|
||||||
@@ -130,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",
|
||||||
|
|||||||
-10
@@ -7,7 +7,6 @@ import plLang from './locales/pl.json';
|
|||||||
|
|
||||||
import { createI18n } from 'vue-i18n';
|
import { createI18n } from 'vue-i18n';
|
||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
import { registerSW } from 'virtual:pwa-register';
|
|
||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
locale: 'pl',
|
locale: 'pl',
|
||||||
@@ -21,15 +20,6 @@ const i18n = createI18n({
|
|||||||
enableLegacy: false,
|
enableLegacy: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
registerSW({
|
|
||||||
onRegistered(r) {
|
|
||||||
r &&
|
|
||||||
setInterval(() => {
|
|
||||||
r.update();
|
|
||||||
}, 60 * 60 * 1000);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const clickOutsideDirective: Directive = {
|
const clickOutsideDirective: Directive = {
|
||||||
mounted(el, binding) {
|
mounted(el, binding) {
|
||||||
el.clickOutsideEvent = (event: Event) => {
|
el.clickOutsideEvent = (event: Event) => {
|
||||||
|
|||||||
+49
-49
@@ -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,
|
||||||
};
|
};
|
||||||
@@ -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
-1
@@ -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;
|
||||||
@@ -47,10 +47,12 @@ export interface TimetableHistory {
|
|||||||
hashesString?: string;
|
hashesString?: string;
|
||||||
currentSceneryName?: string;
|
currentSceneryName?: string;
|
||||||
currentSceneryHash?: string;
|
currentSceneryHash?: string;
|
||||||
|
|
||||||
|
routeSceneries?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SceneryTimetableHistory {
|
export interface SceneryTimetableHistory {
|
||||||
sceneryTimetables: TimetableHistory[];
|
timetables: TimetableHistory[];
|
||||||
totalCount: number;
|
// totalCount: number;
|
||||||
sceneryName: string;
|
// sceneryName: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'];
|
||||||
|
}
|
||||||
@@ -1,79 +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 StationJSONData {
|
export interface StationRoutesInfo {
|
||||||
name: string;
|
routeName: string;
|
||||||
abbr: string;
|
isElectric: boolean;
|
||||||
url: string;
|
isInternal: boolean;
|
||||||
lines: string;
|
isRouteSBL: boolean;
|
||||||
project: string;
|
routeLength: number;
|
||||||
projectUrl: string;
|
routeSpeed: number;
|
||||||
|
routeTracks: number;
|
||||||
reqLevel: number;
|
}
|
||||||
|
|
||||||
signalType: string;
|
export interface StationJSONData {
|
||||||
controlType: string;
|
name: string;
|
||||||
|
abbr: string;
|
||||||
SUP: boolean;
|
url: string;
|
||||||
|
lines: string;
|
||||||
routes: string;
|
project: string;
|
||||||
|
projectUrl: string;
|
||||||
checkpoints: string | null;
|
|
||||||
authors?: string;
|
reqLevel: number;
|
||||||
|
|
||||||
availability: Availability;
|
signalType: string;
|
||||||
}
|
controlType: string;
|
||||||
|
|
||||||
|
SUP: boolean;
|
||||||
|
|
||||||
|
// routes: string;
|
||||||
|
routesInfo: StationRoutesInfo[];
|
||||||
|
|
||||||
|
checkpoints: string | null;
|
||||||
|
authors?: string;
|
||||||
|
|
||||||
|
availability: Availability;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export const useJournalFiltersStore = defineStore('journalFiltersStore', {
|
||||||
|
state: () => ({
|
||||||
|
timetableFilters: {
|
||||||
|
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -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;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+31
-49
@@ -3,7 +3,7 @@ import { defineStore } from 'pinia';
|
|||||||
import { io } from 'socket.io-client';
|
import { io } from 'socket.io-client';
|
||||||
import { DataStatus } from '../scripts/enums/DataStatus';
|
import { DataStatus } from '../scripts/enums/DataStatus';
|
||||||
import StationAPIData from '../scripts/interfaces/api/StationAPIData';
|
import StationAPIData from '../scripts/interfaces/api/StationAPIData';
|
||||||
import {ScheduledTrain} from '../scripts/interfaces/ScheduledTrain';
|
import { ScheduledTrain } from '../scripts/interfaces/ScheduledTrain';
|
||||||
import Station from '../scripts/interfaces/Station';
|
import Station from '../scripts/interfaces/Station';
|
||||||
import StationRoutes from '../scripts/interfaces/StationRoutes';
|
import StationRoutes from '../scripts/interfaces/StationRoutes';
|
||||||
import Train from '../scripts/interfaces/Train';
|
import Train from '../scripts/interfaces/Train';
|
||||||
@@ -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,
|
||||||
@@ -303,57 +303,39 @@ export const useStore = defineStore('store', {
|
|||||||
...scenery,
|
...scenery,
|
||||||
authors: scenery.authors?.split(',').map((a) => a.trim()),
|
authors: scenery.authors?.split(',').map((a) => a.trim()),
|
||||||
routes:
|
routes:
|
||||||
scenery.routes
|
scenery.routesInfo.reduce(
|
||||||
?.split(';')
|
(acc, route) => {
|
||||||
.filter((routeString) => routeString)
|
const propName: keyof StationRoutes = `${route.routeTracks == 2 ? 'twoWay' : 'oneWay'}${
|
||||||
.reduce(
|
route.isElectric ? '' : 'No'
|
||||||
(acc, routeString) => {
|
}CatenaryRouteNames`;
|
||||||
const specs1 = routeString.split('_')[0];
|
|
||||||
const isInternal = specs1.startsWith('!');
|
|
||||||
const name = isInternal ? specs1.replace('!', '') : specs1;
|
|
||||||
|
|
||||||
const specs2 = routeString.split('_')[1].split('');
|
acc[route.routeTracks == 2 ? 'twoWay' : 'oneWay'].push({
|
||||||
const twoWay = specs2[0] == '2';
|
name: route.routeName,
|
||||||
const catenary = specs2[1] == 'E';
|
SBL: route.isRouteSBL,
|
||||||
const SBL = specs2[2] == 'S';
|
TWB: false,
|
||||||
const TWB = specs2[3] ? true : false;
|
catenary: route.isElectric,
|
||||||
const speed = Number(routeString.split(':')[1]) || 0;
|
isInternal: route.isInternal,
|
||||||
const length = Number(routeString.split(':')[2]) || 0;
|
tracks: route.routeTracks,
|
||||||
|
length: route.routeLength,
|
||||||
|
speed: route.routeSpeed,
|
||||||
|
});
|
||||||
|
|
||||||
const propName = twoWay
|
if (!route.isInternal) acc[propName].push(route.routeName);
|
||||||
? catenary
|
|
||||||
? 'twoWayCatenaryRouteNames'
|
|
||||||
: 'twoWayNoCatenaryRouteNames'
|
|
||||||
: catenary
|
|
||||||
? 'oneWayCatenaryRouteNames'
|
|
||||||
: 'oneWayNoCatenaryRouteNames';
|
|
||||||
|
|
||||||
acc[twoWay ? 'twoWay' : 'oneWay'].push({
|
if (route.isRouteSBL) acc['sblRouteNames'].push(route.routeName);
|
||||||
name,
|
|
||||||
SBL,
|
|
||||||
TWB,
|
|
||||||
catenary,
|
|
||||||
isInternal,
|
|
||||||
tracks: twoWay ? 2 : 1,
|
|
||||||
length,
|
|
||||||
speed,
|
|
||||||
});
|
|
||||||
if (!isInternal) acc[propName].push(name);
|
|
||||||
|
|
||||||
if (SBL) acc['sblRouteNames'].push(name);
|
return acc;
|
||||||
|
},
|
||||||
return acc;
|
{
|
||||||
},
|
oneWay: [],
|
||||||
{
|
twoWay: [],
|
||||||
oneWay: [],
|
sblRouteNames: [],
|
||||||
twoWay: [],
|
oneWayCatenaryRouteNames: [],
|
||||||
sblRouteNames: [],
|
oneWayNoCatenaryRouteNames: [],
|
||||||
oneWayCatenaryRouteNames: [],
|
twoWayCatenaryRouteNames: [],
|
||||||
oneWayNoCatenaryRouteNames: [],
|
twoWayNoCatenaryRouteNames: [],
|
||||||
twoWayCatenaryRouteNames: [],
|
} as StationRoutes
|
||||||
twoWayNoCatenaryRouteNames: [],
|
) || {},
|
||||||
} as StationRoutes
|
|
||||||
) || {},
|
|
||||||
checkpoints: scenery.checkpoints
|
checkpoints: scenery.checkpoints
|
||||||
? scenery.checkpoints.split(';').map((sub) => ({ checkpointName: sub, scheduledTrains: [] }))
|
? scenery.checkpoints.split(';').map((sub) => ({ checkpointName: sub, scheduledTrains: [] }))
|
||||||
: [],
|
: [],
|
||||||
|
|||||||
@@ -55,3 +55,26 @@
|
|||||||
background-color: forestgreen;
|
background-color: forestgreen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.train-badge {
|
||||||
|
padding: 0.1em 0.2em;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
font-size: 0.9em;
|
||||||
|
|
||||||
|
&.twr {
|
||||||
|
background-color: var(--clr-twr);
|
||||||
|
box-shadow: 0 0 5px 1px var(--clr-twr);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.skr {
|
||||||
|
background-color: var(--clr-skr);
|
||||||
|
box-shadow: 0 0 5px 1px var(--clr-skr);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.offline {
|
||||||
|
background-color: #be3728;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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,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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
+55
-55
@@ -1,55 +1,55 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import { VitePWA } from 'vite-plugin-pwa';
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
server: {
|
||||||
port: 5001,
|
port: 5001,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
VitePWA({
|
VitePWA({
|
||||||
registerType: 'prompt',
|
registerType: 'autoUpdate',
|
||||||
|
|
||||||
workbox: {
|
workbox: {
|
||||||
globPatterns: ['**/*.{js,css,html,png,svg,jpg}'],
|
globPatterns: ['**/*.{js,css,html,png,svg,jpg}'],
|
||||||
runtimeCaching: [
|
runtimeCaching: [
|
||||||
{
|
{
|
||||||
urlPattern: new RegExp('^https://spythere.pl/api/getSceneries', 'i'),
|
urlPattern: new RegExp('^https://spythere.pl/api/getSceneries', 'i'),
|
||||||
handler: 'NetworkFirst',
|
handler: 'NetworkFirst',
|
||||||
options: {
|
options: {
|
||||||
cacheName: 'sceneries-cache',
|
cacheName: 'sceneries-cache',
|
||||||
expiration: {
|
expiration: {
|
||||||
maxEntries: 1,
|
maxEntries: 1,
|
||||||
maxAgeSeconds: 60 * 60 * 24 * 7, // <== 7 days
|
maxAgeSeconds: 60 * 60 * 24 * 7, // <== 7 days
|
||||||
},
|
},
|
||||||
cacheableResponse: {
|
cacheableResponse: {
|
||||||
statuses: [0, 200],
|
statuses: [0, 200],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
urlPattern: /^https:\/\/rj.td2.info.pl\/dist\/img\/thumbnails\/.*/i,
|
urlPattern: /^https:\/\/rj.td2.info.pl\/dist\/img\/thumbnails\/.*/i,
|
||||||
handler: 'CacheFirst',
|
handler: 'CacheFirst',
|
||||||
options: {
|
options: {
|
||||||
cacheName: 'images-cache',
|
cacheName: 'images-cache',
|
||||||
expiration: {
|
expiration: {
|
||||||
maxEntries: 100,
|
maxEntries: 100,
|
||||||
maxAgeSeconds: 60 * 60 * 24 * 60,
|
maxAgeSeconds: 60 * 60 * 24 * 60,
|
||||||
},
|
},
|
||||||
cacheableResponse: {
|
cacheableResponse: {
|
||||||
statuses: [0, 200, 404],
|
statuses: [0, 200, 404],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
devOptions: {
|
devOptions: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user