mirror of
https://github.com/Spythere/stacjownik.git
synced 2026-05-03 13:28:11 +00:00
statystyki ruchu online
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user