statystyki ruchu online

This commit is contained in:
2023-12-01 21:19:04 +01:00
parent b7bc9625b8
commit 23a954a0d1
8 changed files with 319 additions and 65 deletions
@@ -1,6 +1,6 @@
<template> <template>
<div class="filters-options" @keydown.esc="showOptions = false"> <div class="filters-options dropdown" @keydown.esc="showOptions = false">
<div class="bg" v-if="showOptions" @click="showOptions = false"></div> <div class="dropdown_background" v-if="showOptions" @click="showOptions = false"></div>
<div class="actions-bar"> <div class="actions-bar">
<button <button
@@ -27,8 +27,8 @@
<option v-for="(sugg, i) in dispatcherSuggestions" :key="i" :value="sugg"></option> <option v-for="(sugg, i) in dispatcherSuggestions" :key="i" :value="sugg"></option>
</datalist> </datalist>
<transition name="options-anim"> <transition name="dropdown-anim">
<div class="options_wrapper" v-if="showOptions"> <div class="dropdown_wrapper" v-if="showOptions">
<div class="options_content"> <div class="options_content">
<h1 class="option-title">{{ $t('options.search-title') }}</h1> <h1 class="option-title">{{ $t('options.search-title') }}</h1>
<div class="search_content"> <div class="search_content">
@@ -300,5 +300,6 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/filters_options.scss'; @import '../../styles/dropdown.scss';
@import '../../styles/dropdown_filters.scss';
</style> </style>
+8 -5
View File
@@ -1,6 +1,6 @@
<template> <template>
<div class="filters-options" @keydown.esc="showOptions = false"> <div class="dropdown" @keydown.esc="showOptions = false">
<div class="bg" v-if="showOptions" @click="showOptions = false"></div> <div class="dropdown_background" v-if="showOptions" @click="showOptions = false"></div>
<button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button"> <button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button">
<img src="/images/icon-filter2.svg" alt="Open filters icon" /> <img src="/images/icon-filter2.svg" alt="Open filters icon" />
@@ -8,8 +8,8 @@
<span class="active-indicator" v-if="currentOptionsActive"></span> <span class="active-indicator" v-if="currentOptionsActive"></span>
</button> </button>
<transition name="options-anim"> <transition name="dropdown-anim">
<div class="options_wrapper" v-if="showOptions"> <div class="dropdown_wrapper" v-if="showOptions">
<div class="options_content"> <div class="options_content">
<h1 class="option-title">{{ $t('options.search-title') }}</h1> <h1 class="option-title">{{ $t('options.search-title') }}</h1>
<div class="search_content"> <div class="search_content">
@@ -87,6 +87,8 @@
</button> </button>
</div> </div>
</div> </div>
<div tabindex="0" @focus="showOptions = false"></div>
</div> </div>
</transition> </transition>
</div> </div>
@@ -184,7 +186,8 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/filters_options.scss'; @import '../../styles/dropdown.scss';
@import '../../styles/dropdown_filters.scss';
.search_content > div { .search_content > div {
margin: 0.5em auto; margin: 0.5em auto;
+234
View File
@@ -0,0 +1,234 @@
<template>
<div class="dropdown" @keydown.esc="showOptions = false" @focusout="showOptions = false">
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
<button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button">
<img src="/images/icon-stats.svg" alt="Open filters icon" />
STATYSTYKI
</button>
<transition name="dropdown-anim">
<div class="dropdown_wrapper" v-if="showOptions">
<h1 class="text--primary">
<img src="/images/icon-stats.svg" alt="Open filters icon" />
STATYSTYKI ONLINE
</h1>
<hr style="margin: 0.5em 0" />
<div class="top-list">
<transition-group tag="ul" name="stats-anim">
<li class="badge" key="timetable-count">
<span>AKTYWNE RJ</span>
<span>
<b>{{ timetableCount }}</b>
</span>
</li>
<li class="badge" key="avg-speed">
<span>ŚREDNIA PRĘDKOŚĆ</span>
<span>
<b>{{ avgSpeed.toFixed(1) }} km/h</b>
</span>
</li>
<li class="badge" key="avg-speed">
<span>ŚREDNI RJ</span>
<span>
<b>{{ avgDistance.toFixed(1) }} km</b>
</span>
</li>
</transition-group>
</div>
<div class="top-list categories" v-if="store.dataStatuses.trains == 2">
<h3>Najpopularniejsze kategorie RJ</h3>
<transition-group tag="ul" name="stats-anim">
<li class="badge" v-for="top in topCategories" :key="top.name">
<span>{{ top.name }}</span>
<span>{{ top.count }}</span>
</li>
</transition-group>
</div>
<div class="top-list vehicles" v-if="store.dataStatuses.trains == 2">
<h3>Najpopularniejsze pojazdy</h3>
<transition-group tag="ul" name="stats-anim">
<li class="badge" v-for="top in topVehicles" :key="top.name">
<span>{{ top.name }}</span>
<span>{{ top.count }}</span>
</li>
</transition-group>
</div>
<div class="top-list vehicle-types" v-if="store.dataStatuses.trains == 2">
<h3>Najpopularniejsze jednostki</h3>
<transition-group tag="ul" name="stats-anim">
<li class="badge" v-for="top in topUnits" :key="top.name">
<span>{{ top.name }}</span>
<span>{{ top.count }}</span>
</li>
</transition-group>
</div>
</div>
</transition>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useStore } from '../../store/mainStore';
interface ITop {
name: string;
count: number;
}
export default defineComponent({
data() {
return {
showOptions: false,
store: useStore()
};
},
computed: {
regionTrains() {
return this.store.trainList.filter((train) => train.region == this.store.region.id);
},
regionTrainsWithTT() {
return this.regionTrains.filter((train) => train.timetableData);
},
timetableCount() {
return this.regionTrainsWithTT.length;
},
avgSpeed() {
if (this.regionTrains.length == 0) return 0;
return (
this.regionTrains.reduce((acc, train) => (acc += train.speed), 0) / this.regionTrains.length
);
},
avgDistance() {
return (
this.regionTrainsWithTT.reduce((acc, train) => {
acc += train.timetableData!.routeDistance;
return acc;
}, 0) / this.regionTrainsWithTT.length
);
},
topCategories() {
return this.regionTrainsWithTT
.reduce((acc, train) => {
const topCategory = acc.find((top) => top.name == train.timetableData!.category);
if (!topCategory) acc.push({ name: train.timetableData!.category, count: 1 });
else topCategory.count++;
return acc;
}, [] as ITop[])
.sort((c1, c2) => Math.sign(c2.count - c1.count))
.slice(0, 5);
},
topVehicles() {
return this.regionTrains
.reduce((acc, train) => {
const locoType = train.locoType.split('-')[0];
const topVehicle = acc.find((top) => top.name == locoType);
if (!topVehicle) acc.push({ name: locoType, count: 1 });
else topVehicle.count++;
return acc;
}, [] as ITop[])
.sort((c1, c2) => Math.sign(c2.count - c1.count))
.slice(0, 8);
},
topUnits() {
return this.regionTrains
.reduce((acc, train) => {
const locoType = train.locoType;
const topVehicle = acc.find((top) => top.name == locoType);
if (!topVehicle) acc.push({ name: locoType, count: 1 });
else topVehicle.count++;
return acc;
}, [] as ITop[])
.sort((c1, c2) => Math.sign(c2.count - c1.count))
.slice(0, 5);
}
},
methods: {
toggleShowOptions() {
this.showOptions = !this.showOptions;
this.$nextTick(() => {
if (this.showOptions) (this.$refs['button'] as HTMLButtonElement)?.focus();
});
}
}
});
</script>
<style lang="scss" scoped>
@import '../../styles/dropdown.scss';
@import '../../styles/badge.scss';
h1 img {
vertical-align: text-bottom;
}
h3 {
margin: 0.5em 0;
}
.top-list ul {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
}
.badge {
margin: 0;
& > span:first-child {
background-color: $accentCol;
color: black;
}
}
.dropdown_wrapper {
max-width: 600px;
}
.stats-anim {
&-move,
&-enter-active,
&-leave-active {
transition: all 250ms ease;
}
&-enter-from,
&-leave-to {
opacity: 0;
transform: translateX(5px);
}
&-leave-active {
position: absolute;
}
}
</style>
-2
View File
@@ -16,8 +16,6 @@ export default defineComponent({
}, },
methods: { methods: {
onKeyDownFunction() {},
handleKeyDown(e: KeyboardEvent) { handleKeyDown(e: KeyboardEvent) {
if (!e.key) return; if (!e.key) return;
if (e.key.toLowerCase() == 'f' && !this.preventKeyDown && !e.ctrlKey && !e.altKey) if (e.key.toLowerCase() == 'f' && !this.preventKeyDown && !e.ctrlKey && !e.altKey)
+1
View File
@@ -12,6 +12,7 @@
.journal_wrapper { .journal_wrapper {
max-width: 1350px; max-width: 1350px;
width: 100%; width: 100%;
position: relative;
margin: 0 auto; margin: 0 auto;
+46
View File
@@ -0,0 +1,46 @@
@import 'responsive.scss';
@import 'variables.scss';
.dropdown-anim {
&-enter-from,
&-leave-to {
opacity: 0;
transform: translateY(10px);
}
&-enter-active,
&-leave-active {
transition: all 150ms ease;
}
}
.dropdown_background {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 10;
}
.dropdown_wrapper {
position: absolute;
left: 0;
background-color: $bgCol;
box-shadow: 0 5px 10px 2px #0f0f0f;
width: 100%;
max-width: 550px;
padding: 1em;
z-index: 100;
}
@include smallScreen {
.dropdown_wrapper {
font-size: 1.1em;
max-width: 100%;
}
}
@@ -1,15 +1,11 @@
@import 'search_box.scss';
@import 'responsive.scss'; @import 'responsive.scss';
@import 'variables.scss'; @import 'variables.scss';
@import 'search_box.scss';
.filters-options {
margin-bottom: 0.5em;
position: relative;
}
.actions-bar { .actions-bar {
display: flex; display: flex;
gap: 0.5em; gap: 0.5em;
margin-bottom: 0.5em;
} }
h1.option-title { h1.option-title {
@@ -29,42 +25,6 @@ h1.option-title {
} }
} }
.options-anim {
&-enter-from,
&-leave-to {
opacity: 0;
transform: translateY(10px);
}
&-enter-active,
&-leave-active {
transition: all 150ms ease;
}
}
.bg {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 10;
}
.options_wrapper {
position: absolute;
background-color: $bgCol;
box-shadow: 0 5px 10px 2px #0f0f0f;
width: 100%;
max-width: 550px;
padding: 1em;
z-index: 100;
}
.options_sorters { .options_sorters {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -146,11 +106,6 @@ h1.option-title {
} }
} }
.options_wrapper {
font-size: 1.1em;
max-width: 100%;
}
.options_filters, .options_filters,
.options_sorters { .options_sorters {
justify-content: center; justify-content: center;
+22 -6
View File
@@ -1,10 +1,14 @@
<template> <template>
<section class="trains-view"> <section class="trains-view">
<div class="trains_wrapper"> <div class="trains_wrapper">
<TrainOptions <div class="trains_topbar">
:sorter-option-ids="['routeDistance', 'id', 'progress', 'delay', 'mass', 'speed', 'length']" <TrainOptions
:current-options-active="currentOptionsActive" :sorter-option-ids="sorterIds"
/> :current-options-active="currentOptionsActive"
/>
<TrainStats />
</div>
<TrainTable :trains="computedTrains" /> <TrainTable :trains="computedTrains" />
</div> </div>
@@ -20,11 +24,13 @@ import Train from '../scripts/interfaces/Train';
import { useStore } from '../store/mainStore'; import { useStore } from '../store/mainStore';
import { TrainFilter, trainFilters } from '../components/TrainsView/typings'; import { TrainFilter, trainFilters } from '../components/TrainsView/typings';
import { filteredTrainList } from '../managers/trainFilterManager'; import { filteredTrainList } from '../managers/trainFilterManager';
import TrainStats from '../components/TrainsView/TrainStats.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
TrainTable, TrainTable,
TrainOptions TrainOptions,
TrainStats
}, },
mixins: [modalTrainMixin], mixins: [modalTrainMixin],
@@ -47,7 +53,8 @@ export default defineComponent({
}, },
data: () => ({ data: () => ({
trainStatsOpen: false trainStatsOpen: false,
sorterIds: ['routeDistance', 'id', 'progress', 'delay', 'mass', 'speed', 'length']
}), }),
setup() { setup() {
@@ -121,4 +128,13 @@ export default defineComponent({
margin: 1rem auto; margin: 1rem auto;
max-width: 1350px; max-width: 1350px;
} }
.trains_topbar {
display: flex;
align-items: center;
gap: 0.5em;
position: relative;
margin-bottom: 0.5em;
}
</style> </style>