Compare commits

...

17 Commits

Author SHA1 Message Date
Spythere f28600a7fa Merge do wersji 1.16.0
Wersja 1.16.0
2023-06-12 01:33:45 +02:00
Spythere d59ead87e6 bump v1.16.0 2023-06-12 01:21:49 +02:00
Spythere 34d91bc800 poprawki w pokazywaniu statystyk 2023-06-12 01:19:31 +02:00
Spythere cf9991d8a0 layout filtrów dzienników 2023-06-12 00:51:17 +02:00
Spythere 4ffb79d62b poprawki pobierania danych 2023-06-11 21:47:50 +02:00
Spythere d9f5edb4fe poprawki tłumaczeń 2023-06-11 21:47:22 +02:00
Spythere 1b2112430a feature: długości szlaków po kliknięciu 2023-06-08 23:35:57 +02:00
Spythere 0a972a23ef fix: asynchroniczność pobierania danych z API 2023-06-04 13:35:53 +02:00
Spythere 6d52724d06 zapamiętywanie stanu statystyk dnia 2023-06-04 12:19:46 +02:00
Spythere 99415c35d3 rozbudowane filtry dziennika RJ 2023-06-04 12:06:15 +02:00
Spythere c3f687d439 hotfixy dzienników 2023-06-04 01:45:58 +02:00
Spythere 266edfd6e6 reorder typów danych 2023-06-04 00:33:43 +02:00
Spythere d32d5ad91b poprawki dzienników 2023-06-03 18:55:44 +02:00
Spythere c3481470cb optymalizacja zapytań; filtr scenerii pocz. 2023-06-03 15:49:15 +02:00
Spythere 57e88b9abc Merge do wersji 1.15.1
Wersja 1.15.1
2023-06-02 20:09:43 +02:00
Spythere 44ebf53798 poprawki pwa 2023-06-02 20:06:25 +02:00
Spythere 145dc72b6b pwa: zmiana na autoUpdate 2023-06-02 19:41:33 +02:00
31 changed files with 1350 additions and 1188 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "stacjownik",
"version": "1.15.0",
"version": "1.16.0",
"private": true,
"scripts": {
"dev": "vite",
+18 -4
View File
@@ -95,13 +95,18 @@
<script setup lang="ts">
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 { ITimetablesDailyStats, ITimetablesDailyStatsResponse } from '../../scripts/interfaces/api/StatsAPIData';
import { URLs } from '../../scripts/utils/apiURLs';
import StorageManager from '../../scripts/managers/storageManager';
const intervalId = ref(-1);
const emit = defineEmits<{
(e: 'toggleStatsOpen', value: boolean): void;
}>();
const data = reactive({
statsStatus: DataStatus.Loading,
@@ -158,17 +163,26 @@ async function fetchDailyTimetableStats() {
function startFetchingDailyStats() {
fetchDailyTimetableStats();
if (intervalId.value != -1) return;
intervalId.value = setInterval(fetchDailyTimetableStats, 60000);
}
function stopFetchingDailyStats() {
clearInterval(intervalId.value);
intervalId.value = -1;
}
defineExpose({
startFetchingDailyStats,
stopFetchingDailyStats,
onActivated(() => {
startFetchingDailyStats();
emit('toggleStatsOpen', true);
});
onDeactivated(() => {
stopFetchingDailyStats();
});
</script>
<style lang="scss" scoped>
+40 -30
View File
@@ -49,15 +49,6 @@
</button>
</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>
<h1 class="option-title">{{ $t('options.sort-title') }}</h1>
@@ -74,15 +65,31 @@
</div>
<h1 class="option-title" v-if="filters.length != 0">{{ $t('options.filter-title') }}</h1>
<div class="options_filters">
<button
v-for="filter in filters"
class="filter-option btn--option"
:class="{ checked: journalFilterActive.id === filter.id }"
:id="filter.id"
@click="onFilterChange(filter)"
>
{{ $t(`options.filter-${filter.id}`) }}
<div class="options_filter-sections" v-if="filters.length != 0 && filterList">
<section class="filter-section" v-for="section in JournalFilterSection">
<p>{{ $t(`options.filter-section-${section}`) }}</p>
<div class="options_filters">
<button
v-for="filter in filterList.filter((f) => f.filterSection == section)"
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>
</div>
</div>
@@ -100,9 +107,10 @@ import { DataStatus } from '../../scripts/enums/DataStatus';
import { DriverStatsAPIData } from '../../scripts/interfaces/api/DriverStatsAPIData';
import { URLs } from '../../scripts/utils/apiURLs';
import { useStore } from '../../store/store';
import { JournalTimetableFilter } from '../../types/Journal/JournalTimetablesTypes';
import ActionButton from '../Global/ActionButton.vue';
import SelectBox from '../Global/SelectBox.vue';
import { JournalFilterSection } from '../../scripts/enums/JournalFilterType';
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
export default defineComponent({
components: { SelectBox, ActionButton },
@@ -116,7 +124,7 @@ export default defineComponent({
},
filters: {
type: Array as PropType<JournalTimetableFilter[]>,
type: Array as PropType<JournalFilter[]>,
default: [],
},
@@ -132,13 +140,14 @@ export default defineComponent({
optionsType: {
type: String,
required: true
}
required: true,
},
},
data() {
return {
showOptions: false,
JournalFilterSection,
driverSuggestions: [] as string[],
dispatcherSuggestions: [] as string[],
@@ -154,7 +163,8 @@ export default defineComponent({
return {
searchersValues: inject('searchersValues') as { [key: string]: string },
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: {
async driverStatsName(value: string) {
await this.fetchDriverStats();
this.store.currentStatsTab = value ? 'driver' : 'daily';
// if (value) this.store.currentStatsTab = 'driver';
},
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 }) {
this.sorterActive.id = item.id;
this.sorterActive.dir = -1;
this.$emit('onSearchConfirm');
},
onFilterChange(filter: JournalTimetableFilter) {
this.journalFilterActive = filter;
onFilterChange(filter: JournalFilter) {
// this.journalFilterActive = filter;
this.filterList?.filter((f) => f.filterSection === filter.filterSection).forEach((f) => (f.isActive = false));
filter.isActive = true;
this.$emit('onSearchConfirm');
},
+25 -19
View File
@@ -1,11 +1,13 @@
<template>
<div class="journal-stats" v-show="!store.isOffline">
<div class="journal-stats" v-if="!store.isOffline">
<div class="tabs">
<button
v-for="tab in data.tabs"
class="btn--filled"
:data-selected="tab.name == store.currentStatsTab && areStatsOpen"
:data-inactive="tab.inactive"
:data-disabled="tab.inactive"
:disabled="tab.inactive"
@click="onTabButtonClick(tab.name)"
>
{{ $t(tab.titlePath) }}
@@ -14,7 +16,7 @@
<div class="stats-tab" v-show="areStatsOpen">
<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'" />
</keep-alive>
</div>
@@ -22,22 +24,21 @@
</template>
<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 JournalDailyStats from './DailyStats.vue';
import JournalDriverStats from './JournalDriverStats.vue';
import StorageManager from '../../scripts/managers/storageManager';
// Types
type TStatTab = 'daily' | 'driver';
// Variables
const store = useStore();
const dailyStatsComp: Ref<InstanceType<typeof JournalDailyStats> | null> = ref(null);
const lastDailyStatsOpen = ref(false);
const areStatsOpen = ref(false);
const lastClickedTab = ref('daily');
const lastClickedTab: Ref<'daily' | 'driver' | null> = ref(null);
let data = reactive({
tabs: [
@@ -57,30 +58,35 @@ let data = reactive({
function onTabButtonClick(tab: TStatTab) {
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;
lastClickedTab.value = tab;
if (areStatsOpen.value == false) store.currentStatsTab = null;
}
onActivated(() => {
dailyStatsComp.value?.startFetchingDailyStats();
});
onDeactivated(() => {
dailyStatsComp.value?.stopFetchingDailyStats();
});
function toggleStatsOpen(open: boolean) {
areStatsOpen.value = open;
}
watch(
computed(() => store.driverStatsData),
(statsData) => {
data.tabs[1].inactive = statsData ? false : true;
lastClickedTab.value = statsData ? 'driver' : 'daily';
if (statsData) areStatsOpen.value = true;
if (!statsData) areStatsOpen.value = lastDailyStatsOpen.value;
store.currentStatsTab = statsData ? 'driver' : lastClickedTab.value;
areStatsOpen.value = statsData ? true : lastClickedTab.value !== null;
}
);
onMounted(() => {
if (StorageManager.getBooleanValue('dailyStatsOpen')) {
areStatsOpen.value = true;
store.currentStatsTab = 'daily';
}
});
</script>
<style lang="scss" scoped>
@@ -4,9 +4,11 @@
<b>{{ $t('scenery.one-way-routes') }}</b>
<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 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>
</li>
</ul>
@@ -16,9 +18,11 @@
<b>{{ $t('scenery.two-way-routes') }}</b>
<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 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>
</li>
</ul>
@@ -37,6 +41,19 @@ export default defineComponent({
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>
@@ -66,6 +83,11 @@ ul.routes-list {
li {
margin: 0.5em 0.25em;
cursor: pointer;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
span {
padding: 0.2em 0.25em;
@@ -100,7 +122,6 @@ ul.routes-list {
&:only-child {
border-radius: 0.5em;
}
}
}
+1 -1
View File
@@ -82,10 +82,10 @@
import { defineComponent, inject, PropType } from 'vue';
import imageMixin from '../../mixins/imageMixin';
import keyMixin from '../../mixins/keyMixin';
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes';
import ActionButton from '../Global/ActionButton.vue';
import SelectBox from '../Global/SelectBox.vue';
import { TrainFilterSection } from '../../scripts/enums/TrainFilterType';
import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter';
export default defineComponent({
components: { SelectBox, ActionButton },
@@ -1,28 +1,46 @@
import { JournalFilterType } from "../../scripts/enums/JournalFilterType";
import { JournalTimetableFilter } from "../../types/Journal/JournalTimetablesTypes";
import { JournalFilterSection, JournalFilterType } from '../../scripts/enums/JournalFilterType';
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
export const journalTimetableFilters: JournalTimetableFilter[] = [
export const journalTimetableFilters: JournalFilter[] = [
{
id: JournalFilterType.all,
filterSection: 'timetable-status',
id: JournalFilterType.ALL,
filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: true,
},
{
id: JournalFilterType.active,
filterSection: 'timetable-status',
id: JournalFilterType.ACTIVE,
filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: false,
},
{
id: JournalFilterType.fulfilled,
filterSection: 'timetable-status',
id: JournalFilterType.FULFILLED,
filterSection: JournalFilterSection.TIMETABLE_STATUS,
isActive: false,
},
{
id: JournalFilterType.abandoned,
filterSection: 'timetable-status',
id: JournalFilterType.ABANDONED,
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,
},
];
+1 -1
View File
@@ -1,5 +1,5 @@
import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType';
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes';
import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter';
export const trainFilters: TrainFilter[] = [
{
+10 -4
View File
@@ -99,19 +99,21 @@
"search-dispatcher": "Dispatcher name",
"search-station": "Scenery name",
"search-author": "Timetable author name",
"search-timetables-date": "Timetable date (CEST / GMT+2)",
"search-dispatchers-date": "Service date (CEST / GMT+2)",
"search-issuedFrom": "Origin scenery name",
"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-speed": "speed",
"sort-length": "length",
"sort-distance": "distance",
"sort-routeDistance": "route distance",
"sort-timetable": "train no.",
"sort-progress": "route progress",
"sort-delay": "current delay",
"sort-id": "timetable id",
"sort-total-stops": "total stops",
"sort-allStopsCount": "total stops",
"sort-beginDate": "date",
"sort-timetableId": "timetable ID",
"sort-timestampFrom": "date",
@@ -121,6 +123,7 @@
"filter-withComments": "COMMENTS",
"filter-twr": "HIGH RISK CARGO",
"filter-skr": "EXCEEDED GAUGE",
"filter-twr-skr": "ALL TYPES",
"filter-common": "NO WARNINGS",
"filter-passenger": "PASSENGER",
"filter-freight": "FREIGHT",
@@ -131,6 +134,9 @@
"filter-reset": "RESET FILTERS",
"filter-clear": "CLEAR FILTERS",
"filter-section-timetable-status": "TIMETABLE STATUS",
"filter-section-twrskr": "WARNINGS",
"filter-all": "ALL ENTRIES",
"filter-abandoned": "ABANDONED",
"filter-fulfilled": "FULFILLED",
+10 -4
View File
@@ -99,11 +99,13 @@
"search-dispatcher": "Nick dyżurnego",
"search-station": "Nazwa scenerii",
"search-author": "Nick autora rozkładu jazdy",
"search-timetables-date": "Data rozkładu jazdy (czas polski)",
"search-dispatchers-date": "Data służby (czas polski)",
"search-issuedFrom": "Sceneria początkowa",
"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-total-stops": "stacje",
"sort-routeDistance": "kilometraż",
"sort-allStopsCount": "stacje",
"sort-beginDate": "data",
"sort-timetableId": "ID rozkładu",
"sort-timestampFrom": "data",
@@ -122,6 +124,7 @@
"filter-noComments": "BEZ UWAG",
"filter-twr": "WYS. RYZYKA",
"filter-skr": "SKRAJNIA",
"filter-twr-skr": "WSZYSTKIE",
"filter-common": "ZWYKŁE",
"filter-passenger": "PASAŻERSKIE",
"filter-freight": "TOWAROWE",
@@ -132,6 +135,9 @@
"filter-reset": "ZRESETUJ FILTRY",
"filter-clear": "WYŁĄCZ FILTRY",
"filter-section-timetable-status": "STATUS ROZKŁADU JAZDY",
"filter-section-twrskr": "UWAGI",
"filter-all": "WSZYSTKIE",
"filter-abandoned": "PORZUCONE",
"filter-fulfilled": "WYPEŁNIONE",
-10
View File
@@ -7,7 +7,6 @@ import plLang from './locales/pl.json';
import { createI18n } from 'vue-i18n';
import { createPinia } from 'pinia';
import { registerSW } from 'virtual:pwa-register';
const i18n = createI18n({
locale: 'pl',
@@ -21,15 +20,6 @@ const i18n = createI18n({
enableLegacy: false,
});
registerSW({
onRegistered(r) {
r &&
setInterval(() => {
r.update();
}, 60 * 60 * 1000);
},
});
const clickOutsideDirective: Directive = {
mounted(el, binding) {
el.clickOutsideEvent = (event: Event) => {
@@ -1,4 +1,4 @@
import Filter from "../../scripts/interfaces/Filter";
import Filter from "../../interfaces/Filter";
export const filterInitStates: Filter = {
default: false,
+12 -4
View File
@@ -1,6 +1,14 @@
export const enum JournalFilterType {
active = "active",
fulfilled = "fulfilled",
abandoned = "abandoned",
all = "all"
ACTIVE = 'active',
FULFILLED = 'fulfilled',
ABANDONED = 'abandoned',
ALL = 'all',
TWR = 'twr',
SKR = 'skr',
TWR_SKR = 'twr-skr',
}
export enum JournalFilterSection {
TIMETABLE_STATUS = 'timetable-status',
TWRSKR = 'twrskr',
}
@@ -1,4 +1,4 @@
import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType';
import { TrainFilterSection, TrainFilterType } from '../../enums/TrainFilterType'
export interface TrainFilter {
id: TrainFilterType;
@@ -0,0 +1,21 @@
import { JournalTimetableSorter } from '../../types/JournalTimetablesTypes';
export interface TimetablesQueryParams {
driverName?: string;
trainNo?: string;
authorName?: string;
timestampFrom?: number;
timestampTo?: number;
issuedFrom?: string;
countFrom?: number;
countLimit?: number;
fulfilled?: number;
terminated?: number;
twr?: number;
skr?: number;
sortBy?: JournalTimetableSorter['id'];
}
+1 -1
View File
@@ -34,7 +34,7 @@ export interface StoreState {
chosenModalTrainId?: string;
currentStatsTab: 'daily' | 'driver';
currentStatsTab: 'daily' | 'driver' | null;
dataStatuses: {
connection: DataStatus;
+3 -3
View File
@@ -1,4 +1,4 @@
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes';
import { TrainFilter } from '../interfaces/Trains/TrainFilter';
import { TrainFilterType } from '../enums/TrainFilterType';
import Train from '../interfaces/Train';
import TrainStop from '../interfaces/TrainStop';
@@ -44,7 +44,7 @@ function filterTrainList(trainList: Train[], searchedTrain: string, searchedDriv
return !train.timetableData?.SKR;
case TrainFilterType.common:
return train.timetableData?.SKR || train.timetableData?.TWR;
return train.timetableData?.SKR || train.timetableData?.TWR;
case TrainFilterType.passenger:
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;
return -sorterActive.dir;
case 'distance':
case 'routeDistance':
if ((a.timetableData?.routeDistance || -1) > (b.timetableData?.routeDistance || -1)) 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,6 +1,6 @@
import { HeadIdsTypes } from '../../scripts/data/stationHeaderNames';
import Filter from '../../scripts/interfaces/Filter';
import Station from '../../scripts/interfaces/Station';
import { HeadIdsTypes } from '../data/stationHeaderNames';
import Filter from '../interfaces/Filter';
import Station from '../interfaces/Station';
export const sortStations = (a: Station, b: Station, sorter: { headerName: HeadIdsTypes; dir: number }) => {
let diff = 0;
+9
View File
@@ -0,0 +1,9 @@
import { defineStore } from 'pinia';
export const useJournalFiltersStore = defineStore('journalFiltersStore', {
state: () => ({
timetableFilters: {
},
}),
});
+2 -2
View File
@@ -3,8 +3,8 @@ import inputData from '../data/options.json';
import Station from '../scripts/interfaces/Station';
import StorageManager from '../scripts/managers/storageManager';
import { useStore } from './store';
import { filterInitStates } from './constants/initFilterStates';
import { filterStations, sortStations } from './utils/filterUtils';
import { filterInitStates } from '../scripts/constants/stores/initFilterStates';
import { filterStations, sortStations } from '../scripts/utils/filterUtils';
import { HeadIdsTypes } from '../scripts/data/stationHeaderNames';
export const useStationFiltersStore = defineStore('stationFiltersStore', {
+1 -1
View File
@@ -54,7 +54,7 @@ export const useStore = defineStore('store', {
trains: DataStatus.Loading,
},
currentStatsTab: 'daily',
currentStatsTab: null,
blockScroll: false,
listenerLaunched: false,
+6 -2
View File
@@ -65,12 +65,16 @@
&.twr {
background-color: var(--clr-twr);
box-shadow: 0 0 5px 1px 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);
box-shadow: 0 0 5px 1px var(--clr-skr);
}
&.offline {
background-color: #be3728;
}
}
+20 -17
View File
@@ -82,12 +82,16 @@ h1.option-title {
padding: 0.25em 0.25em 0 0;
}
.options_filter-sections section {
margin: 0.5em 0;
}
.options_filters {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
margin: 0.5em 0 0 0;
margin: 0.25em 0;
}
.sort-option,
@@ -118,17 +122,6 @@ h1.option-title {
margin: 0.5em auto;
}
.search_actions {
display: flex;
gap: 0.5em;
margin: 1em 0;
width: 100%;
button {
width: 100%;
}
}
.search-box {
.search-exit {
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() {
h1 {
text-align: center;
@@ -155,13 +159,12 @@ h1.option-title {
max-width: 100%;
}
.filter-option,
.sort-option {
margin: 0.25em 0.25em;
}
.options_filters,
.options_sorters {
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;
}
+2 -2
View File
@@ -70,7 +70,7 @@ import { URLs } from '../scripts/utils/apiURLs';
import { DataStatus } from '../scripts/enums/DataStatus';
import { useStore } from '../store/store';
import JournalDispatchersList from '../components/JournalView/JournalDispatchersList.vue';
import { JournalDispatcherSearcher, JournalDispatcherSorter } from '../types/Journal/JournalDispatcherTypes';
import { JournalDispatcherSearcher, JournalDispatcherSorter } from '../scripts/types/JournalDispatcherTypes';
import { DispatcherHistory } from '../scripts/interfaces/api/DispatchersAPIData';
import JournalHeader from '../components/JournalView/JournalHeader.vue';
import { LocationQuery } from 'vue-router';
@@ -134,6 +134,7 @@ export default defineComponent({
provide('sorterActive', sorterActive);
provide('journalFilterActive', journalFilterActive);
provide('searchersValues', searchersValues);
provide('filterList', reactive([]));
const scrollElement: Ref<HTMLElement | null> = ref(null);
@@ -287,4 +288,3 @@ export default defineComponent({
<style lang="scss" scoped>
@import '../styles/JournalSection.scss';
</style>
+103 -64
View File
@@ -7,7 +7,7 @@
@on-search-confirm="fetchHistoryData"
@on-options-reset="resetOptions"
@on-refresh-data="fetchHistoryData"
:sorter-option-ids="['timetableId', 'beginDate', 'distance', 'total-stops']"
:sorter-option-ids="['timetableId', 'beginDate', 'routeDistance', 'allStopsCount']"
:filters="journalTimetableFilters"
:currentOptionsActive="currentOptionsActive"
:data-status="dataStatus"
@@ -58,25 +58,32 @@
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
import axios from 'axios';
import DriverStats from '../components/JournalView/JournalDriverStats.vue';
import Loading from '../components/Global/Loading.vue';
import { JournalTimetableSorter } from '../types/Journal/JournalTimetablesTypes';
import imageMixin from '../mixins/imageMixin';
import dateMixin from '../mixins/dateMixin';
import routerMixin from '../mixins/routerMixin';
import modalTrainMixin from '../mixins/modalTrainMixin';
import DriverStats from '../components/JournalView/JournalDriverStats.vue';
import JournalOptions from '../components/JournalView/JournalOptions.vue';
import JournalStats from '../components/JournalView/JournalStats.vue';
import JournalHeader from '../components/JournalView/JournalHeader.vue';
import JournalTimetablesList from '../components/JournalView/JournalTimetablesList.vue';
import Loading from '../components/Global/Loading.vue';
import { DataStatus } from '../scripts/enums/DataStatus';
import { JournalFilterType } from '../scripts/enums/JournalFilterType';
import { TimetableHistory } from '../scripts/interfaces/api/TimetablesAPIData';
import { URLs } from '../scripts/utils/apiURLs';
import { useStore } from '../store/store';
import JournalOptions from '../components/JournalView/JournalOptions.vue';
import { JournalTimetableSearchType } from '../types/Journal/JournalTimetablesTypes';
import modalTrainMixin from '../mixins/modalTrainMixin';
import imageMixin from '../mixins/imageMixin';
import JournalTimetablesList from '../components/JournalView/JournalTimetablesList.vue';
import { journalTimetableFilters } from '../constants/Journal/JournalTimetablesConsts';
import JournalStats from '../components/JournalView/JournalStats.vue';
import JournalHeader from '../components/JournalView/JournalHeader.vue';
import { LocationQuery } from 'vue-router';
import { TimetablesQueryParams } from '../scripts/interfaces/api/TimetablesQueryParams';
import { JournalFilterType } from '../scripts/enums/JournalFilterType';
import {
JournalFilter,
JournalTimetableSearchType,
JournalTimetableSorter,
} from '../scripts/types/JournalTimetablesTypes';
import { journalTimetableFilters } from '../constants/Journal/JournalTimetablesConsts';
const TIMETABLES_API_URL = `${URLs.stacjownikAPI}/api/getTimetables`;
@@ -93,8 +100,7 @@ export default defineComponent({
},
data: () => ({
currentQuery: '',
currentQueryArray: [] as string[],
currentQueryParams: {} as TimetablesQueryParams,
scrollDataLoaded: true,
scrollNoMoreData: false,
@@ -113,13 +119,16 @@ export default defineComponent({
}),
setup() {
const sorterActive: JournalTimetableSorter = reactive({ id: 'timetableId', dir: 1 });
const journalFilterActive = ref(journalTimetableFilters[0]);
const sorterActive: JournalTimetableSorter = reactive({ id: 'timetableId', dir: 'desc' });
// const journalFilterActive = ref(journalTimetableFilters[0]);
const initFilters: readonly JournalFilter[] = JSON.parse(JSON.stringify(journalTimetableFilters));
const filterList: JournalFilter[] = reactive(JSON.parse(JSON.stringify(initFilters)));
const searchersValues = reactive({
'search-train': '',
'search-driver': '',
'search-dispatcher': '',
'search-issuedFrom': '',
'search-date': '',
} as JournalTimetableSearchType);
@@ -128,14 +137,15 @@ export default defineComponent({
provide('searchersValues', searchersValues);
provide('sorterActive', sorterActive);
provide('journalFilterActive', journalFilterActive);
provide('filterList', filterList);
const scrollElement: Ref<HTMLElement | null> = ref(null);
return {
sorterActive,
journalFilterActive,
searchersValues,
filterList,
initFilters,
countFromIndex,
countLimit,
@@ -147,8 +157,8 @@ export default defineComponent({
},
watch: {
currentQueryArray(q: string[]) {
this.currentOptionsActive = q.length >= 2 || q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1]);
currentQueryParams(q: TimetablesQueryParams) {
this.currentOptionsActive = Object.values(q).some((v) => v !== undefined);
},
},
@@ -163,7 +173,6 @@ export default defineComponent({
this.fetchHistoryData();
},
methods: {
handleScroll(e: Event) {
const listElement = e.target as HTMLElement;
@@ -179,29 +188,35 @@ export default defineComponent({
if ('timetableId' in query) this.searchersValues['search-train'] = `#${query.timetableId}`;
},
setSearchers(date: string, driver: string, train: string, dispatcher: string) {
setSearchers(date: string, driver: string, train: string, dispatcher: string, issuedFrom: string) {
this.searchersValues['search-date'] = date;
this.searchersValues['search-driver'] = driver;
this.searchersValues['search-train'] = train;
this.searchersValues['search-dispatcher'] = dispatcher;
this.searchersValues['search-issuedFrom'] = issuedFrom;
},
resetOptions() {
this.setSearchers('', '', '', '');
this.setSearchers('', '', '', '', '');
this.journalFilterActive = this.journalTimetableFilters[0];
this.sorterActive.id = 'timetableId';
this.filterList.forEach(
(f) => (f.isActive = this.initFilters.find((initFilter) => initFilter.id == f.id)?.isActive || false)
);
this.fetchHistoryData();
},
async addHistoryData() {
this.scrollDataLoaded = false;
const countFrom = this.timetableHistory.length;
this.currentQueryParams['countFrom'] = this.timetableHistory.length;
const responseData: TimetableHistory[] = await (
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}&countFrom=${countFrom}`)
await axios.get(`${TIMETABLES_API_URL}`, {
params: { ...this.currentQueryParams },
})
).data;
if (!responseData) return;
@@ -216,57 +231,82 @@ export default defineComponent({
},
async fetchHistoryData() {
// if(this.dataStatus == DataStatus.Loading) return;
const queries: string[] = [];
const driverName = this.searchersValues['search-driver'].trim();
const trainNo = this.searchersValues['search-train'].trim();
const authorName = this.searchersValues['search-dispatcher'].trim();
const dateString = this.searchersValues['search-date'].trim();
const driverName = this.searchersValues['search-driver'].trim() || undefined;
const trainNo = this.searchersValues['search-train'].trim() || undefined;
const authorName = this.searchersValues['search-dispatcher'].trim() || undefined;
const dateString = this.searchersValues['search-date'].trim() || undefined;
const issuedFrom = this.searchersValues['search-issuedFrom'].trim() || undefined;
const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
if (driverName) queries.push(`driverName=${driverName}`);
if (trainNo)
queries.push(trainNo.startsWith('#') ? `timetableId=${trainNo.replace('#', '')}` : `trainNo=${trainNo}`);
if (authorName) queries.push(`authorName=${authorName}`);
if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`);
const queryParams: TimetablesQueryParams = {};
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
if (this.sorterActive.id == 'distance') queries.push('sortBy=routeDistance');
else if (this.sorterActive.id == 'total-stops') queries.push('sortBy=allStopsCount');
else if (this.sorterActive.id == 'beginDate') queries.push('sortBy=beginDate');
// else queries.push('sortBy=timetableId');
this.filterList
.filter((f) => f.isActive)
.forEach((f) => {
switch (f.id) {
case JournalFilterType.ABANDONED:
queryParams['fulfilled'] = 0;
queryParams['terminated'] = 1;
break;
queries.push('countLimit=15');
case JournalFilterType.ACTIVE:
queryParams['fulfilled'] = undefined;
queryParams['terminated'] = 0;
break;
switch (this.journalFilterActive.id) {
case JournalFilterType.abandoned:
queries.push('fulfilled=0', 'terminated=1');
break;
case JournalFilterType.FULFILLED:
queryParams['terminated'] = undefined;
queryParams['fulfilled'] = 1;
break;
case JournalFilterType.active:
queries.push('terminated=0');
break;
case JournalFilterType.ALL:
queryParams['terminated'] = undefined;
queryParams['fulfilled'] = undefined;
break;
case JournalFilterType.fulfilled:
queries.push('fulfilled=1');
break;
case JournalFilterType.TWR_SKR:
queryParams['twr'] = undefined;
queryParams['skr'] = undefined;
break;
default:
break;
}
case JournalFilterType.TWR:
queryParams['twr'] = 1;
queryParams['skr'] = undefined;
break;
if (this.currentQuery != queries.join('&')) this.dataStatus = DataStatus.Loading;
case JournalFilterType.SKR:
queryParams['twr'] = undefined;
queryParams['skr'] = 1;
break;
this.currentQuery = queries.join('&');
this.currentQueryArray = queries;
default:
break;
}
});
queryParams['driverName'] = driverName;
queryParams['trainNo'] = trainNo;
queryParams['countFrom'] = undefined;
queryParams['countLimit'] = undefined;
queryParams['authorName'] = authorName;
queryParams['timestampFrom'] = timestampFrom;
queryParams['timestampTo'] = timestampTo;
queryParams['issuedFrom'] = issuedFrom;
queryParams['sortBy'] = this.sorterActive.id != 'timetableId' ? this.sorterActive.id : undefined;
if (JSON.stringify(this.currentQueryParams) != JSON.stringify(queryParams)) this.dataStatus = DataStatus.Loading;
this.currentQueryParams = queryParams;
try {
const responseData: TimetableHistory[] = await (
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}`)
await axios.get(`${TIMETABLES_API_URL}`, {
params: this.currentQueryParams,
})
).data;
if (!responseData) {
@@ -302,4 +342,3 @@ export default defineComponent({
<style lang="scss" scoped>
@import '../styles/JournalSection.scss';
</style>
+1 -1
View File
@@ -1,4 +1,4 @@
<template>
<template>
<div class="scenery-view">
<div class="scenery-offline" v-if="!stationInfo && isComponentVisible && store.dataStatuses.sceneries == 2">
<div>{{ $t('scenery.no-scenery') }}</div>
+3 -3
View File
@@ -2,7 +2,7 @@
<section class="trains-view">
<div class="trains_wrapper">
<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"
/>
@@ -21,7 +21,7 @@ import modalTrainMixin from '../mixins/modalTrainMixin';
import Train from '../scripts/interfaces/Train';
import { filteredTrainList } from '../scripts/managers/trainFilterManager';
import { useStore } from '../store/store';
import { TrainFilter } from '../types/Trains/TrainOptionsTypes';
import { TrainFilter } from '../scripts/interfaces/Trains/TrainFilter';
export default defineComponent({
components: {
@@ -57,7 +57,7 @@ export default defineComponent({
const store = useStore();
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 currentOptionsActive = ref(false);
+1 -1
View File
@@ -9,7 +9,7 @@ export default defineConfig({
plugins: [
vue(),
VitePWA({
registerType: 'prompt',
registerType: 'autoUpdate',
workbox: {
globPatterns: ['**/*.{js,css,html,png,svg,jpg}'],