mirror of
https://github.com/Spythere/stacjownik.git
synced 2026-05-03 13:28:11 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 87631d1f74 | |||
| 86bb9fcc2e | |||
| b85e3bfe1d | |||
| dd15072813 | |||
| 2f8376c996 | |||
| 514723cf74 | |||
| 0995ce15bc | |||
| 3b3c3bda31 | |||
| 2027b85450 | |||
| 0c6b55146f | |||
| 3c728e3cfa | |||
| adce339392 | |||
| 00a4a840b0 | |||
| 1e705ea496 | |||
| e8ed36df16 | |||
| f4be32aa39 | |||
| e0d3d2585d | |||
| ebfaf06a44 | |||
| 5a651aedf8 | |||
| b66af014b9 | |||
| 634c9e1514 | |||
| c4132a9be2 | |||
| 82a9a9165f | |||
| fcac03c0a4 | |||
| 39c3cf2329 | |||
| 59f4a0cb66 | |||
| e2b42d16a4 | |||
| e23663ed28 | |||
| dc7846c31e | |||
| d875433d56 | |||
| 71e5044cb4 | |||
| e83aa40f82 | |||
| d1c0e0b898 | |||
| 26a7c69886 | |||
| 0dc2c505db | |||
| 188857d335 |
@@ -50,11 +50,6 @@
|
||||
name="twitter:image"
|
||||
content="https://raw.githubusercontent.com/Spythere/api/main/thumbnails/stacjownik.jpg"
|
||||
/>
|
||||
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Quicksand:wght@500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
Generated
+8
-9
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "stacjownik",
|
||||
"version": "1.19.0",
|
||||
"version": "1.19.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "stacjownik",
|
||||
"version": "1.19.0",
|
||||
"version": "1.19.4",
|
||||
"dependencies": {
|
||||
"core-js": "^3.32.2",
|
||||
"dotenv": "^16.3.1",
|
||||
@@ -8152,11 +8152,10 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.4.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
|
||||
"integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
|
||||
"integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.18.10",
|
||||
"postcss": "^8.4.27",
|
||||
@@ -14378,9 +14377,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"vite": {
|
||||
"version": "4.4.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
|
||||
"integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
|
||||
"integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.18.10",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stacjownik",
|
||||
"version": "1.19.1",
|
||||
"version": "1.20.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+39
-40
@@ -10,7 +10,7 @@
|
||||
|
||||
<main class="app_main">
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive exclude="JournalView,SceneryView">
|
||||
<keep-alive exclude="SceneryView">
|
||||
<component :is="Component" :key="$route.name" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
@@ -37,14 +37,15 @@ import { defineComponent, watch } from 'vue';
|
||||
import Clock from './components/App/Clock.vue';
|
||||
|
||||
import packageInfo from '.././package.json';
|
||||
import { regions } from './data/options.json';
|
||||
|
||||
import { useStore } from './store/mainStore';
|
||||
import { useMainStore } from './store/mainStore';
|
||||
import StatusIndicator from './components/App/StatusIndicator.vue';
|
||||
import TrainModal from './components/Global/TrainModal.vue';
|
||||
import AppHeader from './components/App/AppHeader.vue';
|
||||
import axios from 'axios';
|
||||
import StorageManager from './managers/storageManager';
|
||||
import { useApiStore } from './store/apiStore';
|
||||
import { Status } from './typings/common';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -56,7 +57,8 @@ export default defineComponent({
|
||||
|
||||
data: () => ({
|
||||
VERSION: packageInfo.version,
|
||||
store: useStore(),
|
||||
store: useMainStore(),
|
||||
apiStore: useApiStore(),
|
||||
|
||||
currentLang: 'pl',
|
||||
releaseURL: '',
|
||||
@@ -64,29 +66,10 @@ export default defineComponent({
|
||||
}),
|
||||
|
||||
created() {
|
||||
this.loadLang();
|
||||
this.store.setupAPI();
|
||||
|
||||
this.store.isOffline = !window.navigator.onLine;
|
||||
|
||||
window.addEventListener('offline', () => {
|
||||
this.store.isOffline = true;
|
||||
|
||||
this.store.activeData.activeSceneries = [];
|
||||
this.store.activeData.trains = [];
|
||||
this.store.activeData.connectedSocketCount = 0;
|
||||
|
||||
this.store.setStatuses();
|
||||
});
|
||||
|
||||
window.addEventListener('online', () => {
|
||||
this.store.isOffline = false;
|
||||
});
|
||||
this.init();
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.setReleaseURL();
|
||||
|
||||
watch(
|
||||
() => this.store.blockScroll,
|
||||
(value) => {
|
||||
@@ -96,23 +79,39 @@ export default defineComponent({
|
||||
);
|
||||
},
|
||||
|
||||
watch: {
|
||||
'$route.query.region': {
|
||||
immediate: true,
|
||||
handler(regionQuery: string) {
|
||||
if (regionQuery) {
|
||||
this.store.region.id =
|
||||
regions.find(
|
||||
(reg) =>
|
||||
reg.id == regionQuery.toLocaleLowerCase() ||
|
||||
reg.value.toLocaleLowerCase() == regionQuery.toLocaleLowerCase()
|
||||
)?.id || 'eu';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
this.loadLang();
|
||||
this.setReleaseURL();
|
||||
this.setupOfflineHandling();
|
||||
|
||||
this.apiStore.setupAPI();
|
||||
},
|
||||
|
||||
setupOfflineHandling() {
|
||||
this.store.isOffline = !window.navigator.onLine;
|
||||
|
||||
if (this.store.isOffline) this.handleOfflineMode();
|
||||
|
||||
window.addEventListener('offline', this.handleOfflineMode);
|
||||
window.addEventListener('online', this.handleOnlineMode);
|
||||
},
|
||||
|
||||
handleOfflineMode() {
|
||||
this.store.isOffline = true;
|
||||
|
||||
this.apiStore.stopActiveDataScheduler();
|
||||
this.apiStore.activeData = undefined;
|
||||
|
||||
this.apiStore.dataStatuses.connection = Status.Data.Offline;
|
||||
},
|
||||
|
||||
handleOnlineMode() {
|
||||
this.store.isOffline = false;
|
||||
|
||||
this.apiStore.setupAPI();
|
||||
},
|
||||
|
||||
changeLang(lang: string) {
|
||||
this.$i18n.locale = lang;
|
||||
this.currentLang = lang;
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import StatusIndicator from './StatusIndicator.vue';
|
||||
import Clock from './Clock.vue';
|
||||
import RegionDropdown from '../Global/RegionDropdown.vue';
|
||||
@@ -84,7 +84,7 @@ export default defineComponent({
|
||||
|
||||
setup() {
|
||||
return {
|
||||
store: useStore()
|
||||
store: useMainStore()
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -194,9 +194,9 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { StoreState } from '../../store/typings';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import { Status } from '../../typings/common';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
@@ -221,10 +221,11 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const store = useMainStore();
|
||||
const apiStore = useApiStore();
|
||||
|
||||
return {
|
||||
dataStatus: store.dataStatuses,
|
||||
dataStatus: apiStore.dataStatuses,
|
||||
store
|
||||
};
|
||||
},
|
||||
@@ -233,15 +234,15 @@ export default defineComponent({
|
||||
dataStatus: {
|
||||
deep: true,
|
||||
|
||||
handler(statuses: StoreState['dataStatuses']) {
|
||||
handler(statuses: any) {
|
||||
const connectionStatus = statuses.connection;
|
||||
const sceneryDataStatus = statuses.sceneries;
|
||||
const trainsDataStatus = statuses.trains;
|
||||
const dispatcherDataStatus = statuses.dispatchers;
|
||||
|
||||
if (this.store.isOffline) {
|
||||
this.setSignalStatus(Status.Data.Initialized);
|
||||
this.indicator.status = Status.Data.Initialized;
|
||||
if (connectionStatus == Status.Data.Offline) {
|
||||
this.setSignalStatus(Status.Data.Offline);
|
||||
this.indicator.status = Status.Data.Offline;
|
||||
this.indicator.message = 'data-status.S1-offline';
|
||||
return;
|
||||
}
|
||||
@@ -292,7 +293,7 @@ export default defineComponent({
|
||||
this.orangeLight = false;
|
||||
this.redBottomLight = false;
|
||||
|
||||
if (status == Status.Data.Initialized) {
|
||||
if (status == Status.Data.Initialized || status == Status.Data.Offline) {
|
||||
this.redTopLight = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['toggleModal'],
|
||||
@@ -23,7 +23,7 @@ export default defineComponent({
|
||||
|
||||
data() {
|
||||
return {
|
||||
store: useStore()
|
||||
store: useMainStore()
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, Ref, ref } from 'vue';
|
||||
import { regions as regionsJSON } from '../../data/options.json';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
|
||||
interface Item {
|
||||
id: string;
|
||||
@@ -41,7 +41,7 @@ interface Item {
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
store: useMainStore(),
|
||||
selectedItemIndex: 0,
|
||||
listOpen: false
|
||||
};
|
||||
@@ -59,6 +59,21 @@ export default defineComponent({
|
||||
'store.region.id': {
|
||||
handler(regionId) {
|
||||
this.selectedItemIndex = this.regionList.findIndex((reg) => reg.id == regionId);
|
||||
|
||||
console.log('region id', regionId);
|
||||
}
|
||||
},
|
||||
'$route.query.region': {
|
||||
immediate: true,
|
||||
handler(regionQuery: string) {
|
||||
if (regionQuery) {
|
||||
this.store.region.id =
|
||||
regionsJSON.find(
|
||||
(reg) =>
|
||||
reg.id == regionQuery.toLocaleLowerCase() ||
|
||||
reg.value.toLocaleLowerCase() == regionQuery.toLocaleLowerCase()
|
||||
)?.id || 'eu';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -50,8 +50,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { API } from '../../typings/api';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -63,15 +63,15 @@ export default defineComponent({
|
||||
|
||||
data() {
|
||||
return {
|
||||
store: useStore()
|
||||
apiStore: useApiStore()
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onImageError(event: Event, stockName: string) {
|
||||
const fallbackName =
|
||||
Object.keys(this.store.rollingStockData!.info).find((type) => {
|
||||
return this.store.rollingStockData!.info[type as keyof API.RollingStock.Info].find(
|
||||
Object.keys(this.apiStore.rollingStockData!.info).find((type) => {
|
||||
return this.apiStore.rollingStockData!.info[type as keyof API.RollingStock.Info].find(
|
||||
(v) => v[0] === stockName.split(':')[0]
|
||||
);
|
||||
}) || 'vehicle-unknown';
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { API } from '../../typings/api';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -34,7 +34,7 @@ export default defineComponent({
|
||||
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
apiStore: useApiStore(),
|
||||
isNotFound: false,
|
||||
isLoaded: false
|
||||
};
|
||||
@@ -50,11 +50,11 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
stockType() {
|
||||
if (!this.store.rollingStockData) return 'vehicle-unknown';
|
||||
if (!this.apiStore.rollingStockData) return 'vehicle-unknown';
|
||||
|
||||
return (
|
||||
Object.keys(this.store.rollingStockData.info).find((type) => {
|
||||
return this.store.rollingStockData?.info[type as keyof API.RollingStock.Info].find(
|
||||
Object.keys(this.apiStore.rollingStockData.info).find((type) => {
|
||||
return this.apiStore.rollingStockData?.info[type as keyof API.RollingStock.Info].find(
|
||||
(v) => v[0] === this.name.split(':')[0]
|
||||
);
|
||||
}) || 'vehicle-unknown'
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
<template>
|
||||
<section class="daily-stats">
|
||||
<span :data-active="statsStatus">
|
||||
<b v-if="statsStatus == Status.Data.Loading">
|
||||
{{ $t('app.loading') }}
|
||||
</b>
|
||||
|
||||
<b v-else-if="stats.distanceSum == null">
|
||||
{{ $t('journal.daily-stats-info') }}
|
||||
</b>
|
||||
|
||||
<span class="stats-list" v-else>
|
||||
<h3>
|
||||
{{ $t('journal.daily-stats-title') }}
|
||||
<b class="text--primary">{{ new Date().toLocaleDateString($i18n.locale) }}</b>
|
||||
</h3>
|
||||
<hr style="margin-bottom: 0.5em" />
|
||||
|
||||
<div v-if="stats.totalTimetables">
|
||||
•
|
||||
<i18n-t keypath="journal.timetable-stats-total">
|
||||
<template #count>
|
||||
<b class="text--primary">
|
||||
{{ stats.totalTimetables }}
|
||||
{{ $t('journal.timetable-count', stats.totalTimetables) }}
|
||||
</b>
|
||||
</template>
|
||||
|
||||
<template #distance>
|
||||
<b class="text--primary"> {{ stats.distanceSum?.toFixed(2) }} km</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div v-if="stats.maxTimetable">
|
||||
•
|
||||
<i18n-t keypath="journal.timetable-stats-longest">
|
||||
<template #id>
|
||||
<router-link :to="`/journal/timetables?timetableId=${stats.maxTimetable.id}`">
|
||||
<b>{{ stats.maxTimetable.id }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #author>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?dispatcherName=${stats.maxTimetable.authorName}`"
|
||||
>
|
||||
<b>{{ stats.maxTimetable.authorName }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #driver>
|
||||
<b class="text--primary">{{ stats.maxTimetable.driverName }}</b>
|
||||
</template>
|
||||
<template #distance>
|
||||
<b class="text--primary">{{ stats.maxTimetable.routeDistance }} km</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div v-if="topDispatchers.length == 1">
|
||||
•
|
||||
<i18n-t keypath="journal.timetable-stats-most-active-dr">
|
||||
<template #dispatcher>
|
||||
<router-link :to="`/journal/dispatchers?dispatcherName=${topDispatchers[0].name}`">
|
||||
<b>{{ topDispatchers[0].name }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #count>
|
||||
<b class="text--primary">
|
||||
{{ topDispatchers[0].count }}
|
||||
{{ $t('journal.timetable-count', topDispatchers[0].count) }}
|
||||
</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div v-if="topDispatchers.length > 1">
|
||||
•
|
||||
<i18n-t keypath="journal.timetable-stats-most-active-dr-many">
|
||||
<template #dispatchers>
|
||||
<span v-for="(disp, i) in topDispatchers" :key="i">
|
||||
<span v-if="i == topDispatchers.length - 1"> {{ $t('general.and') }} </span>
|
||||
|
||||
<router-link :to="`/journal/dispatchers?dispatcherName=${disp.name}`">
|
||||
<b>{{ disp.name }}</b>
|
||||
</router-link>
|
||||
|
||||
<span v-if="i < topDispatchers.length - 2">, </span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #count>
|
||||
<b class="text--primary">
|
||||
{{ topDispatchers[0].count }}
|
||||
{{ $t('journal.timetable-count', topDispatchers[0].count) }}
|
||||
</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div v-if="stats.longestDuties.length > 0">
|
||||
•
|
||||
<i18n-t keypath="journal.timetable-stats-longest-duties">
|
||||
<template #dispatcher>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?dispatcherName=${stats.longestDuties[0].name}`"
|
||||
>
|
||||
<b>{{ stats.longestDuties[0].name }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<template #station>{{ stats.longestDuties[0].station }}</template>
|
||||
|
||||
<template #duration>
|
||||
{{ calculateDuration(stats.longestDuties[0].duration) }}
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div v-if="stats.mostActiveDrivers.length > 0">
|
||||
•
|
||||
<i18n-t keypath="journal.timetable-stats-most-active-driver">
|
||||
<template #driver>
|
||||
<b class="text--primary">{{ stats.mostActiveDrivers[0].name }}</b>
|
||||
</template>
|
||||
<template #distance>
|
||||
<b class="text--primary">{{ stats.mostActiveDrivers[0].distance.toFixed(2) }} km</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import axios from 'axios';
|
||||
import { defineComponent } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
|
||||
import { URLs } from '../../scripts/utils/apiURLs';
|
||||
import { API } from '../../typings/api';
|
||||
import { Status } from '../../typings/common';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [dateMixin],
|
||||
emits: ['toggleStatsOpen'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
Status,
|
||||
statsStatus: Status.Data.Loading,
|
||||
intervalId: -1,
|
||||
|
||||
stats: {} as API.DailyStats.Response
|
||||
};
|
||||
},
|
||||
|
||||
activated() {
|
||||
this.startFetchingDailyStats();
|
||||
this.$emit('toggleStatsOpen', true);
|
||||
},
|
||||
|
||||
deactivated() {
|
||||
this.stopFetchingDailyStats();
|
||||
},
|
||||
|
||||
computed: {
|
||||
topDispatchers() {
|
||||
if (this.stats.mostActiveDispatchers.length == 0) return [];
|
||||
const maxCount = this.stats.mostActiveDispatchers[0].count;
|
||||
|
||||
return this.stats.mostActiveDispatchers.filter((disp) => disp.count === maxCount);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchDailyTimetableStats() {
|
||||
try {
|
||||
const res: API.DailyStats.Response = await (
|
||||
await axios.get(`${URLs.stacjownikAPI}/api/getDailyTimetableStats`)
|
||||
).data;
|
||||
|
||||
// this.stats = {
|
||||
// totalTimetables: res.totalTimetables,
|
||||
// distanceSum: res.distanceSum,
|
||||
// distanceAvg: res.distanceAvg,
|
||||
// // timetableAuthor: res.maxTimetable?.authorName || '',
|
||||
// // timetableDriver: res.maxTimetable?.driverName || '',
|
||||
// // timetableId: res.maxTimetable?.id || 0,
|
||||
// // timetableRouteDistance: res.maxTimetable?.routeDistance || 0,
|
||||
|
||||
// mostActiveDispatchers: res.mostActiveDispatchers,
|
||||
// mostActiveDrivers: res.mostActiveDrivers,
|
||||
// longestDuties: res.longestDuties
|
||||
// };
|
||||
|
||||
this.stats = res;
|
||||
|
||||
this.statsStatus = Status.Data.Loaded;
|
||||
} catch (error) {
|
||||
console.error('Ups! Wystąpił błąd podczas pobierania statystyk rozkładów jazdy...');
|
||||
this.statsStatus = Status.Data.Error;
|
||||
}
|
||||
},
|
||||
|
||||
startFetchingDailyStats() {
|
||||
this.fetchDailyTimetableStats();
|
||||
|
||||
if (this.intervalId != -1) return;
|
||||
|
||||
this.intervalId = setInterval(this.fetchDailyTimetableStats, 60000);
|
||||
},
|
||||
|
||||
stopFetchingDailyStats() {
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = -1;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/responsive.scss';
|
||||
|
||||
.daily-stats {
|
||||
text-align: left;
|
||||
}
|
||||
.daily-stats > span[data-active='0'] {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.stats-list a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@include smallScreen {
|
||||
h3 {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,165 +0,0 @@
|
||||
<template>
|
||||
<div class="stats_container" v-click-outside="() => (cardVisible = false)">
|
||||
<button class="stats_button" @click="toggleCard">
|
||||
Statystyki dyżurnego {{ store.dispatcherStatsName }}
|
||||
</button>
|
||||
|
||||
<div class="stats_card" v-if="store.dispatcherStatsName && cardVisible">
|
||||
<div>
|
||||
<Loading v-if="!store.dispatcherStatsData" />
|
||||
|
||||
<div class="loading" v-else-if="!store.dispatcherStatsData._count._all">
|
||||
Ten dyżurny nie ma jeszcze szczegółowych statystyk!
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<h3>STATYSTYKI WYSTAWIONYCH ROZKŁADÓW</h3>
|
||||
|
||||
<div class="info-stats" v-if="store.dispatcherStatsData._count._all">
|
||||
<span class="stat-badge">
|
||||
<span>LICZBA</span>
|
||||
<span>{{ store.dispatcherStatsData._count._all }}</span>
|
||||
</span>
|
||||
<span class="stat-badge">
|
||||
<span>SUMA (KM)</span>
|
||||
<span>{{ store.dispatcherStatsData._sum.routeDistance.toFixed(2) }}km</span>
|
||||
</span>
|
||||
<span class="stat-badge">
|
||||
<span>NAJDŁUŻSZY</span>
|
||||
<span>{{ store.dispatcherStatsData._max.routeDistance.toFixed(2) }}km</span>
|
||||
</span>
|
||||
<span class="stat-badge">
|
||||
<span>ŚREDNIO</span>
|
||||
<span>{{ store.dispatcherStatsData._avg.routeDistance.toFixed(2) }}km</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h3>OSTATNIE WYSTAWIONE ROZKŁADY</h3>
|
||||
<div class="last-timetables">
|
||||
<div class="timetable-row" v-for="timetable in timetables" :key="timetable.id">
|
||||
#{{ timetable.timetableId }} |
|
||||
<b>{{ timetable.trainCategoryCode }} {{ timetable.trainNo }}</b> |
|
||||
{{ timetable.driverName }} ({{ timetable.routeDistance }}km)
|
||||
<div>{{ timetable.route.replace('|', ' > ') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import axios from 'axios';
|
||||
import { defineComponent } from 'vue';
|
||||
import { URLs } from '../../scripts/utils/apiURLs';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import Loading from '../Global/Loading.vue';
|
||||
import { API } from '../../typings/api';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Loading },
|
||||
|
||||
setup() {
|
||||
const store = useStore();
|
||||
|
||||
return {
|
||||
store
|
||||
};
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
cardVisible: false,
|
||||
lastDispatcherName: '',
|
||||
timetables: [] as API.TimetableHistory.Response
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleCard() {
|
||||
if (!this.store.dispatcherStatsName) return;
|
||||
|
||||
this.cardVisible = !this.cardVisible;
|
||||
if (this.cardVisible) this.fetchDispatcherStats();
|
||||
},
|
||||
|
||||
async fetchDispatcherStats() {
|
||||
if (this.lastDispatcherName != this.store.dispatcherStatsName) {
|
||||
this.store.dispatcherStatsData = undefined;
|
||||
}
|
||||
|
||||
const statsData: API.DispatcherStats.Response = await (
|
||||
await axios.get(
|
||||
`${URLs.stacjownikAPI}/api/getDispatcherInfo?name=${this.store.dispatcherStatsName}`
|
||||
)
|
||||
).data;
|
||||
|
||||
const timetables: API.TimetableHistory.Response = await (
|
||||
await axios.get(
|
||||
`${URLs.stacjownikAPI}/api/getTimetables?authorName=${this.store.dispatcherStatsName}`
|
||||
)
|
||||
).data;
|
||||
|
||||
this.timetables = timetables;
|
||||
this.store.dispatcherStatsData = statsData;
|
||||
this.lastDispatcherName = this.store.dispatcherStatsName;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/responsive.scss';
|
||||
@import '../../styles/variables.scss';
|
||||
|
||||
.stats_container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.stats_card {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
top: 120%;
|
||||
right: 0;
|
||||
width: 500px;
|
||||
max-width: 97vw;
|
||||
min-height: 100px;
|
||||
overflow: auto;
|
||||
|
||||
border-radius: 1em 0 1em 1em;
|
||||
background-color: #222222f1;
|
||||
box-shadow: 0 3px 10px 5px #131313;
|
||||
padding: 1em 0.5em;
|
||||
}
|
||||
|
||||
.last-timetables {
|
||||
max-height: 400px;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.timetable-row {
|
||||
width: 95%;
|
||||
margin: 0.5em auto;
|
||||
padding: 0.5em;
|
||||
|
||||
background-color: #4d4d4d;
|
||||
}
|
||||
|
||||
h2.card-title {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.last-timetables {
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<section class="daily-stats">
|
||||
<span :data-active="statsStatus">
|
||||
<span class="stats-list">
|
||||
<h3>
|
||||
{{ $t('journal.daily-stats.title') }}
|
||||
<b class="text--primary">{{ new Date().toLocaleDateString($i18n.locale) }}</b>
|
||||
</h3>
|
||||
|
||||
<hr class="header-separator" />
|
||||
|
||||
<b v-if="statsStatus == Status.Data.Loading">
|
||||
{{ $t('app.loading') }}
|
||||
</b>
|
||||
|
||||
<b class="text--error" v-else-if="statsStatus == Status.Data.Error">
|
||||
{{ $t('journal.stats-error') }}
|
||||
</b>
|
||||
|
||||
<b v-else-if="topDispatchers.length == 0">
|
||||
{{ $t('journal.daily-stats.info') }}
|
||||
</b>
|
||||
|
||||
<div v-else>
|
||||
<div v-if="stats.totalTimetables">
|
||||
•
|
||||
<i18n-t keypath="journal.daily-stats.total">
|
||||
<template #count>
|
||||
<b class="text--primary">
|
||||
{{ stats.totalTimetables }}
|
||||
{{ $t('journal.daily-stats.count', stats.totalTimetables) }}
|
||||
</b>
|
||||
</template>
|
||||
|
||||
<template #distance>
|
||||
<b class="text--primary"> {{ stats.distanceSum?.toFixed(2) }} km</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div v-if="stats.maxTimetable">
|
||||
•
|
||||
<i18n-t keypath="journal.daily-stats.longest">
|
||||
<template #id>
|
||||
<router-link :to="`/journal/timetables?search-train=%23${stats.maxTimetable.id}`">
|
||||
<b>{{ stats.maxTimetable.id }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #author>
|
||||
<router-link
|
||||
:to="`/journal/timetables?search-dispatcher=${stats.maxTimetable.authorName}`"
|
||||
>
|
||||
<b>{{ stats.maxTimetable.authorName }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #driver>
|
||||
<b class="text--primary">{{ stats.maxTimetable.driverName }}</b>
|
||||
</template>
|
||||
<template #distance>
|
||||
<b class="text--primary">{{ stats.maxTimetable.routeDistance }} km</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div v-if="topDispatchers.length == 1">
|
||||
•
|
||||
<i18n-t keypath="journal.daily-stats.most-active-dr">
|
||||
<template #dispatcher>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?search-dispatcher=${topDispatchers[0].name}`"
|
||||
>
|
||||
<b>{{ topDispatchers[0].name }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #count>
|
||||
<b class="text--primary">
|
||||
{{ topDispatchers[0].count }}
|
||||
{{ $t('journal.daily-stats.count', topDispatchers[0].count) }}
|
||||
</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div v-if="topDispatchers.length > 1">
|
||||
•
|
||||
<i18n-t keypath="journal.daily-stats.most-active-dr-many">
|
||||
<template #dispatchers>
|
||||
<span v-for="(disp, i) in topDispatchers" :key="i">
|
||||
<span v-if="i == topDispatchers.length - 1"> {{ $t('general.and') }} </span>
|
||||
|
||||
<router-link :to="`/journal/dispatchers?search-dispatcher=${disp.name}`">
|
||||
<b>{{ disp.name }}</b>
|
||||
</router-link>
|
||||
|
||||
<span v-if="i < topDispatchers.length - 2">, </span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<template #count>
|
||||
<b class="text--primary">
|
||||
{{ topDispatchers[0].count }}
|
||||
{{ $t('journal.daily-stats.count', topDispatchers[0].count) }}
|
||||
</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div v-if="stats.longestDuties.length > 0">
|
||||
•
|
||||
<i18n-t keypath="journal.daily-stats.longest-duties">
|
||||
<template #dispatcher>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?search-dispatcher=${stats.longestDuties[0].name}`"
|
||||
>
|
||||
<b>{{ stats.longestDuties[0].name }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<template #station>{{ stats.longestDuties[0].station }}</template>
|
||||
|
||||
<template #duration>
|
||||
{{ calculateDuration(stats.longestDuties[0].duration) }}
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div v-if="stats.mostActiveDrivers.length > 0">
|
||||
•
|
||||
<i18n-t keypath="journal.daily-stats.most-active-driver">
|
||||
<template #driver>
|
||||
<router-link
|
||||
:to="`/journal/timetables?search-driver=${stats.mostActiveDrivers[0].name}`"
|
||||
>
|
||||
<b>{{ stats.mostActiveDrivers[0].name }}</b>
|
||||
</router-link>
|
||||
</template>
|
||||
<template #distance>
|
||||
<b class="text--primary">{{ stats.mostActiveDrivers[0].distance.toFixed(2) }} km</b>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<hr class="section-separator" />
|
||||
|
||||
<div class="stats-badges">
|
||||
<span
|
||||
class="stat-badge"
|
||||
v-for="key in [
|
||||
'rippedSwitches',
|
||||
'derailments',
|
||||
'skippedStopSignals',
|
||||
'radioStops',
|
||||
'kills'
|
||||
]"
|
||||
:key="key"
|
||||
>
|
||||
<span>{{ $t(`journal.daily-stats.${key}`) }}</span>
|
||||
<span>{{
|
||||
Object.entries(stats.globalDiff).find(([k, v]) => k == key)?.[1] || '--'
|
||||
}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
|
||||
import { API } from '../../typings/api';
|
||||
import { Status } from '../../typings/common';
|
||||
import http from '../../http';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'journal-daily-stats',
|
||||
|
||||
mixins: [dateMixin],
|
||||
// emits: ['toggleStatsOpen'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
Status,
|
||||
statsStatus: Status.Data.Loading,
|
||||
intervalId: -1,
|
||||
|
||||
stats: {} as API.DailyStats.Response
|
||||
};
|
||||
},
|
||||
|
||||
activated() {
|
||||
this.startFetchingDailyStats();
|
||||
// this.$emit('toggleStatsOpen', true);
|
||||
},
|
||||
|
||||
deactivated() {
|
||||
this.stopFetchingDailyStats();
|
||||
},
|
||||
|
||||
computed: {
|
||||
topDispatchers() {
|
||||
if (this.stats.mostActiveDispatchers.length == 0) return [];
|
||||
const maxCount = this.stats.mostActiveDispatchers[0].count;
|
||||
|
||||
return this.stats.mostActiveDispatchers.filter((disp) => disp.count === maxCount);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchDailyTimetableStats() {
|
||||
try {
|
||||
const res: API.DailyStats.Response = await (await http.get('api/getDailyStats')).data;
|
||||
|
||||
this.stats = res;
|
||||
|
||||
this.statsStatus = Status.Data.Loaded;
|
||||
} catch (error) {
|
||||
console.error('Ups! Wystąpił błąd podczas pobierania statystyk rozkładów jazdy...');
|
||||
this.statsStatus = Status.Data.Error;
|
||||
}
|
||||
},
|
||||
|
||||
startFetchingDailyStats() {
|
||||
this.fetchDailyTimetableStats();
|
||||
|
||||
if (this.intervalId != -1) return;
|
||||
|
||||
this.intervalId = window.setInterval(this.fetchDailyTimetableStats, 60000);
|
||||
},
|
||||
|
||||
stopFetchingDailyStats() {
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = -1;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/responsive.scss';
|
||||
@import '../../styles/JournalStats.scss';
|
||||
@import '../../styles/badge.scss';
|
||||
|
||||
.daily-stats {
|
||||
text-align: left;
|
||||
}
|
||||
.daily-stats > span[data-active='0'] {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.stats-list a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.stats-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
@include smallScreen {
|
||||
h3 {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="journal-stats dispatcher" v-if="dispatcherName && stats">
|
||||
<span class="loading" v-if="!stats.issuedTimetables && !stats.services">
|
||||
{{ $t('journal.dispatcher-stats.empty') }}
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
<h3>
|
||||
<i18n-t keypath="journal.dispatcher-stats.title">
|
||||
<template #name>
|
||||
<span class="text--primary">{{ dispatcherName.toUpperCase() }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</h3>
|
||||
|
||||
<hr class="header-separator" />
|
||||
|
||||
<div class="info-stats">
|
||||
<span class="stat-badge" v-if="stats.services">
|
||||
<span>{{ $t('journal.dispatcher-stats.services-count') }}</span>
|
||||
<span>{{ stats.services.count }}</span>
|
||||
</span>
|
||||
|
||||
<span class="stat-badge" v-if="stats.services">
|
||||
<span>{{ $t('journal.dispatcher-stats.service-max') }}</span>
|
||||
<span>{{ calculateDuration(stats.services.durationMax) }}</span>
|
||||
</span>
|
||||
|
||||
<span class="stat-badge" v-if="stats.services">
|
||||
<span>{{ $t('journal.dispatcher-stats.service-avg') }}</span>
|
||||
<span>{{ calculateDuration(stats.services.durationAvg) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<hr class="section-separator" />
|
||||
|
||||
<div class="info-stats">
|
||||
<span class="stat-badge" v-if="stats.issuedTimetables">
|
||||
<span>{{ $t('journal.dispatcher-stats.timetables-count') }}</span>
|
||||
<span>{{ stats.issuedTimetables.count }}</span>
|
||||
</span>
|
||||
|
||||
<span class="stat-badge" v-if="stats.issuedTimetables">
|
||||
<span>{{ $t('journal.dispatcher-stats.timetables-sum') }}</span>
|
||||
<span>{{ stats.issuedTimetables.distanceSum.toFixed(2) }}km</span>
|
||||
</span>
|
||||
|
||||
<span class="stat-badge" v-if="stats.issuedTimetables">
|
||||
<span>{{ $t('journal.dispatcher-stats.timetables-max') }}</span>
|
||||
<span>{{ stats.issuedTimetables.distanceMax.toFixed(2) }}km</span>
|
||||
</span>
|
||||
|
||||
<span class="stat-badge" v-if="stats.issuedTimetables">
|
||||
<span>{{ $t('journal.dispatcher-stats.timetables-avg') }}</span>
|
||||
<span>{{ stats.issuedTimetables.distanceAvg.toFixed(2) }}km</span>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import dateMixin from '../../../mixins/dateMixin';
|
||||
import { useMainStore } from '../../../store/mainStore';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'journal-dispatcher-stats',
|
||||
|
||||
mixins: [dateMixin],
|
||||
|
||||
setup() {
|
||||
const store = useMainStore();
|
||||
|
||||
return {
|
||||
stats: store.dispatcherStatsData,
|
||||
dispatcherName: store.dispatcherStatsName
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../styles/JournalStats.scss';
|
||||
</style>
|
||||
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<transition name="status-anim" mode="out-in">
|
||||
<div :key="dataStatus">
|
||||
<div class="journal_warning" v-if="store.isOffline">
|
||||
{{ $t('app.offline') }}
|
||||
</div>
|
||||
|
||||
<Loading v-else-if="dataStatus == Status.Data.Loading" />
|
||||
|
||||
<div v-else-if="dataStatus == Status.Data.Error" class="journal_warning error">
|
||||
{{ $t('app.error') }}
|
||||
</div>
|
||||
|
||||
<div class="journal_warning" v-else-if="dispatcherHistory.length == 0">
|
||||
{{ $t('app.no-result') }}
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<table class="dispatchers-table">
|
||||
<thead>
|
||||
<th>{{ $t('journal.history-name') }}</th>
|
||||
<th>{{ $t('journal.history-hash') }}</th>
|
||||
<th>{{ $t('journal.history-dispatcher') }}</th>
|
||||
<th>{{ $t('journal.history-level') }}</th>
|
||||
<th>{{ $t('journal.history-rate') }}</th>
|
||||
<th>{{ $t('journal.history-region') }}</th>
|
||||
<th>{{ $t('journal.history-date') }}</th>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<transition-group name="list-anim">
|
||||
<tr v-for="historyItem in dispatcherHistory" :key="historyItem.id">
|
||||
<td>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?search-station=${historyItem.stationName}`"
|
||||
>
|
||||
<b>{{ historyItem.stationName }}</b>
|
||||
</router-link>
|
||||
</td>
|
||||
<td>#{{ historyItem.stationHash }}</td>
|
||||
<td>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
||||
>
|
||||
<b
|
||||
v-if="isDonator(historyItem.dispatcherName)"
|
||||
class="text--donator"
|
||||
:title="$t('donations.dispatcher-message')"
|
||||
>
|
||||
{{ historyItem.dispatcherName }}
|
||||
</b>
|
||||
|
||||
<b v-else>
|
||||
{{ historyItem.dispatcherName }}
|
||||
</b>
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
<b
|
||||
v-if="historyItem.dispatcherLevel !== null"
|
||||
class="level-badge dispatcher"
|
||||
:style="
|
||||
calculateExpStyle(
|
||||
historyItem.dispatcherLevel,
|
||||
historyItem.dispatcherIsSupporter
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
||||
</b>
|
||||
</td>
|
||||
<td class="text--primary">
|
||||
<b>{{ historyItem.dispatcherRate }}</b>
|
||||
</td>
|
||||
<td>
|
||||
<b class="region-badge" :aria-describedby="historyItem.region">{{
|
||||
regions.find((r) => r.id == historyItem.region)?.value || '???'
|
||||
}}</b>
|
||||
</td>
|
||||
<td style="min-width: 200px" class="time">
|
||||
<span v-if="historyItem.timestampTo" class="text--offline">
|
||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
||||
{{ timestampToString(historyItem.timestampFrom) }}
|
||||
- {{ timestampToString(historyItem.timestampTo) }} ({{
|
||||
calculateDuration(historyItem.currentDuration)
|
||||
}})
|
||||
</span>
|
||||
<span class="dispatcher-online" v-else>
|
||||
<b class="text--online">
|
||||
<router-link :to="`/scenery?station=${historyItem.stationName}`">{{
|
||||
$t('journal.online-since')
|
||||
}}</router-link>
|
||||
{{ timestampToString(historyItem.timestampFrom) }}
|
||||
</b>
|
||||
({{ calculateDuration(historyItem.currentDuration) }})
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</transition-group>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<AddDataButton
|
||||
:list="dispatcherHistory"
|
||||
:scrollDataLoaded="scrollDataLoaded"
|
||||
:scrollNoMoreData="scrollNoMoreData"
|
||||
@addHistoryData="addHistoryData"
|
||||
/>
|
||||
</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>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { regions } from '../../../data/options.json';
|
||||
import { useMainStore } from '../../../store/mainStore';
|
||||
import { API } from '../../../typings/api';
|
||||
import { Status } from '../../../typings/common';
|
||||
import Loading from '../../Global/Loading.vue';
|
||||
import AddDataButton from '../../Global/AddDataButton.vue';
|
||||
import dateMixin from '../../../mixins/dateMixin';
|
||||
import donatorMixin from '../../../mixins/donatorMixin';
|
||||
import styleMixin from '../../../mixins/styleMixin';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Loading, AddDataButton },
|
||||
|
||||
mixins: [dateMixin, styleMixin, donatorMixin],
|
||||
|
||||
props: {
|
||||
dispatcherHistory: {
|
||||
type: Array as PropType<API.DispatcherHistory.Response>,
|
||||
required: true
|
||||
},
|
||||
scrollNoMoreData: {
|
||||
type: Boolean
|
||||
},
|
||||
scrollDataLoaded: {
|
||||
type: Boolean
|
||||
},
|
||||
addHistoryData: {
|
||||
type: Function as PropType<() => void>
|
||||
},
|
||||
dataStatus: {
|
||||
type: Number as PropType<Status.Data>
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
Status,
|
||||
store: useMainStore(),
|
||||
regions
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
computedDispatcherHistory() {
|
||||
return this.dispatcherHistory.reduce(
|
||||
(acc, historyItem, i) => {
|
||||
if (this.isAnotherDay(i - 1, i))
|
||||
acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL'));
|
||||
acc.push(historyItem);
|
||||
|
||||
return acc;
|
||||
},
|
||||
[] as (API.DispatcherHistory.Data | string)[]
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
navigateToScenery(name: string, isOnline: boolean) {
|
||||
if (!isOnline) return;
|
||||
|
||||
this.$router.push(`/scenery?station=${name.trim().replace(/ /g, '_')}`);
|
||||
},
|
||||
|
||||
isAnotherDay(prevIndex: number, currIndex: number) {
|
||||
if (currIndex == 0) return true;
|
||||
|
||||
return (
|
||||
new Date(this.dispatcherHistory[prevIndex].timestampFrom).getDate() !=
|
||||
new Date(this.dispatcherHistory[currIndex].timestampFrom).getDate()
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../../styles/animations.scss';
|
||||
@import '../../../styles/responsive.scss';
|
||||
@import '../../../styles/badge.scss';
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/JournalSection.scss';
|
||||
|
||||
table.dispatchers-table {
|
||||
--_bg-table: #111;
|
||||
--_bg-head: #101010;
|
||||
--_bg-row: #2f2f2f;
|
||||
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
margin-bottom: 1em;
|
||||
|
||||
thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--_bg-head);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
tr {
|
||||
background-color: var(--_bg-row);
|
||||
border-bottom: 2px solid black;
|
||||
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.75em;
|
||||
|
||||
.level-badge {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
&--online {
|
||||
color: springgreen;
|
||||
}
|
||||
|
||||
&--offline {
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,260 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<transition name="status-anim" mode="out-in">
|
||||
<div :key="dataStatus">
|
||||
<div class="journal_warning" v-if="store.isOffline">
|
||||
{{ $t('app.offline') }}
|
||||
</div>
|
||||
|
||||
<Loading v-else-if="dataStatus == Status.Data.Loading" />
|
||||
|
||||
<div v-else-if="dataStatus == Status.Data.Error" class="journal_warning error">
|
||||
{{ $t('app.error') }}
|
||||
</div>
|
||||
|
||||
<div class="journal_warning" v-else-if="dispatcherHistory.length == 0">
|
||||
{{ $t('app.no-result') }}
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<table class="scenery-history-table">
|
||||
<thead>
|
||||
<th>{{ $t('journal.history-name') }}</th>
|
||||
<th>{{ $t('journal.history-hash') }}</th>
|
||||
<th>{{ $t('journal.history-dispatcher') }}</th>
|
||||
<th>{{ $t('journal.history-level') }}</th>
|
||||
<th>{{ $t('journal.history-rate') }}</th>
|
||||
<th>{{ $t('journal.history-region') }}</th>
|
||||
<th>{{ $t('journal.history-date') }}</th>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<transition-group name="list-anim">
|
||||
<tr v-for="historyItem in dispatcherHistory" :key="historyItem.id">
|
||||
<td>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?sceneryName=${historyItem.stationName}`"
|
||||
>
|
||||
<b>{{ historyItem.stationName }}</b>
|
||||
</router-link>
|
||||
</td>
|
||||
<td>#{{ historyItem.stationHash }}</td>
|
||||
<td>
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`"
|
||||
>
|
||||
<b
|
||||
v-if="isDonator(historyItem.dispatcherName)"
|
||||
class="text--donator"
|
||||
:title="$t('donations.dispatcher-message')"
|
||||
>
|
||||
{{ historyItem.dispatcherName }}
|
||||
</b>
|
||||
|
||||
<b v-else>
|
||||
{{ historyItem.dispatcherName }}
|
||||
</b>
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
<b
|
||||
v-if="historyItem.dispatcherLevel !== null"
|
||||
class="level-badge dispatcher"
|
||||
:style="
|
||||
calculateExpStyle(
|
||||
historyItem.dispatcherLevel,
|
||||
historyItem.dispatcherIsSupporter
|
||||
)
|
||||
"
|
||||
>
|
||||
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
||||
</b>
|
||||
</td>
|
||||
<td class="text--primary">
|
||||
<b>{{ historyItem.dispatcherRate }}</b>
|
||||
</td>
|
||||
<td>
|
||||
<b class="region-badge" :aria-describedby="historyItem.region">{{
|
||||
regions.find((r) => r.id == historyItem.region)?.value || '???'
|
||||
}}</b>
|
||||
</td>
|
||||
<td style="min-width: 200px" class="time">
|
||||
<span v-if="historyItem.timestampTo" class="text--offline">
|
||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
||||
{{ timestampToString(historyItem.timestampFrom) }}
|
||||
- {{ timestampToString(historyItem.timestampTo) }} ({{
|
||||
calculateDuration(historyItem.currentDuration)
|
||||
}})
|
||||
</span>
|
||||
<span class="dispatcher-online" v-else>
|
||||
<b class="text--online">
|
||||
<router-link :to="`/scenery?station=${historyItem.stationName}`">{{
|
||||
$t('journal.online-since')
|
||||
}}</router-link>
|
||||
{{ timestampToString(historyItem.timestampFrom) }}
|
||||
</b>
|
||||
({{ calculateDuration(historyItem.currentDuration) }})
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</transition-group>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<AddDataButton
|
||||
:list="dispatcherHistory"
|
||||
:scrollDataLoaded="scrollDataLoaded"
|
||||
:scrollNoMoreData="scrollNoMoreData"
|
||||
@addHistoryData="addHistoryData"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import styleMixin from '../../mixins/styleMixin';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import Loading from '../Global/Loading.vue';
|
||||
import { regions } from '../../data/options.json';
|
||||
import AddDataButton from '../Global/AddDataButton.vue';
|
||||
import { API } from '../../typings/api';
|
||||
import { Status } from '../../typings/common';
|
||||
import donatorMixin from '../../mixins/donatorMixin';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Loading, AddDataButton },
|
||||
|
||||
mixins: [dateMixin, styleMixin, donatorMixin],
|
||||
|
||||
props: {
|
||||
dispatcherHistory: {
|
||||
type: Array as PropType<API.DispatcherHistory.Response>,
|
||||
required: true
|
||||
},
|
||||
scrollNoMoreData: {
|
||||
type: Boolean
|
||||
},
|
||||
scrollDataLoaded: {
|
||||
type: Boolean
|
||||
},
|
||||
addHistoryData: {
|
||||
type: Function as PropType<() => void>
|
||||
},
|
||||
dataStatus: {
|
||||
type: Number as PropType<Status.Data>
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
Status,
|
||||
store: useStore(),
|
||||
regions
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
computedDispatcherHistory() {
|
||||
console.log(this.dispatcherHistory.length);
|
||||
|
||||
return this.dispatcherHistory.reduce(
|
||||
(acc, historyItem, i) => {
|
||||
if (this.isAnotherDay(i - 1, i))
|
||||
acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL'));
|
||||
acc.push(historyItem);
|
||||
|
||||
return acc;
|
||||
},
|
||||
[] as (API.DispatcherHistory.Data | string)[]
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
navigateToScenery(name: string, isOnline: boolean) {
|
||||
if (!isOnline) return;
|
||||
|
||||
this.$router.push(`/scenery?station=${name.trim().replace(/ /g, '_')}`);
|
||||
},
|
||||
|
||||
isAnotherDay(prevIndex: number, currIndex: number) {
|
||||
if (currIndex == 0) return true;
|
||||
|
||||
return (
|
||||
new Date(this.dispatcherHistory[prevIndex].timestampFrom).getDate() !=
|
||||
new Date(this.dispatcherHistory[currIndex].timestampFrom).getDate()
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/animations.scss';
|
||||
@import '../../styles/responsive.scss';
|
||||
@import '../../styles/badge.scss';
|
||||
@import '../../styles/variables.scss';
|
||||
@import '../../styles/JournalSection.scss';
|
||||
|
||||
table.scenery-history-table {
|
||||
--_bg-table: #111;
|
||||
--_bg-head: #101010;
|
||||
--_bg-row: #2f2f2f;
|
||||
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
margin-bottom: 1em;
|
||||
|
||||
thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--_bg-head);
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
tr {
|
||||
background-color: var(--_bg-row);
|
||||
border-bottom: 2px solid black;
|
||||
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.75em;
|
||||
|
||||
.level-badge {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
&--online {
|
||||
color: springgreen;
|
||||
}
|
||||
|
||||
&--offline {
|
||||
color: #ddd;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -33,7 +33,7 @@
|
||||
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
||||
<div class="search_content">
|
||||
<div class="search" v-for="(_, propName) in searchersValues" :key="propName">
|
||||
<label v-if="propName == 'search-date'" for="date">{{
|
||||
<label v-if="propName == 'search-date'" for="search-date">{{
|
||||
$t(`options.search-${optionsType}-date`)
|
||||
}}</label>
|
||||
|
||||
@@ -41,12 +41,13 @@
|
||||
<input
|
||||
class="search-input"
|
||||
v-model="searchersValues[propName]"
|
||||
@keydown.enter="onSearchConfirm"
|
||||
@keydown.enter="searchConfirm"
|
||||
@focus="preventKeyDown = true"
|
||||
@blur="preventKeyDown = false"
|
||||
:placeholder="$t(`options.${propName}`)"
|
||||
:type="propName == 'search-date' ? 'date' : 'text'"
|
||||
:min="propName == 'search-date' ? '2022-02-01' : undefined"
|
||||
:id="`${propName}`"
|
||||
:list="propName.toString()"
|
||||
/>
|
||||
|
||||
@@ -110,14 +111,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import axios from 'axios';
|
||||
import { defineComponent, inject, PropType } from 'vue';
|
||||
import keyMixin from '../../mixins/keyMixin';
|
||||
import { URLs } from '../../scripts/utils/apiURLs';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import { Journal } from './typings';
|
||||
import { API } from '../../typings/api';
|
||||
import { Status } from '../../typings/common';
|
||||
import http from '../../http';
|
||||
|
||||
export default defineComponent({
|
||||
emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'],
|
||||
@@ -158,7 +157,7 @@ export default defineComponent({
|
||||
dispatcherSuggestions: [] as string[],
|
||||
|
||||
searchTimeout: 0,
|
||||
store: useStore(),
|
||||
store: useMainStore(),
|
||||
|
||||
JournalFilterSection: Journal.FilterSection
|
||||
};
|
||||
@@ -182,12 +181,6 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
watch: {
|
||||
async 'store.driverStatsName'() {
|
||||
await this.fetchDriverStats();
|
||||
|
||||
// if (value) this.store.currentStatsTab = 'driver';
|
||||
},
|
||||
|
||||
async 'searchersValues.search-driver'(value: string | undefined) {
|
||||
clearTimeout(this.searchTimeout);
|
||||
|
||||
@@ -206,29 +199,34 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchDriverStats() {
|
||||
this.store.driverStatsData = undefined;
|
||||
// filters & sorters from URL params
|
||||
handleRouteParams() {
|
||||
this.$router.push({
|
||||
query: {
|
||||
...this.$route.query,
|
||||
'sorter-active':
|
||||
this.sorterOptionIds.indexOf(`${this.sorterActive.id}`) != 0
|
||||
? this.sorterActive.id
|
||||
: undefined,
|
||||
...Object.keys(this.searchersValues).reduce(
|
||||
(acc, k) => {
|
||||
const searchVal = this.searchersValues[k as Journal.TimetableSearchKey];
|
||||
|
||||
if (!this.store.driverStatsName) {
|
||||
this.store.driverStatsStatus = Status.Data.Initialized;
|
||||
return;
|
||||
}
|
||||
acc[k] = searchVal || undefined;
|
||||
|
||||
try {
|
||||
this.store.driverStatsStatus = Status.Data.Loading;
|
||||
|
||||
const statsData: API.DriverStats.Response = await (
|
||||
await axios.get(
|
||||
`${URLs.stacjownikAPI}/api/getDriverInfo?name=${this.store.driverStatsName}`
|
||||
return acc;
|
||||
},
|
||||
{} as { [k: string]: string | undefined }
|
||||
),
|
||||
...this.filterList?.reduce(
|
||||
(acc, f) => {
|
||||
if (f.isActive) acc[f.filterSection] = f.default ? undefined : f.id;
|
||||
return acc;
|
||||
},
|
||||
{} as { [k: string]: string | undefined }
|
||||
)
|
||||
).data;
|
||||
|
||||
this.store.driverStatsData = statsData;
|
||||
this.store.driverStatsStatus = Status.Data.Loaded;
|
||||
} catch (error) {
|
||||
this.store.driverStatsStatus = Status.Data.Error;
|
||||
console.error('Ups! Wystąpił błąd przy próbie pobrania statystyk maszynisty! :/');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refreshData() {
|
||||
@@ -240,17 +238,17 @@ export default defineComponent({
|
||||
|
||||
window.clearTimeout(this.searchTimeout);
|
||||
|
||||
this.searchTimeout = setTimeout(async () => {
|
||||
this.searchTimeout = window.setTimeout(async () => {
|
||||
try {
|
||||
const suggestions: string[] = await (
|
||||
await axios.get(`${URLs.stacjownikAPI}/api/get${type}Suggestions?name=${value}`)
|
||||
await http.get(`api/get${type}Suggestions?name=${value}`)
|
||||
).data;
|
||||
|
||||
this[`${type}Suggestions`] = suggestions;
|
||||
} catch (error) {
|
||||
this[`${type}Suggestions`] = [];
|
||||
}
|
||||
}, 450);
|
||||
}, 250);
|
||||
},
|
||||
|
||||
// Override keyMixin function
|
||||
@@ -265,7 +263,7 @@ export default defineComponent({
|
||||
onSorterChange(item: { id: string | number; value: string }) {
|
||||
this.sorterActive.id = item.id;
|
||||
this.sorterActive.dir = -1;
|
||||
this.$emit('onSearchConfirm');
|
||||
this.searchConfirm();
|
||||
},
|
||||
|
||||
onFilterChange(filter: Journal.TimetableFilter) {
|
||||
@@ -275,25 +273,27 @@ export default defineComponent({
|
||||
.forEach((f) => (f.isActive = false));
|
||||
filter.isActive = true;
|
||||
|
||||
this.$emit('onSearchConfirm');
|
||||
this.searchConfirm();
|
||||
},
|
||||
|
||||
onInputClear(id: any) {
|
||||
this.searchersValues[id] = '';
|
||||
this.$emit('onSearchConfirm');
|
||||
this.searchConfirm();
|
||||
},
|
||||
|
||||
onSearchConfirm() {
|
||||
searchConfirm() {
|
||||
this.$emit('onSearchConfirm');
|
||||
this.handleRouteParams();
|
||||
},
|
||||
|
||||
onSearchButtonConfirm() {
|
||||
this.showOptions = false;
|
||||
this.$emit('onSearchConfirm');
|
||||
this.searchConfirm();
|
||||
},
|
||||
|
||||
onResetButtonClick() {
|
||||
this.$emit('onOptionsReset');
|
||||
this.handleRouteParams();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,122 +1,85 @@
|
||||
<template>
|
||||
<div class="journal-stats" v-if="!store.isOffline">
|
||||
<div class="tabs">
|
||||
<div
|
||||
class="journal-stats dropdown"
|
||||
v-if="!mainStore.isOffline"
|
||||
@keydown.esc="currentStatsTab = null"
|
||||
>
|
||||
<div
|
||||
class="dropdown_background"
|
||||
v-if="currentStatsTab !== null"
|
||||
@click="currentStatsTab = null"
|
||||
></div>
|
||||
|
||||
<div class="actions-bar">
|
||||
<button
|
||||
v-for="tab in data.tabs"
|
||||
:key="tab.name"
|
||||
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)"
|
||||
v-for="button in statsButtons"
|
||||
:key="button.tab"
|
||||
class="btn--filled btn--image"
|
||||
:data-selected="button.tab == currentStatsTab"
|
||||
:data-disabled="button.disabled"
|
||||
:disabled="button.disabled"
|
||||
@click="onTabButtonClick(button.tab)"
|
||||
>
|
||||
{{ $t(tab.titlePath) }}
|
||||
<img
|
||||
v-if="button.iconName"
|
||||
:src="`/images/icon-${button.iconName}.svg`"
|
||||
:alt="button.iconName"
|
||||
/>
|
||||
{{ $t(button.localeKey) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="stats-tab" v-show="areStatsOpen">
|
||||
<keep-alive>
|
||||
<JournalDailyStats
|
||||
v-if="store.currentStatsTab == 'daily'"
|
||||
@toggleStatsOpen="toggleStatsOpen"
|
||||
/>
|
||||
<JournalDriverStats v-else-if="store.currentStatsTab == 'driver'" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
<transition name="dropdown-anim">
|
||||
<div class="dropdown_wrapper" v-if="currentStatsTab !== null">
|
||||
<keep-alive>
|
||||
<component :is="currentStatsTab" :key="currentStatsTab"></component>
|
||||
</keep-alive>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, Ref, ref, watch } from 'vue';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import JournalDailyStats from './DailyStats.vue';
|
||||
import JournalDriverStats from './JournalDriverStats.vue';
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import StorageManager from '../../managers/storageManager';
|
||||
import { Journal } from './typings';
|
||||
import JournalDailyStats from './JournalDailyStats.vue';
|
||||
import JournalDispatcherStats from '../JournalView/JournalDispatchers/JournalDispatcherStats.vue';
|
||||
import JournalDriverStats from '../JournalView/JournalTimetables/JournalDriverStats.vue';
|
||||
|
||||
// Types
|
||||
type TStatTab = 'daily' | 'driver';
|
||||
|
||||
// Variables
|
||||
const store = useStore();
|
||||
|
||||
const lastDailyStatsOpen = ref(false);
|
||||
const areStatsOpen = ref(false);
|
||||
const lastClickedTab: Ref<'daily' | 'driver' | null> = ref(null);
|
||||
|
||||
let data = reactive({
|
||||
tabs: [
|
||||
{
|
||||
name: 'daily',
|
||||
titlePath: 'journal.daily-stats-title'
|
||||
},
|
||||
{
|
||||
name: 'driver',
|
||||
titlePath: 'journal.driver-stats-title'
|
||||
// inactive: true,
|
||||
export default defineComponent({
|
||||
components: { JournalDailyStats, JournalDriverStats, JournalDispatcherStats },
|
||||
props: {
|
||||
statsButtons: {
|
||||
type: Array as PropType<Journal.StatsButton[]>,
|
||||
required: true
|
||||
}
|
||||
] as { name: TStatTab; titlePath: string; inactive?: boolean }[]
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
Journal,
|
||||
mainStore: useMainStore(),
|
||||
currentStatsTab: null as Journal.StatsTab | null
|
||||
};
|
||||
},
|
||||
|
||||
// Methods
|
||||
function onTabButtonClick(tab: TStatTab) {
|
||||
if (lastClickedTab.value == tab || !lastClickedTab.value || !areStatsOpen.value)
|
||||
areStatsOpen.value = !areStatsOpen.value;
|
||||
methods: {
|
||||
onTabButtonClick(tab: Journal.StatsTab) {
|
||||
this.currentStatsTab = tab == this.currentStatsTab ? null : tab;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function toggleStatsOpen(open: boolean) {
|
||||
areStatsOpen.value = open;
|
||||
}
|
||||
|
||||
watch(
|
||||
computed(() => store.driverStatsData),
|
||||
(statsData) => {
|
||||
store.currentStatsTab = statsData ? 'driver' : lastClickedTab.value;
|
||||
areStatsOpen.value = statsData ? true : lastClickedTab.value !== null;
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (StorageManager.getBooleanValue('dailyStatsOpen')) {
|
||||
areStatsOpen.value = true;
|
||||
store.currentStatsTab = 'daily';
|
||||
StorageManager.setStringValue('journalStatsTab', this.currentStatsTab ?? '');
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/JournalStats.scss';
|
||||
@import '../../styles/dropdown.scss';
|
||||
@import '../../styles/dropdown_filters.scss';
|
||||
@import '../../styles/variables.scss';
|
||||
|
||||
.tabs {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
button {
|
||||
font-weight: bold;
|
||||
padding: 0.5em 0.75em;
|
||||
|
||||
&[data-inactive='true'] {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
&[data-selected='true'] {
|
||||
color: $accentCol;
|
||||
}
|
||||
}
|
||||
.dropdown_wrapper {
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
+20
-21
@@ -1,14 +1,19 @@
|
||||
<template>
|
||||
<div class="journal-stats">
|
||||
<span v-if="store.driverStatsData">
|
||||
<div class="journal-stats driver" v-if="store.driverStatsData">
|
||||
<span>
|
||||
<h3>
|
||||
{{ $t('journal.stats-title') }}
|
||||
<span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
|
||||
<i18n-t keypath="journal.driver-stats.title">
|
||||
<template #name>
|
||||
<span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</h3>
|
||||
|
||||
<hr class="header-separator" />
|
||||
|
||||
<div class="info-stats">
|
||||
<span class="stat-badge">
|
||||
<span>{{ $t('journal.stats-timetables') }}</span>
|
||||
<span>{{ $t('journal.driver-stats.timetables') }}</span>
|
||||
<span
|
||||
>{{ store.driverStatsData._count.fulfilled }} /
|
||||
{{ store.driverStatsData._count._all }}</span
|
||||
@@ -16,17 +21,17 @@
|
||||
</span>
|
||||
|
||||
<span class="stat-badge">
|
||||
<span>{{ $t('journal.stats-longest-timetable') }}</span>
|
||||
<span>{{ $t('journal.driver-stats.longest-timetable') }}</span>
|
||||
<span> {{ store.driverStatsData._max.routeDistance.toFixed(2) }}km </span>
|
||||
</span>
|
||||
|
||||
<span class="stat-badge">
|
||||
<span>{{ $t('journal.stats-avg-timetable') }}</span>
|
||||
<span>{{ $t('journal.driver-stats.avg-timetable') }}</span>
|
||||
<span> {{ store.driverStatsData._avg.routeDistance.toFixed(2) }}km </span>
|
||||
</span>
|
||||
|
||||
<span class="stat-badge">
|
||||
<span>{{ $t('journal.stats-distance') }}</span>
|
||||
<span>{{ $t('journal.driver-stats.distance') }}</span>
|
||||
<span>
|
||||
{{ store.driverStatsData._sum.currentDistance.toFixed(2) }} /
|
||||
{{ store.driverStatsData._sum.routeDistance.toFixed(2) }}km
|
||||
@@ -34,7 +39,7 @@
|
||||
</span>
|
||||
|
||||
<span class="stat-badge">
|
||||
<span>{{ $t('journal.stats-stations') }}</span>
|
||||
<span>{{ $t('journal.driver-stats.stations') }}</span>
|
||||
<span>
|
||||
{{ store.driverStatsData._sum.confirmedStopsCount }} /
|
||||
{{ store.driverStatsData._sum.allStopsCount }}
|
||||
@@ -42,26 +47,20 @@
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<b v-else-if="store.driverStatsStatus == Status.Data.Loading">{{
|
||||
$t('journal.stats-loading')
|
||||
}}</b>
|
||||
<b v-else-if="store.driverStatsStatus == Status.Data.Error">
|
||||
{{ $t('journal.stats-error ') }}
|
||||
</b>
|
||||
<b v-else>{{ $t('journal.driver-stats-info') }}</b>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { Status } from '../../typings/common';
|
||||
import { useMainStore } from '../../../store/mainStore';
|
||||
import { Status } from '../../../typings/common';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'journal-driver-stats',
|
||||
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
store: useMainStore(),
|
||||
Status: Status
|
||||
};
|
||||
}
|
||||
@@ -69,5 +68,5 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/JournalStats.scss';
|
||||
@import '../../../styles/JournalStats.scss';
|
||||
</style>
|
||||
@@ -42,7 +42,7 @@ import { defineComponent, PropType } from 'vue';
|
||||
import Loading from '../../Global/Loading.vue';
|
||||
import AddDataButton from '../../Global/AddDataButton.vue';
|
||||
import TimetableHistoryList from './TimetableHistoryList.vue';
|
||||
import { useStore } from '../../../store/mainStore';
|
||||
import { useMainStore } from '../../../store/mainStore';
|
||||
import { Status } from '../../../typings/common';
|
||||
import { API } from '../../../typings/api';
|
||||
|
||||
@@ -71,7 +71,7 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
Status,
|
||||
store: useStore()
|
||||
store: useMainStore()
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -111,16 +111,17 @@ export default defineComponent({
|
||||
|
||||
gap: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
@include smallScreen() {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.info-date {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.badges {
|
||||
display: flex;
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
.info-badge {
|
||||
padding: 0.05em 0.35em;
|
||||
color: black;
|
||||
@@ -142,7 +143,14 @@ export default defineComponent({
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
@include smallScreen {
|
||||
.item-general {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
export namespace Journal {
|
||||
export type DispatcherSearcher = {
|
||||
[key in 'search-dispatcher' | 'search-station' | 'search-date']: string;
|
||||
};
|
||||
|
||||
export interface DispatcherSorter {
|
||||
id: 'timestampFrom' | 'duration';
|
||||
dir: -1 | 1;
|
||||
}
|
||||
export type DispatcherSearchKey = 'search-dispatcher' | 'search-station' | 'search-date';
|
||||
|
||||
export type TimetableSearchKey =
|
||||
| 'search-driver'
|
||||
@@ -19,11 +12,29 @@ export namespace Journal {
|
||||
[key in TimetableSearchKey]: string;
|
||||
};
|
||||
|
||||
export type DispatcherSearchType = {
|
||||
[key in DispatcherSearchKey]: string;
|
||||
};
|
||||
|
||||
export type TimetableSorterKey = 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
|
||||
export type DispatcherSorterKey = 'timestampFrom' | 'duration';
|
||||
|
||||
export interface DispatcherSorter {
|
||||
id: DispatcherSorterKey;
|
||||
dir: -1 | 1;
|
||||
}
|
||||
|
||||
export interface TimetableSorter {
|
||||
id: TimetableSorterKey;
|
||||
dir: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
export const enum TimetableFilterId {
|
||||
ALL_STATUSES = 'all-statuses',
|
||||
ACTIVE = 'active',
|
||||
FULFILLED = 'fulfilled',
|
||||
ABANDONED = 'abandoned',
|
||||
ALL = 'all',
|
||||
ALL_SPECIALS = 'all-specials',
|
||||
TWR = 'twr',
|
||||
SKR = 'skr',
|
||||
TWR_SKR = 'twr-skr'
|
||||
@@ -31,19 +42,26 @@ export namespace Journal {
|
||||
|
||||
export enum FilterSection {
|
||||
TIMETABLE_STATUS = 'timetable-status',
|
||||
TWRSKR = 'twrskr'
|
||||
SPECIAL = 'special'
|
||||
}
|
||||
|
||||
export interface TimetableFilter {
|
||||
id: TimetableFilterId;
|
||||
filterSection: string;
|
||||
isActive: boolean;
|
||||
default: boolean;
|
||||
}
|
||||
|
||||
export type TimetableSorterKey = 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
|
||||
export enum StatsTab {
|
||||
DRIVER_STATS = 'journal-driver-stats',
|
||||
DISPATCHER_STATS = 'journal-dispatcher-stats',
|
||||
DAILY_STATS = 'journal-daily-stats'
|
||||
}
|
||||
|
||||
export interface TimetableSorter {
|
||||
id: TimetableSorterKey;
|
||||
dir: 'asc' | 'desc';
|
||||
export interface StatsButton {
|
||||
tab: StatsTab;
|
||||
localeKey: string;
|
||||
iconName: string;
|
||||
disabled: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
<tr v-for="historyItem in historyList" :key="historyItem.id">
|
||||
<td>#{{ historyItem.stationHash }}</td>
|
||||
<td>
|
||||
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
|
||||
<router-link
|
||||
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
|
||||
>
|
||||
<b>{{ historyItem.dispatcherName }}</b>
|
||||
</router-link>
|
||||
</td>
|
||||
@@ -33,6 +35,8 @@
|
||||
>
|
||||
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
||||
</b>
|
||||
|
||||
<b v-else>?</b>
|
||||
</td>
|
||||
<td class="text--primary">
|
||||
<b>{{ historyItem.dispatcherRate }}</b>
|
||||
@@ -66,17 +70,16 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import axios from 'axios';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import Station from '../../scripts/interfaces/Station';
|
||||
import { URLs } from '../../scripts/utils/apiURLs';
|
||||
import Loading from '../Global/Loading.vue';
|
||||
import styleMixin from '../../mixins/styleMixin';
|
||||
import listObserverMixin from '../../mixins/listObserverMixin';
|
||||
import { OnlineScenery } from '../../store/typings';
|
||||
import { API } from '../../typings/api';
|
||||
import { Status } from '../../typings/common';
|
||||
import http from '../../http';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SceneryDispatchersHistory',
|
||||
@@ -84,12 +87,10 @@ export default defineComponent({
|
||||
components: { Loading },
|
||||
props: {
|
||||
station: {
|
||||
type: Object as PropType<Station>,
|
||||
required: true
|
||||
type: Object as PropType<Station>
|
||||
},
|
||||
onlineScenery: {
|
||||
type: Object as PropType<OnlineScenery>,
|
||||
required: false
|
||||
type: Object as PropType<OnlineScenery>
|
||||
}
|
||||
},
|
||||
|
||||
@@ -113,12 +114,20 @@ export default defineComponent({
|
||||
countFrom = 0,
|
||||
countLimit = 30
|
||||
): Promise<API.DispatcherHistory.Response | null> {
|
||||
if (!this.station && !this.onlineScenery) {
|
||||
this.dataStatus = Status.Data.Loaded;
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
this.dataStatus = Status.Data.Loading;
|
||||
|
||||
const requestString = `${URLs.stacjownikAPI}/api/getDispatchers?stationName=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
||||
const requestString = `api/getDispatchers?stationName=${
|
||||
this.station?.name || this.onlineScenery?.name
|
||||
}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
||||
|
||||
const historyAPIData: API.DispatcherHistory.Response = await (
|
||||
await axios.get(requestString)
|
||||
await http.get(requestString)
|
||||
).data;
|
||||
|
||||
this.dataStatus = Status.Data.Loaded;
|
||||
@@ -130,7 +139,9 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
navigateToHistory() {
|
||||
this.$router.push(`/journal/dispatchers?sceneryName=${this.station.name}`);
|
||||
this.$router.push(
|
||||
`/journal/dispatchers?search-station=${this.station?.name || this.onlineScenery?.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<section class="info-header">
|
||||
<a class="scenery-name" :href="station.generalInfo?.url" target="_blank">
|
||||
{{ station.name }}
|
||||
<a class="scenery-name" :href="station?.generalInfo?.url" target="_blank">
|
||||
{{ stationName.replace(/_/g, ' ') }}
|
||||
</a>
|
||||
|
||||
<div class="scenery-abbrev">
|
||||
{{ $t('scenery.abbrev') }} <b>{{ station.generalInfo?.abbr }}</b>
|
||||
<div class="scenery-abbrev" v-if="station?.generalInfo?.abbr">
|
||||
{{ $t('scenery.abbrev') }} <b>{{ station.generalInfo.abbr }}</b>
|
||||
</div>
|
||||
|
||||
<div class="scenery-hash" v-if="onlineScenery?.hash">#{{ onlineScenery.hash }}</div>
|
||||
@@ -20,13 +20,16 @@ import { OnlineScenery } from '../../store/typings';
|
||||
export default defineComponent({
|
||||
props: {
|
||||
station: {
|
||||
type: Object as PropType<Station>,
|
||||
type: Object as PropType<Station>
|
||||
},
|
||||
|
||||
stationName: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
|
||||
onlineScenery: {
|
||||
type: Object as PropType<OnlineScenery>,
|
||||
required: false
|
||||
type: Object as PropType<OnlineScenery>
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -58,4 +61,3 @@ export default defineComponent({
|
||||
font-size: 1.2em;
|
||||
}
|
||||
</style>
|
||||
../../store/storeTypes
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="scenery-info">
|
||||
<section>
|
||||
<div class="scenery-info-general" v-if="station.generalInfo">
|
||||
<div class="scenery-info-general">
|
||||
<SceneryInfoIcons :station="station" />
|
||||
|
||||
<div class="scenery-general-list">
|
||||
<div class="scenery-general-list" v-if="station?.generalInfo">
|
||||
<span>
|
||||
<b>{{ $t('availability.title') }}:</b>
|
||||
{{ $t(`availability.${station.generalInfo.availability}`) }}
|
||||
@@ -46,11 +46,11 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<SceneryInfoRoutes :station="station" />
|
||||
<SceneryInfoRoutes v-if="station" :station="station" />
|
||||
|
||||
<div
|
||||
class="scenery-authors"
|
||||
v-if="station.generalInfo.authors && station.generalInfo.authors.length > 0"
|
||||
v-if="station?.generalInfo?.authors && station.generalInfo.authors.length > 0"
|
||||
>
|
||||
<b>
|
||||
{{
|
||||
@@ -102,13 +102,11 @@ export default defineComponent({
|
||||
},
|
||||
props: {
|
||||
station: {
|
||||
type: Object as PropType<Station>,
|
||||
required: true
|
||||
type: Object as PropType<Station>
|
||||
},
|
||||
|
||||
onlineScenery: {
|
||||
type: Object as PropType<OnlineScenery>,
|
||||
required: false
|
||||
type: Object as PropType<OnlineScenery>
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<router-link
|
||||
class="dispatcher_name"
|
||||
:to="`/journal/dispatchers?dispatcherName=${onlineScenery.dispatcherName}`"
|
||||
:to="`/journal/dispatchers?search-dispatcher=${onlineScenery.dispatcherName}`"
|
||||
>
|
||||
<span
|
||||
class="text--donator"
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
<template>
|
||||
<section class="info-icons">
|
||||
<span
|
||||
v-if="station.generalInfo && station.generalInfo.reqLevel >= 0"
|
||||
class="scenery-icon icon-info level"
|
||||
:style="calculateExpStyle(station.generalInfo.reqLevel)"
|
||||
>
|
||||
{{ station.generalInfo.reqLevel >= 2 ? station.generalInfo.reqLevel : 'L' }}
|
||||
<span v-if="!station || !station.generalInfo">
|
||||
<img
|
||||
class="icon-info"
|
||||
src="/images/icon-unknown.svg"
|
||||
alt="icon-unknown"
|
||||
:title="$t('desc.unknown')"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="station.generalInfo"
|
||||
v-if="station?.generalInfo && station?.generalInfo.reqLevel >= 0"
|
||||
class="scenery-icon icon-info level"
|
||||
:style="calculateExpStyle(station?.generalInfo.reqLevel)"
|
||||
>
|
||||
{{ station?.generalInfo.reqLevel >= 2 ? station?.generalInfo.reqLevel : 'L' }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="station?.generalInfo"
|
||||
class="scenery-icon icon-info"
|
||||
:class="station.generalInfo.controlType.replace('+', '-')"
|
||||
:title="$t('desc.control-type') + $t(`controls.${station.generalInfo.controlType}`)"
|
||||
v-html="getControlTypeAbbrev(station.generalInfo.controlType)"
|
||||
:class="station?.generalInfo.controlType.replace('+', '-')"
|
||||
:title="$t('desc.control-type') + $t(`controls.${station?.generalInfo.controlType}`)"
|
||||
v-html="getControlTypeAbbrev(station?.generalInfo.controlType)"
|
||||
>
|
||||
</span>
|
||||
|
||||
<img
|
||||
v-if="station.generalInfo?.SUP"
|
||||
v-if="station?.generalInfo?.SUP"
|
||||
class="icon-info"
|
||||
src="/images/icon-SUP.svg"
|
||||
alt="SUP (RASP-UZK)"
|
||||
@@ -26,7 +35,7 @@
|
||||
/>
|
||||
|
||||
<img
|
||||
v-if="station.generalInfo?.signalType"
|
||||
v-if="station?.generalInfo?.signalType"
|
||||
class="icon-info"
|
||||
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
|
||||
:alt="station.generalInfo.signalType"
|
||||
@@ -34,7 +43,7 @@
|
||||
/>
|
||||
|
||||
<img
|
||||
v-if="station.generalInfo?.availability == 'nonPublic'"
|
||||
v-if="station?.generalInfo?.availability == 'nonPublic'"
|
||||
class="icon-info"
|
||||
src="/images/icon-lock.svg"
|
||||
alt="Non-public scenery"
|
||||
@@ -42,7 +51,7 @@
|
||||
/>
|
||||
|
||||
<img
|
||||
v-if="station.generalInfo?.availability == 'unavailable'"
|
||||
v-if="station?.generalInfo?.availability == 'unavailable'"
|
||||
class="icon-info"
|
||||
src="/images/icon-unavailable.svg"
|
||||
alt="Unavailable scenery"
|
||||
@@ -50,7 +59,7 @@
|
||||
/>
|
||||
|
||||
<img
|
||||
v-if="station.generalInfo?.availability == 'abandoned'"
|
||||
v-if="station?.generalInfo?.availability == 'abandoned'"
|
||||
class="icon-info"
|
||||
src="/images/icon-abandoned.svg"
|
||||
alt="Abandoned scenery"
|
||||
@@ -58,20 +67,12 @@
|
||||
/>
|
||||
|
||||
<img
|
||||
v-if="station.generalInfo?.lines"
|
||||
v-if="station?.generalInfo?.lines"
|
||||
class="icon-info"
|
||||
src="/images/icon-real.svg"
|
||||
alt="real scenery"
|
||||
:title="`${$t('desc.real')} ${station.generalInfo.lines}`"
|
||||
/>
|
||||
|
||||
<img
|
||||
v-if="!station.generalInfo"
|
||||
class="icon-info"
|
||||
src="/images/icon-unknown.svg"
|
||||
alt="icon-unknown"
|
||||
:title="$t('desc.unknown')"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -85,8 +86,7 @@ export default defineComponent({
|
||||
mixins: [stationInfoMixin, styleMixin],
|
||||
props: {
|
||||
station: {
|
||||
type: Object as PropType<Station>,
|
||||
required: true
|
||||
type: Object as PropType<Station>
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="header_links">
|
||||
<a
|
||||
<span class="header_links" v-if="station">
|
||||
<!-- <a
|
||||
:href="`https://pragotron-td2.web.app/board?name=${station.name}`"
|
||||
target="_blank"
|
||||
:title="$t('scenery.pragotron-link')"
|
||||
>
|
||||
<img src="/images/icon-pragotron.svg" alt="icon-pragotron" />
|
||||
</a>
|
||||
</a> -->
|
||||
|
||||
<a :href="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')">
|
||||
<img src="/images/icon-tablice.ico" alt="icon-tablice" />
|
||||
@@ -48,7 +48,7 @@
|
||||
<transition-group name="list-anim">
|
||||
<div
|
||||
style="padding-bottom: 5em"
|
||||
v-if="store.dataStatuses.trains == 0 && computedScheduledTrains.length == 0"
|
||||
v-if="apiStore.dataStatuses.connection == 0 && computedScheduledTrains.length == 0"
|
||||
key="list-loading"
|
||||
>
|
||||
<Loading />
|
||||
@@ -187,10 +187,11 @@ import Loading from '../Global/Loading.vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import routerMixin from '../../mixins/routerMixin';
|
||||
import Station from '../../scripts/interfaces/Station';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
|
||||
import { OnlineScenery } from '../../store/typings';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SceneryTimetable',
|
||||
@@ -201,12 +202,10 @@ export default defineComponent({
|
||||
|
||||
props: {
|
||||
station: {
|
||||
type: Object as PropType<Station>,
|
||||
required: true
|
||||
type: Object as PropType<Station>
|
||||
},
|
||||
onlineScenery: {
|
||||
type: Object as PropType<OnlineScenery>,
|
||||
required: false
|
||||
type: Object as PropType<OnlineScenery>
|
||||
}
|
||||
},
|
||||
|
||||
@@ -226,7 +225,8 @@ export default defineComponent({
|
||||
const route = useRoute();
|
||||
const currentURL = computed(() => `${location.origin}${route.fullPath}`);
|
||||
|
||||
const store = useStore();
|
||||
const apiStore = useApiStore();
|
||||
const mainStore = useMainStore();
|
||||
|
||||
const chosenCheckpoint = ref(
|
||||
props.station?.generalInfo?.checkpoints?.length == 0
|
||||
@@ -237,25 +237,29 @@ export default defineComponent({
|
||||
return {
|
||||
currentURL,
|
||||
chosenCheckpoint,
|
||||
store
|
||||
apiStore,
|
||||
mainStore
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
tabliceZbiorczeHref() {
|
||||
let url = `https://tablice-td2.web.app/?station=${this.station.name}`;
|
||||
let url = `https://tablice-td2.web.app/?station=${this.station!.name}`;
|
||||
if (this.chosenCheckpoint) url += `&checkpoint=${this.chosenCheckpoint}`;
|
||||
|
||||
return url;
|
||||
},
|
||||
|
||||
computedScheduledTrains() {
|
||||
if (!this.station) return [];
|
||||
|
||||
return (
|
||||
this.onlineScenery?.scheduledTrains
|
||||
?.filter(
|
||||
(train) =>
|
||||
train.checkpointName.toLocaleLowerCase() ==
|
||||
(this.chosenCheckpoint || this.station.name).toLocaleLowerCase()
|
||||
(this.chosenCheckpoint || this.station!.name).toLocaleLowerCase() &&
|
||||
train.region == this.mainStore.region.id
|
||||
)
|
||||
.sort((a, b) => {
|
||||
if (a.stopStatusID > b.stopStatusID) return 1;
|
||||
@@ -272,6 +276,8 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
loadSelectedOption() {
|
||||
if (!this.station) return;
|
||||
|
||||
this.chosenCheckpoint =
|
||||
this.station.generalInfo?.checkpoints[0]?.checkpointName || this.station.name;
|
||||
},
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<tbody>
|
||||
<tr v-for="historyItem in historyList" :key="historyItem.id">
|
||||
<td>
|
||||
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">
|
||||
<router-link :to="`/journal/timetables?search-train=%23${historyItem.id}`">
|
||||
#{{ historyItem.id }}
|
||||
</router-link>
|
||||
</td>
|
||||
@@ -37,11 +37,16 @@
|
||||
{{ historyItem.trainNo }}
|
||||
</td>
|
||||
<td>{{ historyItem.route.replace('|', ' -> ') }}</td>
|
||||
<td>{{ historyItem.driverName }}</td>
|
||||
<td>
|
||||
<router-link :to="`/journal/timetables?search-driver=${historyItem.driverName}`">
|
||||
{{ historyItem.driverName }}
|
||||
</router-link>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<router-link
|
||||
v-if="historyItem.authorName"
|
||||
:to="`/journal/timetables?authorName=${historyItem.authorName}`"
|
||||
:to="`/journal/timetables?search-dispatcher=${historyItem.authorName}`"
|
||||
>{{ historyItem.authorName }}
|
||||
</router-link>
|
||||
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
||||
@@ -63,29 +68,26 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import axios from 'axios';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
|
||||
import Station from '../../scripts/interfaces/Station';
|
||||
import { URLs } from '../../scripts/utils/apiURLs';
|
||||
import Loading from '../Global/Loading.vue';
|
||||
import listObserverMixin from '../../mixins/listObserverMixin';
|
||||
import { OnlineScenery } from '../../store/typings';
|
||||
import { API } from '../../typings/api';
|
||||
import { Status } from '../../typings/common';
|
||||
import http from '../../http';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SceneryTimetablesHistory',
|
||||
mixins: [dateMixin, listObserverMixin],
|
||||
props: {
|
||||
station: {
|
||||
type: Object as PropType<Station>,
|
||||
required: true
|
||||
type: Object as PropType<Station>
|
||||
},
|
||||
onlineScenery: {
|
||||
type: Object as PropType<OnlineScenery>,
|
||||
required: false
|
||||
type: Object as PropType<OnlineScenery>
|
||||
}
|
||||
},
|
||||
|
||||
@@ -102,11 +104,20 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchAPIData(countFrom = 0, countLimit = 15) {
|
||||
try {
|
||||
const requestString = `${URLs.stacjownikAPI}/api/getTimetables?issuedFrom=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
||||
async fetchAPIData() {
|
||||
if (!this.station && !this.onlineScenery) {
|
||||
this.dataStatus = Status.Data.Loaded;
|
||||
return;
|
||||
}
|
||||
|
||||
const response: API.TimetableHistory.Response = await (await axios.get(requestString)).data;
|
||||
try {
|
||||
const response: API.TimetableHistory.Response = await (
|
||||
await http.get('api/getTimetables', {
|
||||
params: {
|
||||
issuedFrom: this.station?.name
|
||||
}
|
||||
})
|
||||
).data;
|
||||
|
||||
this.historyList = response;
|
||||
|
||||
@@ -117,7 +128,12 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
navigateToHistory() {
|
||||
this.$router.push(`/journal/timetables?issuedFrom=${this.station.name}`);
|
||||
this.$router.push({
|
||||
path: '/journal/timetables',
|
||||
query: {
|
||||
'search-issuedFrom': this.station?.name || this.onlineScenery?.name
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
components: { Loading }
|
||||
|
||||
@@ -139,7 +139,7 @@ import { defineComponent, inject } from 'vue';
|
||||
import keyMixin from '../../mixins/keyMixin';
|
||||
import routerMixin from '../../mixins/routerMixin';
|
||||
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
|
||||
import FilterOption from './FilterOption.vue';
|
||||
import StorageManager from '../../managers/storageManager';
|
||||
@@ -163,7 +163,7 @@ export default defineComponent({
|
||||
|
||||
setup() {
|
||||
const isVisible = inject('isFilterCardVisible');
|
||||
const store = useStore();
|
||||
const store = useMainStore();
|
||||
const filterStore = useStationFiltersStore();
|
||||
|
||||
return {
|
||||
@@ -447,7 +447,7 @@ export default defineComponent({
|
||||
|
||||
.section-inputs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.5em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
<td class="station_dispatcher-name">
|
||||
<span v-if="station.onlineInfo?.dispatcherName">
|
||||
<b
|
||||
v-if="store.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
||||
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
||||
:title="$t('donations.dispatcher-message')"
|
||||
@click.stop="openDonationModal"
|
||||
>
|
||||
@@ -279,7 +279,7 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<Loading v-if="!isDataLoaded && stations.length == 0" />
|
||||
<Loading v-if="apiStore.dataStatuses.connection == Status.Loading" />
|
||||
|
||||
<div class="no-stations" v-else-if="stations.length == 0">
|
||||
{{ $t('sceneries.no-stations') }}
|
||||
@@ -288,17 +288,18 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, PropType } from 'vue';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import stationInfoMixin from '../../mixins/stationInfoMixin';
|
||||
import styleMixin from '../../mixins/styleMixin';
|
||||
import Station from '../../scripts/interfaces/Station';
|
||||
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import Loading from '../Global/Loading.vue';
|
||||
import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationHeaderNames';
|
||||
import StationStatusBadge from '../Global/StationStatusBadge.vue';
|
||||
import { Status } from '../../typings/common';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -325,17 +326,15 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const mainStore = useMainStore();
|
||||
const apiStore = useApiStore();
|
||||
const stationFiltersStore = useStationFiltersStore();
|
||||
|
||||
const isDataLoaded = computed(() => {
|
||||
return store.dataStatuses.sceneries != Status.Data.Loading;
|
||||
});
|
||||
|
||||
return {
|
||||
isDataLoaded,
|
||||
Status: Status.Data,
|
||||
stationFiltersStore,
|
||||
store
|
||||
mainStore,
|
||||
apiStore
|
||||
};
|
||||
},
|
||||
|
||||
@@ -357,7 +356,7 @@ export default defineComponent({
|
||||
|
||||
openDonationModal(e: Event) {
|
||||
this.$emit('toggleDonationModal', true);
|
||||
this.store.modalLastClickedTarget = e.target;
|
||||
this.mainStore.modalLastClickedTarget = e.target;
|
||||
},
|
||||
|
||||
openForumSite(e: Event, url: string | undefined) {
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
<div class="train-driver">
|
||||
<b
|
||||
v-if="store.donatorsData.includes(train.driverName)"
|
||||
v-if="apiStore.donatorsData.includes(train.driverName)"
|
||||
:title="$t('donations.driver-message')"
|
||||
>
|
||||
{{ train.driverName }}
|
||||
@@ -126,7 +126,8 @@ import trainInfoMixin from '../../mixins/trainInfoMixin';
|
||||
import Train from '../../scripts/interfaces/Train';
|
||||
import ProgressBar from '../Global/ProgressBar.vue';
|
||||
import TrainThumbnail from '../Global/TrainThumbnail.vue';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [trainInfoMixin, styleMixin],
|
||||
@@ -145,7 +146,8 @@ export default defineComponent({
|
||||
|
||||
data() {
|
||||
return {
|
||||
store: useStore()
|
||||
store: useMainStore(),
|
||||
apiStore: useApiStore()
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
import { computed, defineComponent, PropType } from 'vue';
|
||||
import dateMixin from '../../mixins/dateMixin';
|
||||
import Train from '../../scripts/interfaces/Train';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import StopDate from '../Global/StopDate.vue';
|
||||
import StockList from '../Global/StockList.vue';
|
||||
import { TrainStop } from '../../store/typings';
|
||||
@@ -92,7 +92,7 @@ export default defineComponent({
|
||||
|
||||
setup(props) {
|
||||
return {
|
||||
store: useStore(),
|
||||
store: useMainStore(),
|
||||
|
||||
lastConfirmed: computed(() => {
|
||||
return props.train.timetableData!.followingStops.findIndex(
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<hr style="margin: 0.5em 0" />
|
||||
|
||||
<div v-if="store.dataStatuses.trains == Status.Loaded && regionTrains.length > 0">
|
||||
<div v-if="apiStore.dataStatuses.connection == Status.Loaded && regionTrains.length > 0">
|
||||
<div class="top-list general">
|
||||
<transition-group tag="ul" name="stats-anim">
|
||||
<li class="badge" key="timetable-count">
|
||||
@@ -88,7 +88,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="store.dataStatuses.trains != Status.Loaded">
|
||||
<div v-else-if="apiStore.dataStatuses.connection != Status.Loaded">
|
||||
{{ $t('train-stats.stats-loading') }}
|
||||
</div>
|
||||
|
||||
@@ -102,8 +102,9 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import { Status } from '../../typings/common';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
|
||||
interface ITop {
|
||||
name: string;
|
||||
@@ -127,7 +128,8 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
showOptions: false,
|
||||
store: useStore(),
|
||||
store: useMainStore(),
|
||||
apiStore: useApiStore(),
|
||||
Status: Status.Data
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
<template>
|
||||
<transition name="status-anim" mode="out-in" tag="div" class="train-table">
|
||||
<div :key="store.dataStatuses.trains">
|
||||
<div :key="apiStore.dataStatuses.connection">
|
||||
<div class="table-info" key="offline" v-if="store.isOffline">
|
||||
{{ $t('app.offline') }}
|
||||
</div>
|
||||
|
||||
<Loading v-else-if="trains.length == 0 && store.dataStatuses.trains == 0" key="loading" />
|
||||
<Loading v-else-if="apiStore.dataStatuses.connection == Status.Loading" key="loading" />
|
||||
|
||||
<div
|
||||
class="table-info"
|
||||
key="no-trains"
|
||||
v-else-if="trains.length == 0 && store.dataStatuses.trains != 0"
|
||||
>
|
||||
<div class="table-info" key="no-trains" v-else-if="trains.length == 0">
|
||||
{{ $t('trains.no-trains') }}
|
||||
</div>
|
||||
|
||||
@@ -35,10 +31,11 @@
|
||||
import { defineComponent, inject, PropType, Ref } from 'vue';
|
||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||
import Train from '../../scripts/interfaces/Train';
|
||||
import { useStore } from '../../store/mainStore';
|
||||
import { useMainStore } from '../../store/mainStore';
|
||||
import Loading from '../Global/Loading.vue';
|
||||
import TrainInfo from './TrainInfo.vue';
|
||||
import { Status } from '../../typings/common';
|
||||
import { useApiStore } from '../../store/apiStore';
|
||||
|
||||
export default defineComponent({
|
||||
components: { Loading, TrainInfo },
|
||||
@@ -53,7 +50,8 @@ export default defineComponent({
|
||||
mixins: [modalTrainMixin],
|
||||
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const store = useMainStore();
|
||||
const apiStore = useApiStore();
|
||||
const searchedTrain = inject('searchedTrain') as Ref<string>;
|
||||
const searchedDriver = inject('searchedDriver') as Ref<string>;
|
||||
|
||||
@@ -61,6 +59,8 @@ export default defineComponent({
|
||||
searchedTrain,
|
||||
searchedDriver,
|
||||
store,
|
||||
apiStore,
|
||||
Status: Status.Data,
|
||||
sorterActive: inject('sorterActive') as {
|
||||
id: string | number;
|
||||
dir: number;
|
||||
@@ -72,7 +72,7 @@ export default defineComponent({
|
||||
dataStatus() {
|
||||
if (this.store.isOffline) return Status.Data.Offline;
|
||||
|
||||
if (this.trains.length == 0 && this.store.dataStatuses.trains == Status.Data.Loading)
|
||||
if (this.trains.length == 0 && this.apiStore.dataStatuses.connection == Status.Data.Loading)
|
||||
return Status.Data.Loading;
|
||||
|
||||
return Status.Data.Loaded;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+10
@@ -0,0 +1,10 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const http = axios.create({
|
||||
baseURL:
|
||||
import.meta.env.VITE_API_MODE === 'development'
|
||||
? 'http://localhost:3001'
|
||||
: 'https://stacjownik.spythere.eu'
|
||||
});
|
||||
|
||||
export default http;
|
||||
+46
-25
@@ -144,7 +144,8 @@
|
||||
"filter-withComments": "COMMENTS",
|
||||
"filter-twr": "HIGH RISK CARGO",
|
||||
"filter-skr": "EXCEEDED GAUGE",
|
||||
"filter-twr-skr": "ALL TYPES",
|
||||
"filter-twr-skr": "BOTH TYPES",
|
||||
"filter-all-specials": "ALL",
|
||||
"filter-common": "NO WARNINGS",
|
||||
"filter-passenger": "PASSENGER",
|
||||
"filter-freight": "FREIGHT",
|
||||
@@ -156,9 +157,9 @@
|
||||
"filter-clear": "CLEAR FILTERS",
|
||||
|
||||
"filter-section-timetable-status": "TIMETABLE STATUS",
|
||||
"filter-section-twrskr": "WARNINGS",
|
||||
"filter-section-special": "SPECIAL TYPE",
|
||||
|
||||
"filter-all": "ALL ENTRIES",
|
||||
"filter-all-statuses": "ALL",
|
||||
"filter-abandoned": "ABANDONED",
|
||||
"filter-fulfilled": "FULFILLED",
|
||||
"filter-active": "ACTIVE"
|
||||
@@ -347,29 +348,49 @@
|
||||
"last-seen-at": "Last seen at",
|
||||
"currently-at": "Currently at",
|
||||
|
||||
"stats-title": "DRIVING STATISTICS OF",
|
||||
"driver-stats": {
|
||||
"button": "DRIVER STATS",
|
||||
"title": "{name}'s DRIVER STATS",
|
||||
"info": "Enter a proper nickname into filters [F] to see user's driving statistics!",
|
||||
"timetables": "TIMETABLES",
|
||||
"longest-timetable": "LONGEST TIMETABLE",
|
||||
"avg-timetable": "AVERAGE TIMETABLE LENGTH",
|
||||
"distance": "DISTANCE",
|
||||
"stations": "STATIONS"
|
||||
},
|
||||
|
||||
"stats-timetables": "TIMETABLES",
|
||||
"stats-longest-timetable": "LONGEST TIMETABLE",
|
||||
"stats-avg-timetable": "AVERAGE TIMETABLE LENGTH",
|
||||
"stats-distance": "DISTANCE",
|
||||
"stats-stations": "STATIONS",
|
||||
"daily-stats": {
|
||||
"button": "DAILY STATS",
|
||||
"title": "STATS OF THE DAY",
|
||||
"info": "Today's statistics are unavailable yet!",
|
||||
"total": "Issued timetables: {count} (total distance: {distance})",
|
||||
"longest": "The longest timetable: #{id} (made by {author} for {driver}, distance: {distance})",
|
||||
"most-active-dr": "The most active dispatcher: {dispatcher} (created {count})",
|
||||
"most-active-dr-many": "The most active dispatchers: {dispatchers} (created {count} each)",
|
||||
"most-active-driver": "The most active driver: {driver} (total driven distance: {distance})",
|
||||
"longest-duties": "The longest service: {dispatcher} at {station} (duration: {duration})",
|
||||
"count": "timetable | timetables",
|
||||
|
||||
"timetable-stats-title": "Daily stats on {date}",
|
||||
"timetable-stats-total": "Issued timetables: {count} (total distance: {distance})",
|
||||
"timetable-stats-longest": "The longest timetable: #{id} (made by {author} for {driver}, distance: {distance})",
|
||||
"timetable-stats-most-active-dr": "The most active dispatcher: {dispatcher} (created {count})",
|
||||
"timetable-stats-most-active-dr-many": "The most active dispatchers: {dispatchers} (created {count} each)",
|
||||
"timetable-stats-most-active-driver": "The most active driver: {driver} (total driven distance: {distance})",
|
||||
"timetable-stats-longest-duties": "The longest service: {dispatcher} at {station} (duration: {duration})",
|
||||
"rippedSwitches": "RIPPED SWITCHES",
|
||||
"derailments": "DERAILMENTS",
|
||||
"skippedStopSignals": "SKIPPED STOP SIGNALS",
|
||||
"radioStops": "RADIOSTOPS",
|
||||
"kills": "KILLS"
|
||||
},
|
||||
|
||||
"timetable-count": "timetable | timetables",
|
||||
|
||||
"daily-stats-title": "DAILY STATS",
|
||||
"daily-stats-info": "Today's statistics are unavailable yet!",
|
||||
|
||||
"driver-stats-title": "DRIVER STATS",
|
||||
"driver-stats-info": "Enter a proper nickname into filters [F] to see user's driving statistics!",
|
||||
"dispatcher-stats": {
|
||||
"button": "DISPATCHER STATS",
|
||||
"title": "{name}'s DISPATCHER STATS",
|
||||
"empty": "This user has no statistics saved yet!",
|
||||
"info": "Enter a proper nickname into filters [F] to see user's dispatcher statistics!",
|
||||
"services-count": "SERVICES",
|
||||
"service-max": "MAX SERVICE DURATION",
|
||||
"service-avg": "AVG SERVICE DURATION",
|
||||
"timetables-count": "ISSUED TIMETABLES",
|
||||
"timetables-sum": "TIMETABLES DISTANCE SUM",
|
||||
"timetables-max": "LONGEST TIMETABLE",
|
||||
"timetables-avg": "AVG TIMETABLE DISTANCE"
|
||||
},
|
||||
|
||||
"stats-loading": "Fetching statistics...",
|
||||
"stats-error": "Oops! An unexpected error occurred while trying to fetch statistics! :/",
|
||||
@@ -405,8 +426,8 @@
|
||||
"two-way-routes": "Two way routes",
|
||||
|
||||
"option-active-timetables": "Active timetables",
|
||||
"option-timetables-history": "Timetables history",
|
||||
"option-dispatchers-history": "Dispatchers history",
|
||||
"option-timetables-history": "Timetables history PL1",
|
||||
"option-dispatchers-history": "Dispatchers history PL1",
|
||||
|
||||
"timetable-author-title": "Issued by",
|
||||
"timetable-author-unknown": "Author unknown",
|
||||
|
||||
+45
-24
@@ -133,7 +133,8 @@
|
||||
"filter-noComments": "BEZ UWAG",
|
||||
"filter-twr": "WYS. RYZYKA",
|
||||
"filter-skr": "SKRAJNIA",
|
||||
"filter-twr-skr": "WSZYSTKIE",
|
||||
"filter-twr-skr": "TWR/SKR",
|
||||
"filter-all-statuses": "WSZYSTKIE",
|
||||
"filter-common": "ZWYKŁE",
|
||||
"filter-passenger": "PASAŻERSKIE",
|
||||
"filter-freight": "TOWAROWE",
|
||||
@@ -145,9 +146,9 @@
|
||||
"filter-clear": "WYŁĄCZ FILTRY",
|
||||
|
||||
"filter-section-timetable-status": "STATUS ROZKŁADU JAZDY",
|
||||
"filter-section-twrskr": "UWAGI",
|
||||
"filter-section-special": "TYPY SPECJALNE",
|
||||
|
||||
"filter-all": "WSZYSTKIE",
|
||||
"filter-all-specials": "WSZYSTKIE",
|
||||
"filter-abandoned": "PORZUCONE",
|
||||
"filter-fulfilled": "WYPEŁNIONE",
|
||||
"filter-active": "AKTYWNE"
|
||||
@@ -326,31 +327,51 @@
|
||||
|
||||
"load-data": "Pobierz dalszą historię...",
|
||||
|
||||
"stats-title": "STATYSTYKI MASZYNISTY",
|
||||
|
||||
"last-seen-at": "Ostatnio widziany na: ",
|
||||
"currently-at": "Obecnie na scenerii: ",
|
||||
|
||||
"stats-timetables": "ROZKŁADY JAZDY",
|
||||
"stats-longest-timetable": "NAJDŁUŻSZY RJ",
|
||||
"stats-avg-timetable": "ŚREDNIA DŁUGOŚĆ RJ",
|
||||
"stats-distance": "DYSTANS",
|
||||
"stats-stations": "STACJE",
|
||||
"driver-stats": {
|
||||
"button": "STAT. MASZYNISTY",
|
||||
"title": "STATYSTYKI MASZYNISTY {name}",
|
||||
"info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki maszynisty!",
|
||||
"timetables": "ROZKŁADY JAZDY",
|
||||
"longest-timetable": "NAJDŁUŻSZY RJ",
|
||||
"avg-timetable": "ŚREDNIA DŁUGOŚĆ RJ",
|
||||
"distance": "DYSTANS",
|
||||
"stations": "STACJE"
|
||||
},
|
||||
|
||||
"timetable-stats-total": "Stworzone rozkłady jazdy: {count} (łączny dystans: {distance})",
|
||||
"timetable-stats-longest": "Najdłuższy rozkład jazdy: #{id} (stworzony przez dyżurnego {author} dla maszynisty {driver} o dystansie {distance})",
|
||||
"timetable-stats-most-active-dr": "Najaktywniejszy dyżurny: {dispatcher} (stworzył {count})",
|
||||
"timetable-stats-most-active-dr-many": "Najaktywniejsi dyżurni: {dispatchers} (stworzyli po {count})",
|
||||
"timetable-stats-most-active-driver": "Najaktywniejszy maszynista: {driver} (łączny przejechany dystans: {distance})",
|
||||
"timetable-stats-longest-duties": "Najdłuższa służba: {dispatcher} na scenerii {station} (czas trwania: {duration})",
|
||||
"daily-stats": {
|
||||
"button": "STATYSTYKI DNIA",
|
||||
"title": "STATYSTYKI DNIA",
|
||||
"info": "Dzisiejsze statystyki nie są jeszcze dostępne!",
|
||||
"total": "Stworzone rozkłady jazdy: {count} (łączny dystans: {distance})",
|
||||
"longest": "Najdłuższy rozkład jazdy: #{id} (stworzony przez dyżurnego {author} dla maszynisty {driver} o dystansie {distance})",
|
||||
"most-active-dr": "Najaktywniejszy dyżurny: {dispatcher} (stworzył {count})",
|
||||
"most-active-dr-many": "Najaktywniejsi dyżurni: {dispatchers} (stworzyli po {count})",
|
||||
"most-active-driver": "Najaktywniejszy maszynista: {driver} (łączny przejechany dystans: {distance})",
|
||||
"longest-duties": "Najdłuższa służba: {dispatcher} na scenerii {station} (czas trwania: {duration})",
|
||||
"count": "rozkład jazdy | rozkładów jazdy",
|
||||
|
||||
"timetable-count": "rozkład jazdy | rozkładów jazdy",
|
||||
"rippedSwitches": "ROZPRUTE ZWROTNICE",
|
||||
"derailments": "WYKOLEJENIA",
|
||||
"skippedStopSignals": "POMINIĘTE S1",
|
||||
"radioStops": "RADIOSTOPY",
|
||||
"kills": "POTRĄCENIA"
|
||||
},
|
||||
|
||||
"daily-stats-title": "STATYSTYKI DNIA",
|
||||
"daily-stats-info": "Dzisiejsze statystyki nie są jeszcze dostępne!",
|
||||
|
||||
"driver-stats-title": "STATYSTYKI GRACZA",
|
||||
"driver-stats-info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki maszynisty!",
|
||||
"dispatcher-stats": {
|
||||
"button": "STATYSTYKI DYŻURNEGO",
|
||||
"title": "STATYSTYKI DYŻURNEGO {name}",
|
||||
"info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki dyżurnego!",
|
||||
"services-count": "DYŻURY",
|
||||
"service-max": "MAKS. CZAS DYŻURU",
|
||||
"service-avg": "ŚREDNI CZAS DYŻURU",
|
||||
"timetables-count": "WYSTAWIONE RJ",
|
||||
"timetables-sum": "SUMA WYSTAWIONYCH RJ",
|
||||
"timetables-max": "NAJDŁUŻSZY WYSTAWIONY RJ",
|
||||
"timetables-avg": "ŚREDNIA WYSTAWIONYCH RJ"
|
||||
},
|
||||
|
||||
"stats-loading": "Pobieranie statystyk...",
|
||||
"stats-error": "Ups! Wystąpił błąd podczas próby pobrania statystyk!",
|
||||
@@ -386,8 +407,8 @@
|
||||
"two-way-routes": "Szlaki dwutorowe",
|
||||
|
||||
"option-active-timetables": "Aktywne rozkłady jazdy",
|
||||
"option-timetables-history": "Historia rozkładów",
|
||||
"option-dispatchers-history": "Historia dyżurów",
|
||||
"option-timetables-history": "Historia rozkładów PL1",
|
||||
"option-dispatchers-history": "Historia dyżurów PL1",
|
||||
|
||||
"timetable-author-title": "Wydany przez",
|
||||
"timetable-author-unknown": "Autor nieznany",
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../store/mainStore';
|
||||
import { useApiStore } from '../store/apiStore';
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
store: useStore()
|
||||
apiStore: useApiStore()
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
isDonator(name: string) {
|
||||
return this.store.donatorsData.includes(name);
|
||||
return this.apiStore.donatorsData.includes(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,8 +10,6 @@ export default defineComponent({
|
||||
mountObserver(actionFunction: () => void, target: Element) {
|
||||
this.observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
console.log(entries);
|
||||
|
||||
if (entries[0].intersectionRatio > 0.5) actionFunction();
|
||||
},
|
||||
{ threshold: 0.2 }
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../store/mainStore';
|
||||
import { useMainStore } from '../store/mainStore';
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
store: useStore()
|
||||
store: useMainStore()
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
+5
-9
@@ -18,7 +18,8 @@ const routes: Array<RouteRecordRaw> = [
|
||||
props: (route) => ({
|
||||
train: route.query.train,
|
||||
driver: route.query.driver,
|
||||
trainId: route.query.trainId
|
||||
trainId: route.query.trainId,
|
||||
region: route.query.region
|
||||
})
|
||||
},
|
||||
{
|
||||
@@ -39,9 +40,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'JournalTimetables',
|
||||
component: JournalTimetablesVue,
|
||||
props: (route) => ({
|
||||
trainNo: route.query.trainNo,
|
||||
driverName: route.query.driverName,
|
||||
timetableId: route.query.timetableId
|
||||
region: route.query.region
|
||||
})
|
||||
},
|
||||
{
|
||||
@@ -49,8 +48,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'JournalDispatchers',
|
||||
component: JournalDispatchersVue,
|
||||
props: (route) => ({
|
||||
sceneryName: route.query.sceneryName,
|
||||
dispatcherName: route.query.dispatcherName
|
||||
region: route.query.region
|
||||
})
|
||||
},
|
||||
{
|
||||
@@ -61,12 +59,10 @@ const routes: Array<RouteRecordRaw> = [
|
||||
|
||||
const router = createRouter({
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
if (to.name == 'SceneryView' && from.name && from.query['view'] === undefined)
|
||||
if (to.name == 'SceneryView' && from.name !== to.name && from.query['view'] === undefined)
|
||||
return { el: `.app_main` };
|
||||
|
||||
if (savedPosition) return savedPosition;
|
||||
|
||||
// if (from.name == 'SceneryView' && to.name == 'StationsView') return { el: `.last-selected`, top: 20 };
|
||||
},
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export const URLs = {
|
||||
stacjownikAPI:
|
||||
import.meta.env.VITE_APP_API_DEV === '1' && !import.meta.env.PROD
|
||||
? 'http://localhost:3001'
|
||||
: 'https://stacjownik.spythere.eu',
|
||||
stacjownikAPIDev: 'localhost:3000'
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import http from '../http';
|
||||
import { API } from '../typings/api';
|
||||
import axios from 'axios';
|
||||
import { Status } from '../typings/common';
|
||||
import { StationJSONData } from './typings';
|
||||
|
||||
export const useApiStore = defineStore('apiStore', {
|
||||
state: () => ({
|
||||
dataStatuses: {
|
||||
connection: Status.Data.Loading,
|
||||
sceneries: Status.Data.Loading,
|
||||
timetables: Status.Data.Loading,
|
||||
dispatchers: Status.Data.Loading,
|
||||
trains: Status.Data.Loading
|
||||
},
|
||||
|
||||
activeData: undefined as API.ActiveData.Response | undefined,
|
||||
rollingStockData: undefined as API.RollingStock.Response | undefined,
|
||||
donatorsData: [] as API.Donators.Response,
|
||||
sceneryData: [] as StationJSONData[],
|
||||
|
||||
activeDataTimeout: undefined as number | undefined
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async setupAPI() {
|
||||
// Static data
|
||||
this.fetchStockInfoData();
|
||||
this.fetchDonatorsData();
|
||||
this.fetchStationsGeneralInfo();
|
||||
|
||||
if (this.activeDataTimeout === undefined) this.startActiveDataScheduler();
|
||||
},
|
||||
|
||||
// async setDataStatuses() {
|
||||
// if (!window.navigator.onLine) {
|
||||
// this.dataStatuses.connection = Status.Data.Offline;
|
||||
// this.dataStatuses.sceneries = Status.Data.Offline;
|
||||
// this.dataStatuses.trains = Status.Data.Offline;
|
||||
// this.dataStatuses.dispatchers = Status.Data.Offline;
|
||||
// this.dataStatuses.timetables = Status.Data.Offline;
|
||||
// }
|
||||
|
||||
// if (!this.activeData?.activeSceneries) {
|
||||
// this.dataStatuses.connection = Status.Data.Loaded;
|
||||
// this.dataStatuses.sceneries = Status.Data.Error;
|
||||
// this.dataStatuses.trains = Status.Data.Error;
|
||||
// this.dataStatuses.dispatchers = Status.Data.Error;
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
// this.dataStatuses.connection = Status.Data.Loaded;
|
||||
// this.dataStatuses.sceneries = Status.Data.Loaded;
|
||||
// this.dataStatuses.trains = !this.activeData.trains ? Status.Data.Warning : Status.Data.Loaded;
|
||||
// this.dataStatuses.dispatchers = Status.Data.Loaded;
|
||||
// },
|
||||
|
||||
async fetchDonatorsData() {
|
||||
try {
|
||||
const response = await http.get<API.Donators.Response>('api/getDonators');
|
||||
|
||||
this.donatorsData = response.data;
|
||||
} catch (error) {
|
||||
console.error('Ups! Wystąpił błąd podczas pobierania informacji o donatorach:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async fetchStockInfoData() {
|
||||
try {
|
||||
this.rollingStockData = (
|
||||
await axios.get<API.RollingStock.Response>(
|
||||
'https://raw.githubusercontent.com/Spythere/api/main/td2/data/stockInfo.json'
|
||||
)
|
||||
).data;
|
||||
} catch (error) {
|
||||
console.error('Ups! Wystąpił błąd podczas pobierania informacji o taborze z API:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async startActiveDataScheduler() {
|
||||
if (!window.navigator.onLine) {
|
||||
this.dataStatuses.connection = Status.Data.Offline;
|
||||
return;
|
||||
}
|
||||
|
||||
if (import.meta.env.VITE_API_MODE === 'mock') {
|
||||
const mockActiveData = await import('../data/mockActiveData.json');
|
||||
this.dataStatuses.connection = Status.Data.Loaded;
|
||||
this.activeData = mockActiveData;
|
||||
|
||||
console.warn('Stacjownik działa w trybie mockowania danych z WS');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = (await http.get<API.ActiveData.Response>('api/getActiveData')).data;
|
||||
|
||||
this.activeData = data;
|
||||
this.dataStatuses.connection = Status.Data.Loaded;
|
||||
} catch (error) {
|
||||
this.dataStatuses.connection = Status.Data.Error;
|
||||
console.error('Wystąpił błąd podczas pobierania danych online z API!');
|
||||
} finally {
|
||||
this.activeDataTimeout = window.setTimeout(
|
||||
() => {
|
||||
this.startActiveDataScheduler();
|
||||
},
|
||||
~~(1000 * (Math.random() * (25 - 20) + 25))
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
async stopActiveDataScheduler() {
|
||||
window.clearTimeout(this.activeDataTimeout);
|
||||
this.activeDataTimeout = undefined;
|
||||
},
|
||||
|
||||
async fetchStationsGeneralInfo() {
|
||||
const sceneryData: StationJSONData[] = (await http.get<StationJSONData[]>('api/getSceneries'))
|
||||
.data;
|
||||
|
||||
if (!sceneryData) {
|
||||
this.dataStatuses.sceneries = Status.Data.Error;
|
||||
return;
|
||||
}
|
||||
|
||||
this.dataStatuses.sceneries = Status.Data.Loaded;
|
||||
this.sceneryData = sceneryData;
|
||||
}
|
||||
}
|
||||
});
|
||||
+99
-127
@@ -1,41 +1,24 @@
|
||||
import axios from 'axios';
|
||||
import { defineStore } from 'pinia';
|
||||
import StationRoutes from '../scripts/interfaces/StationRoutes';
|
||||
import Train from '../scripts/interfaces/Train';
|
||||
import { URLs } from '../scripts/utils/apiURLs';
|
||||
import { parseSpawns, getScheduledTrains, getStationTrains } from './utils';
|
||||
|
||||
import { OnlineScenery, ScheduledTrain, StationJSONData, StoreState } from './typings';
|
||||
import { OnlineScenery, ScheduledTrain, StoreState } from './typings';
|
||||
|
||||
import { API } from '../typings/api';
|
||||
import { Status } from '../typings/common';
|
||||
import Station from '../scripts/interfaces/Station';
|
||||
import { useApiStore } from './apiStore';
|
||||
import { API } from '../typings/api';
|
||||
|
||||
const API_INTERVAL_MS = 30000;
|
||||
|
||||
export const useStore = defineStore('store', {
|
||||
export const useMainStore = defineStore('store', {
|
||||
state: () =>
|
||||
({
|
||||
activeData: {} as unknown,
|
||||
rollingStockData: undefined,
|
||||
donatorsData: [],
|
||||
|
||||
stationList: [],
|
||||
regionOnlineCounters: [],
|
||||
|
||||
routesList: [],
|
||||
|
||||
sceneryData: [],
|
||||
lastDispatcherStatuses: [],
|
||||
|
||||
region: { id: 'eu', value: 'PL1' },
|
||||
|
||||
trainCount: 0,
|
||||
stationCount: 0,
|
||||
|
||||
isOffline: false,
|
||||
|
||||
dispatcherStatsName: '',
|
||||
dispatcherStatsData: undefined,
|
||||
dispatcherStatsStatus: Status.Data.Initialized,
|
||||
|
||||
driverStatsName: '',
|
||||
driverStatsData: undefined,
|
||||
@@ -43,31 +26,15 @@ export const useStore = defineStore('store', {
|
||||
|
||||
chosenModalTrainId: undefined,
|
||||
|
||||
dataStatuses: {
|
||||
connection: Status.Data.Loading,
|
||||
sceneries: Status.Data.Loading,
|
||||
timetables: Status.Data.Loading,
|
||||
dispatchers: Status.Data.Loading,
|
||||
trains: Status.Data.Loading
|
||||
},
|
||||
|
||||
currentStatsTab: null,
|
||||
|
||||
blockScroll: false,
|
||||
listenerLaunched: false,
|
||||
modalLastClickedTarget: null,
|
||||
|
||||
tooltip: {
|
||||
content: '',
|
||||
visible: false,
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
modalLastClickedTarget: null
|
||||
}) as StoreState,
|
||||
|
||||
getters: {
|
||||
trainList(): Train[] {
|
||||
return (this.activeData?.trains ?? [])
|
||||
const apiStore = useApiStore();
|
||||
|
||||
return (apiStore.activeData?.trains ?? [])
|
||||
.filter((train) => train.timetable || train.online)
|
||||
.map((train) => {
|
||||
const stock = train.stockString.split(';');
|
||||
@@ -86,7 +53,7 @@ export const useStore = defineStore('store', {
|
||||
|
||||
distance: train.distance,
|
||||
signal: train.signal,
|
||||
online: train.online,
|
||||
online: Boolean(train.online),
|
||||
driverId: train.driverId,
|
||||
driverName: train.driverName,
|
||||
currentStationName: train.currentStationName,
|
||||
@@ -118,10 +85,12 @@ export const useStore = defineStore('store', {
|
||||
},
|
||||
|
||||
onlineSceneryList(state): OnlineScenery[] {
|
||||
if (state.isOffline) return [];
|
||||
if (!state.activeData?.activeSceneries) return [];
|
||||
const apiStore = useApiStore();
|
||||
|
||||
return state.activeData?.activeSceneries.reduce((list, scenery) => {
|
||||
if (state.isOffline) return [];
|
||||
if (!apiStore.activeData?.activeSceneries) return [];
|
||||
|
||||
return apiStore.activeData?.activeSceneries.reduce((list, scenery) => {
|
||||
if (scenery.isOnline !== 1 && Date.now() - scenery.lastSeen > 1000 * 60 * 2) return list;
|
||||
if (scenery.dispatcherStatus == Status.ActiveDispatcher.UNKNOWN) return list;
|
||||
|
||||
@@ -180,20 +149,12 @@ export const useStore = defineStore('store', {
|
||||
|
||||
return list;
|
||||
}, [] as OnlineScenery[]);
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async fetchStationsGeneralInfo() {
|
||||
const sceneryData: StationJSONData[] = await (
|
||||
await axios.get(`${URLs.stacjownikAPI}/api/getSceneries`)
|
||||
).data;
|
||||
},
|
||||
|
||||
if (!sceneryData) {
|
||||
this.dataStatuses.sceneries = Status.Data.Error;
|
||||
return;
|
||||
}
|
||||
stationList(): Station[] {
|
||||
const apiStore = useApiStore();
|
||||
|
||||
this.stationList = sceneryData.map((scenery) => {
|
||||
return apiStore.sceneryData.map((scenery) => {
|
||||
return {
|
||||
name: scenery.name,
|
||||
|
||||
@@ -242,86 +203,97 @@ export const useStore = defineStore('store', {
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async processStationsOnlineInfo(activeData: API.ActiveData.Response) {
|
||||
if (!activeData.activeSceneries) return;
|
||||
|
||||
async fetchActiveData() {
|
||||
if (import.meta.env.VITE_APP_WS_DEV === '1') {
|
||||
const mockWebsocketData = await import('../data/mockWebsocketData.json');
|
||||
this.dataStatuses.connection = Status.Data.Loaded;
|
||||
this.activeData = mockWebsocketData as any;
|
||||
this.setStatuses();
|
||||
const onlineSceneries = activeData.activeSceneries.reduce((acc, scenery) => {
|
||||
const savedStation = this.stationList.find((st) => scenery.stationName === st.name);
|
||||
|
||||
console.warn('Stacjownik działa w trybie mockowania danych z WS');
|
||||
if (scenery.isOnline !== 1 && Date.now() - scenery.lastSeen > 1000 * 60 * 2) return acc;
|
||||
if (scenery.dispatcherStatus == Status.ActiveDispatcher.UNKNOWN) return acc;
|
||||
|
||||
return;
|
||||
}
|
||||
const station = this.stationList.find((s) => s.name === scenery.stationName);
|
||||
|
||||
try {
|
||||
const data = (
|
||||
await axios.get<API.ActiveData.Response>(`${URLs.stacjownikAPI}/api/getActiveData`)
|
||||
).data;
|
||||
const scheduledTrains = getScheduledTrains(this.trainList, scenery, station?.generalInfo);
|
||||
|
||||
this.activeData = data;
|
||||
this.dataStatuses.connection = Status.Data.Loaded;
|
||||
this.setStatuses();
|
||||
} catch (error) {
|
||||
this.dataStatuses.connection = Status.Data.Error;
|
||||
console.error('Wystąpił błąd podczas pobierania danych online z API!');
|
||||
}
|
||||
},
|
||||
const stationTrains = getStationTrains(
|
||||
this.trainList,
|
||||
scheduledTrains,
|
||||
this.region.id,
|
||||
scenery
|
||||
);
|
||||
|
||||
async setupAPI() {
|
||||
this.fetchActiveData();
|
||||
// Remove checkpoint duplicates
|
||||
const uniqueScheduledTrains = scheduledTrains.reduce(
|
||||
(uniqueList, sTrain) =>
|
||||
uniqueList.find((v) => v.trainId === sTrain.trainId)
|
||||
? uniqueList
|
||||
: [...uniqueList, sTrain],
|
||||
[] as ScheduledTrain[]
|
||||
);
|
||||
|
||||
setInterval(() => {
|
||||
this.fetchActiveData();
|
||||
}, API_INTERVAL_MS);
|
||||
const dispatcherTimestamp =
|
||||
scenery.dispatcherStatus == Status.ActiveDispatcher.NO_LIMIT
|
||||
? Date.now() + 25500000
|
||||
: scenery.dispatcherStatus > 5
|
||||
? scenery.dispatcherStatus
|
||||
: null;
|
||||
|
||||
this.fetchStockInfoData();
|
||||
this.fetchDonatorsData();
|
||||
this.fetchStationsGeneralInfo();
|
||||
const onlineInfo = {
|
||||
name: scenery.stationName,
|
||||
hash: scenery.stationHash,
|
||||
region: scenery.region,
|
||||
maxUsers: scenery.maxUsers,
|
||||
currentUsers: scenery.currentUsers,
|
||||
spawns: parseSpawns(scenery.spawnString),
|
||||
dispatcherName: scenery.dispatcherName,
|
||||
dispatcherRate: scenery.dispatcherRate,
|
||||
dispatcherId: scenery.dispatcherId,
|
||||
dispatcherExp: scenery.dispatcherExp,
|
||||
dispatcherIsSupporter: scenery.dispatcherIsSupporter,
|
||||
scheduledTrains: scheduledTrains,
|
||||
stationTrains: stationTrains,
|
||||
dispatcherStatus: scenery.dispatcherStatus,
|
||||
dispatcherTimestamp: dispatcherTimestamp,
|
||||
|
||||
isOnline: scenery.isOnline == 1,
|
||||
|
||||
scheduledTrainCount: {
|
||||
all: uniqueScheduledTrains.length,
|
||||
confirmed: uniqueScheduledTrains.filter((train) => train.stopInfo.confirmed).length,
|
||||
unconfirmed: uniqueScheduledTrains.filter((train) => !train.stopInfo.confirmed).length
|
||||
}
|
||||
};
|
||||
|
||||
if (savedStation) savedStation.onlineInfo = onlineInfo;
|
||||
else
|
||||
this.stationList.push({
|
||||
name: onlineInfo.name,
|
||||
onlineInfo: onlineInfo
|
||||
});
|
||||
|
||||
acc.push(onlineInfo);
|
||||
|
||||
return acc;
|
||||
}, [] as OnlineScenery[]);
|
||||
|
||||
// Reset online info of already offline sceneries
|
||||
this.stationList
|
||||
.filter(
|
||||
(station) =>
|
||||
station.onlineInfo &&
|
||||
onlineSceneries.findIndex(
|
||||
(os) => os.region == station.onlineInfo!.region && station.name == os.name
|
||||
) != -1
|
||||
)
|
||||
.forEach((station) => (station.onlineInfo = undefined));
|
||||
},
|
||||
|
||||
async changeRegion(region: StoreState['region']) {
|
||||
this.region = region;
|
||||
},
|
||||
|
||||
async fetchStockInfoData() {
|
||||
try {
|
||||
this.rollingStockData = (
|
||||
await axios.get<API.RollingStock.Response>(
|
||||
'https://raw.githubusercontent.com/Spythere/api/main/td2/data/stockInfo.json'
|
||||
)
|
||||
).data;
|
||||
} catch (error) {
|
||||
console.error('Ups! Wystąpił błąd podczas pobierania informacji o taborze z API:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async fetchDonatorsData() {
|
||||
try {
|
||||
const response = await axios.get<API.Donators.Response>(
|
||||
`${URLs.stacjownikAPI}/api/getDonators`
|
||||
);
|
||||
|
||||
if (response.data) this.donatorsData = response.data;
|
||||
} catch (error) {
|
||||
console.error('Ups! Wystąpił błąd podczas pobierania informacji o donatorach:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async setStatuses() {
|
||||
if (!this.activeData.activeSceneries) {
|
||||
this.dataStatuses.sceneries = Status.Data.Error;
|
||||
this.dataStatuses.trains = Status.Data.Error;
|
||||
this.dataStatuses.dispatchers = Status.Data.Error;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.dataStatuses.sceneries = Status.Data.Loaded;
|
||||
this.dataStatuses.trains = !this.activeData.trains ? Status.Data.Warning : Status.Data.Loaded;
|
||||
this.dataStatuses.dispatchers = Status.Data.Loaded;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import inputData from '../data/options.json';
|
||||
import { useStore } from './mainStore';
|
||||
import { useMainStore } from './mainStore';
|
||||
import { filterStations, sortStations } from '../scripts/utils/filterUtils';
|
||||
import { HeadIdsTypes } from '../scripts/data/stationHeaderNames';
|
||||
import StorageManager from '../managers/storageManager';
|
||||
@@ -70,14 +70,26 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
|
||||
},
|
||||
|
||||
filteredStationList: (state) => {
|
||||
const store = useStore();
|
||||
return store.stationList
|
||||
.map((station) => ({
|
||||
const store = useMainStore();
|
||||
const savedStationNames = store.stationList.map((s) => s.name);
|
||||
|
||||
const onlineUnsavedStations = store.onlineSceneryList
|
||||
.filter((os) => !savedStationNames.includes(os.name) && os.region == store.region.id)
|
||||
.map((os) => ({
|
||||
name: os.name,
|
||||
generalInfo: undefined,
|
||||
onlineInfo: os
|
||||
}));
|
||||
|
||||
return [
|
||||
...onlineUnsavedStations,
|
||||
...store.stationList.map((station) => ({
|
||||
...station,
|
||||
onlineInfo: store.onlineSceneryList.find(
|
||||
(os) => os.name == station.name && os.region == store.region.id
|
||||
)
|
||||
}))
|
||||
]
|
||||
.filter((station) => filterStations(station, state.filters))
|
||||
.sort((a, b) => sortStations(a, b, state.sorterActive));
|
||||
}
|
||||
|
||||
+2
-36
@@ -1,4 +1,3 @@
|
||||
import Station from '../scripts/interfaces/Station';
|
||||
import { API } from '../typings/api';
|
||||
import { Status } from '../typings/common';
|
||||
|
||||
@@ -11,24 +10,7 @@ export interface RegionCounters {
|
||||
}
|
||||
|
||||
export interface StoreState {
|
||||
stationList: Station[];
|
||||
activeData: API.ActiveData.Response;
|
||||
rollingStockData?: API.RollingStock.Response;
|
||||
donatorsData: API.Donators.Response;
|
||||
|
||||
regionOnlineCounters: RegionCounters[];
|
||||
|
||||
lastDispatcherStatuses: {
|
||||
hash: string;
|
||||
statusTimestamp: number;
|
||||
statusID: Status.ActiveDispatcher;
|
||||
}[];
|
||||
|
||||
sceneryData: any[][];
|
||||
|
||||
region: { id: string; value: string };
|
||||
trainCount: number;
|
||||
stationCount: number;
|
||||
|
||||
isOffline: boolean;
|
||||
|
||||
@@ -41,26 +23,8 @@ export interface StoreState {
|
||||
|
||||
chosenModalTrainId?: string;
|
||||
|
||||
currentStatsTab: 'daily' | 'driver' | null;
|
||||
|
||||
dataStatuses: {
|
||||
connection: Status.Data;
|
||||
sceneries: Status.Data;
|
||||
timetables: Status.Data;
|
||||
dispatchers: Status.Data;
|
||||
trains: Status.Data;
|
||||
};
|
||||
|
||||
listenerLaunched: boolean;
|
||||
blockScroll: boolean;
|
||||
modalLastClickedTarget: EventTarget | null;
|
||||
|
||||
tooltip: {
|
||||
visible: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
content: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface StationRoutesInfo {
|
||||
@@ -164,6 +128,8 @@ export interface ScheduledTrain {
|
||||
stopLabel: string;
|
||||
stopStatus: StopStatus;
|
||||
stopStatusID: number;
|
||||
|
||||
region: string;
|
||||
}
|
||||
|
||||
export enum StopStatus {
|
||||
|
||||
@@ -175,6 +175,8 @@ export function getCheckpointTrain(
|
||||
stopStatus: trainStopStatus.stopStatus,
|
||||
stopStatusID: trainStopStatus.stopStatusID,
|
||||
|
||||
region: train.region,
|
||||
|
||||
arrivingLine,
|
||||
departureLine,
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
overflow-y: auto;
|
||||
height: 90vh;
|
||||
min-height: 550px;
|
||||
margin-top: 0.5em;
|
||||
|
||||
padding-right: 0.2em;
|
||||
}
|
||||
@@ -24,7 +25,7 @@
|
||||
text-align: end;
|
||||
|
||||
padding: 0.25em;
|
||||
margin: 0.5em 0;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.journal_warning {
|
||||
@@ -53,9 +54,7 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
|
||||
position: relative;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.btn--load-data {
|
||||
|
||||
@@ -2,24 +2,35 @@
|
||||
@import 'responsive.scss';
|
||||
|
||||
.stats-tab {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
z-index: 99;
|
||||
|
||||
transform: translateY(1em);
|
||||
|
||||
width: 100%;
|
||||
|
||||
background-color: #1a1a1a;
|
||||
box-shadow: 0 0 5px 1px $accentCol;
|
||||
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
margin-bottom: 0.5em;
|
||||
hr.header-separator {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
width: 100%;
|
||||
hr.section-separator {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.info-stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 0.5em;
|
||||
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.stat-badge {
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
top: calc(100% + 0.5em);
|
||||
|
||||
background-color: $bgCol;
|
||||
box-shadow: 0 5px 10px 2px #0f0f0f;
|
||||
// box-shadow: 0 5px 10px 2px #0f0f0f;
|
||||
box-shadow: 0 0 5px 1px $accentCol;
|
||||
|
||||
width: 100%;
|
||||
max-width: 550px;
|
||||
|
||||
@@ -5,11 +5,6 @@
|
||||
.actions-bar {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.filters-options {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
h1.option-title {
|
||||
@@ -57,6 +52,7 @@ h1.option-title {
|
||||
|
||||
.sort-option[data-selected='true'] {
|
||||
color: $accentCol;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.filter-option {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
src:
|
||||
url('/fonts/Quicksand-Bold.woff2') format('woff2'),
|
||||
url('/fonts/Quicksand-Bold.woff') format('woff');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
src:
|
||||
url('/fonts/Quicksand-SemiBold.woff2') format('woff2'),
|
||||
url('/fonts/Quicksand-SemiBold.woff') format('woff');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
src:
|
||||
url('/fonts/Quicksand-Medium.woff2') format('woff2'),
|
||||
url('/fonts/Quicksand-Medium.woff') format('woff');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
src:
|
||||
url('/fonts/Quicksand-Regular.woff2') format('woff2'),
|
||||
url('/fonts/Quicksand-Regular.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
src:
|
||||
url('/fonts/Quicksand-Light.woff2') format('woff2'),
|
||||
url('/fonts/Quicksand-Light.woff') format('woff');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
@import 'fonts.scss';
|
||||
|
||||
:root {
|
||||
--clr-primary: #ffc014;
|
||||
--clr-secondary: #2f2f2f;
|
||||
@@ -11,7 +13,7 @@
|
||||
--clr-skr: #ff5100;
|
||||
--clr-twr: #ffbb00;
|
||||
|
||||
--clr-error: #df3e3e;
|
||||
--clr-error: #fa3636;
|
||||
--clr-warning: #c59429;
|
||||
|
||||
--clr-donator: #f7a4ff;
|
||||
@@ -158,6 +160,10 @@ ul {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
&--error {
|
||||
color: var(--clr-error);
|
||||
}
|
||||
|
||||
&--donator {
|
||||
color: var(--clr-donator);
|
||||
text-shadow: var(--clr-donator) 0 0 10px;
|
||||
@@ -183,7 +189,7 @@ a.a-button {
|
||||
&[data-disabled='true'] {
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
opacity: 0.85;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
&.btn--filled {
|
||||
|
||||
+64
-28
@@ -5,7 +5,6 @@ export namespace API {
|
||||
export interface Response {
|
||||
activeSceneries?: API.ActiveSceneries.Response;
|
||||
trains?: API.ActiveTrains.Response;
|
||||
connectedSocketCount: number;
|
||||
}
|
||||
}
|
||||
export namespace DispatcherHistory {
|
||||
@@ -32,7 +31,11 @@ export namespace API {
|
||||
|
||||
export namespace DispatcherStats {
|
||||
export interface DistanceStat {
|
||||
routeDistance: number;
|
||||
routeDistance: number | null;
|
||||
}
|
||||
|
||||
export interface DurationStat {
|
||||
currentDuration: number | null;
|
||||
}
|
||||
|
||||
export interface Count {
|
||||
@@ -40,11 +43,18 @@ export namespace API {
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
_sum: DistanceStat;
|
||||
_max: DistanceStat;
|
||||
_min: DistanceStat;
|
||||
_avg: DistanceStat;
|
||||
_count: Count;
|
||||
services: {
|
||||
count: number;
|
||||
durationMax: number;
|
||||
durationAvg: number;
|
||||
} | null;
|
||||
|
||||
issuedTimetables: {
|
||||
count: number;
|
||||
distanceMax: number;
|
||||
distanceAvg: number;
|
||||
distanceSum: number;
|
||||
} | null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,9 +133,9 @@ export namespace API {
|
||||
driverLevel?: number;
|
||||
|
||||
currentStationName: string;
|
||||
currentStationHash: string;
|
||||
currentStationHash?: string;
|
||||
|
||||
online: boolean;
|
||||
online: number;
|
||||
lastSeen: number;
|
||||
|
||||
region: string;
|
||||
@@ -139,16 +149,16 @@ export namespace API {
|
||||
stopNameRAW: string;
|
||||
stopType: string;
|
||||
stopDistance: number;
|
||||
pointId: number;
|
||||
pointId: string;
|
||||
|
||||
mainStop: boolean;
|
||||
|
||||
arrivalLine: string;
|
||||
arrivalLine: string | null;
|
||||
arrivalTimestamp: number;
|
||||
arrivalRealTimestamp: number;
|
||||
arrivalDelay: number;
|
||||
|
||||
departureLine: string;
|
||||
departureLine: string | null;
|
||||
departureTimestamp: number;
|
||||
departureRealTimestamp: number;
|
||||
departureDelay: number;
|
||||
@@ -157,9 +167,9 @@ export namespace API {
|
||||
|
||||
beginsHere: boolean;
|
||||
terminatesHere: boolean;
|
||||
confirmed: boolean;
|
||||
stopped: boolean;
|
||||
stopTime: number;
|
||||
confirmed: number;
|
||||
stopped: number;
|
||||
stopTime: number | null;
|
||||
}
|
||||
|
||||
export interface Timetable {
|
||||
@@ -264,21 +274,47 @@ export namespace API {
|
||||
distanceAvg: number;
|
||||
maxTimetable: API.TimetableHistory.Data | null;
|
||||
|
||||
mostActiveDispatchers: {
|
||||
name: string;
|
||||
count: number;
|
||||
}[];
|
||||
globalDiff: GlobalDiff;
|
||||
globalMax: GlobalMax;
|
||||
|
||||
mostActiveDrivers: {
|
||||
name: string;
|
||||
distance: number;
|
||||
}[];
|
||||
mostActiveDispatchers: MostActiveDispatcher[];
|
||||
mostActiveDrivers: MostActiveDriver[];
|
||||
|
||||
longestDuties: {
|
||||
name: string;
|
||||
duration: number;
|
||||
station: string;
|
||||
}[];
|
||||
longestDuties: LongestDuty[];
|
||||
}
|
||||
|
||||
export interface MostActiveDispatcher {
|
||||
name: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface MostActiveDriver {
|
||||
name: string;
|
||||
distance: number;
|
||||
}
|
||||
|
||||
export interface LongestDuty {
|
||||
name: string;
|
||||
duration: number;
|
||||
station: string;
|
||||
}
|
||||
|
||||
export interface GlobalDiff {
|
||||
rippedSwitches: number;
|
||||
derailments: number;
|
||||
skippedStopSignals: number;
|
||||
radioStops: number;
|
||||
kills: number;
|
||||
drivenKilometers: number;
|
||||
routedTrains: number;
|
||||
}
|
||||
|
||||
export interface GlobalMax {
|
||||
_max: {
|
||||
drivers: number;
|
||||
dispatchers: number;
|
||||
timetables: number;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export namespace Status {
|
||||
}
|
||||
|
||||
export enum Data {
|
||||
Offline = 2,
|
||||
Offline = -2,
|
||||
Initialized = -1,
|
||||
Loading = 0,
|
||||
Error = 1,
|
||||
|
||||
@@ -3,15 +3,19 @@
|
||||
<JournalHeader />
|
||||
|
||||
<div class="journal_wrapper">
|
||||
<JournalOptions
|
||||
@on-search-confirm="fetchHistoryData"
|
||||
@on-options-reset="resetOptions"
|
||||
@on-refresh-data="fetchHistoryData(true)"
|
||||
:sorter-option-ids="['timestampFrom', 'duration']"
|
||||
:data-status="dataStatus"
|
||||
:current-options-active="currentOptionsActive"
|
||||
optionsType="dispatchers"
|
||||
/>
|
||||
<div class="journal_top-bar">
|
||||
<JournalOptions
|
||||
@on-search-confirm="fetchHistoryData"
|
||||
@on-options-reset="resetOptions"
|
||||
@on-refresh-data="fetchHistoryData(true)"
|
||||
:sorter-option-ids="['timestampFrom', 'duration']"
|
||||
:data-status="dataStatus"
|
||||
:current-options-active="currentOptionsActive"
|
||||
optionsType="dispatchers"
|
||||
/>
|
||||
|
||||
<JournalStats :statsButtons="statsButtons" />
|
||||
</div>
|
||||
|
||||
<div class="journal_refreshed-date" v-if="dataRefreshedAt">
|
||||
{{ $t('journal.data-refreshed-at') }}: {{ dataRefreshedAt.toLocaleString($i18n.locale) }}
|
||||
@@ -32,26 +36,34 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
import JournalOptions from '../components/JournalView/JournalOptions.vue';
|
||||
import { URLs } from '../scripts/utils/apiURLs';
|
||||
import { useStore } from '../store/mainStore';
|
||||
import JournalDispatchersList from '../components/JournalView/JournalDispatchersList.vue';
|
||||
|
||||
import JournalHeader from '../components/JournalView/JournalHeader.vue';
|
||||
import http from '../http';
|
||||
import { useMainStore } from '../store/mainStore';
|
||||
import { LocationQuery } from 'vue-router';
|
||||
import { Journal } from '../components/JournalView/typings';
|
||||
import { API } from '../typings/api';
|
||||
import { Status } from '../typings/common';
|
||||
|
||||
const DISPATCHERS_API_URL = `${URLs.stacjownikAPI}/api/getDispatchers`;
|
||||
import JournalDispatchersList from '../components/JournalView/JournalDispatchers/JournalDispatchersList.vue';
|
||||
import JournalOptions from '../components/JournalView/JournalOptions.vue';
|
||||
import JournalHeader from '../components/JournalView/JournalHeader.vue';
|
||||
import JournalStats from '../components/JournalView/JournalStats.vue';
|
||||
|
||||
const statsButtons: Journal.StatsButton[] = [
|
||||
{
|
||||
tab: Journal.StatsTab.DISPATCHER_STATS,
|
||||
localeKey: 'journal.dispatcher-stats.button',
|
||||
iconName: 'user',
|
||||
disabled: true
|
||||
}
|
||||
];
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
JournalOptions,
|
||||
JournalDispatchersList,
|
||||
JournalHeader
|
||||
JournalHeader,
|
||||
JournalStats,
|
||||
JournalDispatchersList
|
||||
},
|
||||
name: 'JournalDispatchers',
|
||||
|
||||
@@ -68,6 +80,8 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
statsButtons,
|
||||
|
||||
currentQuery: '',
|
||||
currentQueryArray: [] as string[],
|
||||
dataRefreshedAt: null as Date | null,
|
||||
@@ -92,7 +106,7 @@ export default defineComponent({
|
||||
'search-dispatcher': '',
|
||||
'search-station': '',
|
||||
'search-date': ''
|
||||
} as Journal.DispatcherSearcher);
|
||||
} as Journal.DispatcherSearchType);
|
||||
|
||||
const countFromIndex = ref(0);
|
||||
const countLimit = 15;
|
||||
@@ -105,7 +119,7 @@ export default defineComponent({
|
||||
const scrollElement: Ref<HTMLElement | null> = ref(null);
|
||||
|
||||
return {
|
||||
store: useStore(),
|
||||
mainStore: useMainStore(),
|
||||
|
||||
sorterActive,
|
||||
searchersValues,
|
||||
@@ -123,6 +137,15 @@ export default defineComponent({
|
||||
this.currentOptionsActive =
|
||||
q.length > 2 ||
|
||||
q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1] != 'timestampFrom');
|
||||
},
|
||||
|
||||
'mainStore.dispatcherStatsData'(stats) {
|
||||
this.statsButtons.find((sb) => sb.tab == Journal.StatsTab.DISPATCHER_STATS)!.disabled =
|
||||
stats === undefined;
|
||||
},
|
||||
|
||||
async 'mainStore.dispatcherStatsName'() {
|
||||
this.fetchDispatcherStats();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -145,6 +168,16 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleRouteParams() {
|
||||
this.$router.push({
|
||||
query: {
|
||||
'search-date': this.searchersValues['search-date'] || undefined,
|
||||
'search-station': this.searchersValues['search-station'] || undefined,
|
||||
'search-dispatcher': this.searchersValues['search-dispatcher'] || undefined
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handleScroll(e: Event) {
|
||||
const listElement = e.target as HTMLElement;
|
||||
const scrollTop = listElement.scrollTop;
|
||||
@@ -157,24 +190,44 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
handleQueries(query: LocationQuery) {
|
||||
const queryKeys = Object.keys(query);
|
||||
|
||||
if (queryKeys.includes('sceneryName')) this.setSearchers('', `${query.sceneryName}`, '');
|
||||
if (queryKeys.includes('dispatcherName'))
|
||||
this.setSearchers('', '', `${query.dispatcherName}`);
|
||||
this.setOptions(query as any);
|
||||
},
|
||||
|
||||
setSearchers(date: string, station: string, dispatcher: string) {
|
||||
this.searchersValues['search-date'] = date;
|
||||
this.searchersValues['search-station'] = station;
|
||||
this.searchersValues['search-dispatcher'] = dispatcher;
|
||||
async fetchDispatcherStats() {
|
||||
if (!this.mainStore.dispatcherStatsName) {
|
||||
this.mainStore.dispatcherStatsData = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const statsData: API.DispatcherStats.Response = await (
|
||||
await http.get('api/getDispatcherStats', {
|
||||
params: {
|
||||
name: this.mainStore.dispatcherStatsName
|
||||
}
|
||||
})
|
||||
).data;
|
||||
|
||||
this.mainStore.dispatcherStatsData = statsData;
|
||||
} catch (error) {
|
||||
this.mainStore.dispatcherStatsData = undefined;
|
||||
|
||||
console.error('Ups! Wystąpił błąd przy próbie pobrania statystyk dyżurnego! :/');
|
||||
}
|
||||
},
|
||||
|
||||
setOptions(options: { [key: string]: string }) {
|
||||
this.searchersValues['search-date'] = options['search-date'] ?? '';
|
||||
this.searchersValues['search-station'] = options['search-station'] ?? '';
|
||||
this.searchersValues['search-dispatcher'] = options['search-dispatcher'] ?? '';
|
||||
|
||||
this.sorterActive.id =
|
||||
(options['sorter-active'] as Journal.DispatcherSorterKey) ?? 'timestampFrom';
|
||||
},
|
||||
|
||||
resetOptions() {
|
||||
this.setSearchers('', '', '');
|
||||
this.setOptions({});
|
||||
this.sorterActive.id = 'timestampFrom';
|
||||
|
||||
this.fetchHistoryData();
|
||||
},
|
||||
|
||||
async addHistoryData() {
|
||||
@@ -183,9 +236,7 @@ export default defineComponent({
|
||||
this.countFromIndex = this.historyList.length;
|
||||
|
||||
const responseData: API.DispatcherHistory.Response = await (
|
||||
await axios.get(
|
||||
`${DISPATCHERS_API_URL}?${this.currentQuery}&countFrom=${this.countFromIndex}`
|
||||
)
|
||||
await http.get(`api/getDispatchers?${this.currentQuery}&countFrom=${this.countFromIndex}`)
|
||||
).data;
|
||||
|
||||
if (!responseData) return;
|
||||
@@ -232,7 +283,7 @@ export default defineComponent({
|
||||
if (reset) this.dataStatus = Status.Data.Loading;
|
||||
|
||||
const responseData: API.DispatcherHistory.Response = await (
|
||||
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}`)
|
||||
await http.get(`api/getDispatchers?${this.currentQuery}`)
|
||||
).data;
|
||||
|
||||
if (!responseData) {
|
||||
@@ -246,7 +297,7 @@ export default defineComponent({
|
||||
this.historyList = responseData;
|
||||
|
||||
// Stats display
|
||||
this.store.dispatcherStatsName =
|
||||
this.mainStore.dispatcherStatsName =
|
||||
this.historyList.length > 0 && this.searchersValues['search-dispatcher'].trim()
|
||||
? this.historyList[0].dispatcherName
|
||||
: '';
|
||||
|
||||
+139
-79
@@ -3,18 +3,19 @@
|
||||
<JournalHeader />
|
||||
|
||||
<div class="journal_wrapper">
|
||||
<JournalOptions
|
||||
@on-search-confirm="fetchHistoryData"
|
||||
@on-options-reset="resetOptions"
|
||||
@on-refresh-data="fetchHistoryData"
|
||||
:sorter-option-ids="['timetableId', 'beginDate', 'routeDistance', 'allStopsCount']"
|
||||
:filters="journalTimetableFilters"
|
||||
:currentOptionsActive="currentOptionsActive"
|
||||
:data-status="dataStatus"
|
||||
optionsType="timetables"
|
||||
/>
|
||||
<div class="journal_top-bar">
|
||||
<JournalOptions
|
||||
@onOptionsReset="resetOptions"
|
||||
@onRefreshData="fetchHistoryData"
|
||||
:sorter-option-ids="['timetableId', 'beginDate', 'routeDistance', 'allStopsCount']"
|
||||
:filters="journalTimetableFilters"
|
||||
:currentOptionsActive="currentOptionsActive"
|
||||
:data-status="dataStatus"
|
||||
optionsType="timetables"
|
||||
/>
|
||||
|
||||
<JournalStats />
|
||||
<JournalStats :statsButtons="statsButtons" />
|
||||
</div>
|
||||
|
||||
<div class="journal_refreshed-date" v-if="dataRefreshedAt">
|
||||
{{ $t('journal.data-refreshed-at') }}: {{ dataRefreshedAt.toLocaleString($i18n.locale) }}
|
||||
@@ -35,7 +36,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
|
||||
import axios from 'axios';
|
||||
|
||||
import dateMixin from '../mixins/dateMixin';
|
||||
import routerMixin from '../mixins/routerMixin';
|
||||
@@ -45,8 +45,7 @@ import JournalOptions from '../components/JournalView/JournalOptions.vue';
|
||||
import JournalStats from '../components/JournalView/JournalStats.vue';
|
||||
import JournalHeader from '../components/JournalView/JournalHeader.vue';
|
||||
|
||||
import { URLs } from '../scripts/utils/apiURLs';
|
||||
import { useStore } from '../store/mainStore';
|
||||
import { useMainStore } from '../store/mainStore';
|
||||
|
||||
import { LocationQuery } from 'vue-router';
|
||||
|
||||
@@ -54,50 +53,62 @@ import JournalTimetablesList from '../components/JournalView/JournalTimetables/J
|
||||
import { Journal } from '../components/JournalView/typings';
|
||||
import { Status } from '../typings/common';
|
||||
import { API } from '../typings/api';
|
||||
|
||||
const TIMETABLES_API_URL = `${URLs.stacjownikAPI}/api/getTimetables`;
|
||||
import http from '../http';
|
||||
|
||||
export const journalTimetableFilters: Journal.TimetableFilter[] = [
|
||||
{
|
||||
id: Journal.TimetableFilterId.ALL,
|
||||
id: Journal.TimetableFilterId.ALL_STATUSES,
|
||||
filterSection: Journal.FilterSection.TIMETABLE_STATUS,
|
||||
isActive: true
|
||||
isActive: true,
|
||||
default: true
|
||||
},
|
||||
|
||||
{
|
||||
id: Journal.TimetableFilterId.ACTIVE,
|
||||
filterSection: Journal.FilterSection.TIMETABLE_STATUS,
|
||||
isActive: false
|
||||
isActive: false,
|
||||
default: false
|
||||
},
|
||||
|
||||
{
|
||||
id: Journal.TimetableFilterId.FULFILLED,
|
||||
filterSection: Journal.FilterSection.TIMETABLE_STATUS,
|
||||
isActive: false
|
||||
isActive: false,
|
||||
default: false
|
||||
},
|
||||
|
||||
{
|
||||
id: Journal.TimetableFilterId.ABANDONED,
|
||||
filterSection: Journal.FilterSection.TIMETABLE_STATUS,
|
||||
isActive: false
|
||||
isActive: false,
|
||||
default: false
|
||||
},
|
||||
|
||||
{
|
||||
id: Journal.TimetableFilterId.TWR_SKR,
|
||||
filterSection: Journal.FilterSection.TWRSKR,
|
||||
isActive: true
|
||||
id: Journal.TimetableFilterId.ALL_SPECIALS,
|
||||
filterSection: Journal.FilterSection.SPECIAL,
|
||||
isActive: true,
|
||||
default: true
|
||||
},
|
||||
|
||||
{
|
||||
id: Journal.TimetableFilterId.TWR,
|
||||
filterSection: Journal.FilterSection.TWRSKR,
|
||||
isActive: false
|
||||
filterSection: Journal.FilterSection.SPECIAL,
|
||||
isActive: false,
|
||||
default: false
|
||||
},
|
||||
|
||||
{
|
||||
id: Journal.TimetableFilterId.SKR,
|
||||
filterSection: Journal.FilterSection.TWRSKR,
|
||||
isActive: false
|
||||
filterSection: Journal.FilterSection.SPECIAL,
|
||||
isActive: false,
|
||||
default: false
|
||||
},
|
||||
{
|
||||
id: Journal.TimetableFilterId.TWR_SKR,
|
||||
filterSection: Journal.FilterSection.SPECIAL,
|
||||
isActive: false,
|
||||
default: false
|
||||
}
|
||||
];
|
||||
|
||||
@@ -107,8 +118,12 @@ interface TimetablesQueryParams {
|
||||
timetableId?: string;
|
||||
|
||||
authorName?: string;
|
||||
timestampFrom?: number;
|
||||
timestampTo?: number;
|
||||
// timestampFrom?: number;
|
||||
// timestampTo?: number;
|
||||
|
||||
dateFrom?: string;
|
||||
dateTo?: string;
|
||||
|
||||
issuedFrom?: string;
|
||||
|
||||
countFrom?: number;
|
||||
@@ -141,6 +156,24 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
journalTimetableFilters,
|
||||
mainStore: useMainStore(),
|
||||
|
||||
statsButtons: [
|
||||
{
|
||||
tab: Journal.StatsTab.DAILY_STATS,
|
||||
localeKey: 'journal.daily-stats.button',
|
||||
iconName: 'stats',
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
tab: Journal.StatsTab.DRIVER_STATS,
|
||||
localeKey: 'journal.driver-stats.button',
|
||||
iconName: 'train',
|
||||
disabled: true
|
||||
}
|
||||
],
|
||||
|
||||
currentQueryParams: {} as TimetablesQueryParams,
|
||||
dataRefreshedAt: null as Date | null,
|
||||
|
||||
@@ -152,7 +185,6 @@ export default defineComponent({
|
||||
currentOptionsActive: false,
|
||||
|
||||
timetableHistory: [] as API.TimetableHistory.Response,
|
||||
journalTimetableFilters,
|
||||
|
||||
dataStatus: Status.Data.Loading,
|
||||
dataErrorMessage: ''
|
||||
@@ -160,10 +192,11 @@ export default defineComponent({
|
||||
|
||||
setup() {
|
||||
const sorterActive: Journal.TimetableSorter = reactive({ id: 'timetableId', dir: 'desc' });
|
||||
// const journalFilterActive = ref(journalTimetableFilters[0]);
|
||||
|
||||
const initFilters: readonly Journal.TimetableFilter[] = JSON.parse(
|
||||
JSON.stringify(journalTimetableFilters)
|
||||
);
|
||||
|
||||
const filterList: Journal.TimetableFilter[] = reactive(JSON.parse(JSON.stringify(initFilters)));
|
||||
|
||||
const searchersValues = reactive({
|
||||
@@ -192,15 +225,22 @@ export default defineComponent({
|
||||
countFromIndex,
|
||||
countLimit,
|
||||
|
||||
scrollElement,
|
||||
|
||||
store: useStore()
|
||||
scrollElement
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
currentQueryParams(q: TimetablesQueryParams) {
|
||||
this.currentOptionsActive = Object.values(q).some((v) => v !== undefined);
|
||||
},
|
||||
|
||||
'mainStore.driverStatsData'(driverStats) {
|
||||
this.statsButtons.find((sb) => sb.tab == Journal.StatsTab.DRIVER_STATS)!.disabled =
|
||||
driverStats === undefined;
|
||||
},
|
||||
|
||||
async 'mainStore.driverStatsName'() {
|
||||
this.fetchDriverStats();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -228,42 +268,51 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
handleQueries(query: LocationQuery) {
|
||||
const queryKeys = Object.keys(query);
|
||||
|
||||
if (queryKeys.includes('timetableId'))
|
||||
this.setSearchers('', '', `#${query.timetableId}`, '', '');
|
||||
if (queryKeys.includes('issuedFrom'))
|
||||
this.setSearchers('', '', '', '', `${query.issuedFrom}`);
|
||||
if (queryKeys.includes('authorName'))
|
||||
this.setSearchers('', '', '', `${query.authorName}`, '');
|
||||
this.setOptions(query as any);
|
||||
},
|
||||
|
||||
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;
|
||||
async fetchDriverStats() {
|
||||
if (!this.mainStore.driverStatsName) {
|
||||
this.mainStore.driverStatsData = undefined;
|
||||
this.mainStore.driverStatsStatus = Status.Data.Initialized;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.mainStore.driverStatsStatus = Status.Data.Loading;
|
||||
|
||||
const statsData: API.DriverStats.Response = await (
|
||||
await http.get(`api/getDriverInfo?name=${this.mainStore.driverStatsName}`)
|
||||
).data;
|
||||
|
||||
this.mainStore.driverStatsData = statsData;
|
||||
this.mainStore.driverStatsStatus = Status.Data.Loaded;
|
||||
} catch (error) {
|
||||
this.mainStore.driverStatsData = undefined;
|
||||
this.mainStore.driverStatsStatus = Status.Data.Error;
|
||||
console.error('Ups! Wystąpił błąd przy próbie pobrania statystyk maszynisty! :/');
|
||||
}
|
||||
},
|
||||
|
||||
setOptions(options: { [key: string]: string }) {
|
||||
this.searchersValues['search-date'] = options['search-date'] ?? '';
|
||||
this.searchersValues['search-driver'] = options['search-driver'] ?? '';
|
||||
this.searchersValues['search-train'] = options['search-train'] ?? '';
|
||||
this.searchersValues['search-dispatcher'] = options['search-dispatcher'] ?? '';
|
||||
this.searchersValues['search-issuedFrom'] = options['search-issuedFrom'] ?? '';
|
||||
|
||||
this.sorterActive.id =
|
||||
(options['sorter-active'] as Journal.TimetableSorterKey) ?? 'timetableId';
|
||||
|
||||
this.filterList.forEach((f) => {
|
||||
f.isActive =
|
||||
options[f.filterSection] === f.id ||
|
||||
(options[f.filterSection] === undefined && f.default);
|
||||
});
|
||||
},
|
||||
|
||||
resetOptions() {
|
||||
this.setSearchers('', '', '', '', '');
|
||||
|
||||
this.sorterActive.id = 'timetableId';
|
||||
|
||||
this.filterList.forEach(
|
||||
(f) =>
|
||||
(f.isActive =
|
||||
this.initFilters.find((initFilter) => initFilter.id == f.id)?.isActive || false)
|
||||
);
|
||||
|
||||
this.fetchHistoryData();
|
||||
this.setOptions({});
|
||||
},
|
||||
|
||||
async addHistoryData() {
|
||||
@@ -272,7 +321,7 @@ export default defineComponent({
|
||||
this.currentQueryParams['countFrom'] = this.timetableHistory.length;
|
||||
|
||||
const responseData: API.TimetableHistory.Response = await (
|
||||
await axios.get(`${TIMETABLES_API_URL}`, {
|
||||
await http.get('api/getTimetables', {
|
||||
params: { ...this.currentQueryParams }
|
||||
})
|
||||
).data;
|
||||
@@ -292,13 +341,19 @@ export default defineComponent({
|
||||
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 dateFrom = 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;
|
||||
let dateTo: string | undefined = undefined;
|
||||
|
||||
if (dateFrom) {
|
||||
const d = new Date(dateFrom);
|
||||
d.setDate(d.getDate() + 1);
|
||||
|
||||
dateTo = d.toISOString().split('T')[0];
|
||||
}
|
||||
// const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) : undefined;
|
||||
// const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
|
||||
|
||||
const queryParams: TimetablesQueryParams = {};
|
||||
|
||||
@@ -321,23 +376,28 @@ export default defineComponent({
|
||||
queryParams['fulfilled'] = 1;
|
||||
break;
|
||||
|
||||
case Journal.TimetableFilterId.ALL:
|
||||
case Journal.TimetableFilterId.ALL_STATUSES:
|
||||
queryParams['terminated'] = undefined;
|
||||
queryParams['fulfilled'] = undefined;
|
||||
break;
|
||||
|
||||
case Journal.TimetableFilterId.TWR_SKR:
|
||||
case Journal.TimetableFilterId.ALL_SPECIALS:
|
||||
queryParams['twr'] = undefined;
|
||||
queryParams['skr'] = undefined;
|
||||
break;
|
||||
|
||||
case Journal.TimetableFilterId.TWR:
|
||||
queryParams['twr'] = 1;
|
||||
queryParams['skr'] = undefined;
|
||||
queryParams['skr'] = 0;
|
||||
break;
|
||||
|
||||
case Journal.TimetableFilterId.SKR:
|
||||
queryParams['twr'] = undefined;
|
||||
queryParams['twr'] = 0;
|
||||
queryParams['skr'] = 1;
|
||||
break;
|
||||
|
||||
case Journal.TimetableFilterId.TWR_SKR:
|
||||
queryParams['twr'] = 1;
|
||||
queryParams['skr'] = 1;
|
||||
break;
|
||||
|
||||
@@ -352,8 +412,8 @@ export default defineComponent({
|
||||
queryParams['countLimit'] = undefined;
|
||||
|
||||
queryParams['authorName'] = authorName;
|
||||
queryParams['timestampFrom'] = timestampFrom;
|
||||
queryParams['timestampTo'] = timestampTo;
|
||||
queryParams['dateFrom'] = dateFrom;
|
||||
queryParams['dateTo'] = dateTo;
|
||||
queryParams['issuedFrom'] = issuedFrom;
|
||||
queryParams['sortBy'] =
|
||||
this.sorterActive.id != 'timetableId' ? this.sorterActive.id : undefined;
|
||||
@@ -365,7 +425,7 @@ export default defineComponent({
|
||||
|
||||
try {
|
||||
const responseData: API.TimetableHistory.Response = await (
|
||||
await axios.get(`${TIMETABLES_API_URL}`, {
|
||||
await http.get('api/getTimetables', {
|
||||
params: this.currentQueryParams
|
||||
})
|
||||
).data;
|
||||
|
||||
+23
-16
@@ -1,14 +1,6 @@
|
||||
<template>
|
||||
<div class="scenery-view">
|
||||
<div class="scenery-offline" v-if="!stationInfo && store.dataStatuses.sceneries == 2">
|
||||
<div>{{ $t('scenery.no-scenery') }}</div>
|
||||
|
||||
<action-button>
|
||||
<router-link to="/">{{ $t('scenery.return-btn') }}</router-link>
|
||||
</action-button>
|
||||
</div>
|
||||
|
||||
<div class="scenery-wrapper" v-if="stationInfo" ref="card-wrapper">
|
||||
<div class="scenery-wrapper" ref="card-wrapper">
|
||||
<div class="scenery-left">
|
||||
<div class="scenery-actions">
|
||||
<button class="back-btn btn" :title="$t('scenery.return-btn')" @click="navigateTo('/')">
|
||||
@@ -16,7 +8,11 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<SceneryHeader :station="stationInfo" :onlineScenery="onlineSceneryInfo" />
|
||||
<SceneryHeader
|
||||
:stationName="station"
|
||||
:station="stationInfo"
|
||||
:onlineScenery="onlineSceneryInfo"
|
||||
/>
|
||||
<SceneryInfo :station="stationInfo" :onlineScenery="onlineSceneryInfo" />
|
||||
</div>
|
||||
|
||||
@@ -33,7 +29,14 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<keep-alive>
|
||||
<div
|
||||
v-if="
|
||||
apiStore.dataStatuses.sceneries == Status.Loading ||
|
||||
apiStore.dataStatuses.connection == Status.Loading
|
||||
"
|
||||
></div>
|
||||
|
||||
<keep-alive v-else>
|
||||
<component
|
||||
:is="currentMode"
|
||||
:onlineScenery="onlineSceneryInfo"
|
||||
@@ -50,7 +53,7 @@
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import routerMixin from '../mixins/routerMixin';
|
||||
import { useStore } from '../store/mainStore';
|
||||
import { useMainStore } from '../store/mainStore';
|
||||
|
||||
import SceneryInfo from '../components/SceneryView/SceneryInfo.vue';
|
||||
import SceneryHeader from '../components/SceneryView/SceneryHeader.vue';
|
||||
@@ -58,6 +61,8 @@ import SceneryTimetable from '../components/SceneryView/SceneryTimetable.vue';
|
||||
import SceneryTimetablesHistory from '../components/SceneryView/SceneryTimetablesHistory.vue';
|
||||
import SceneryDispatchersHistory from '../components/SceneryView/SceneryDispatchersHistory.vue';
|
||||
import ActionButton from '../components/Global/ActionButton.vue';
|
||||
import { Status } from '../typings/common';
|
||||
import { useApiStore } from '../store/apiStore';
|
||||
|
||||
enum SceneryViewMode {
|
||||
'TIMETABLES_ACTIVE',
|
||||
@@ -92,7 +97,9 @@ export default defineComponent({
|
||||
mixins: [routerMixin],
|
||||
|
||||
data: () => ({
|
||||
store: useStore(),
|
||||
store: useMainStore(),
|
||||
apiStore: useApiStore(),
|
||||
|
||||
viewModes: [
|
||||
{
|
||||
id: 'scenery.option-active-timetables',
|
||||
@@ -110,7 +117,8 @@ export default defineComponent({
|
||||
sceneryViewMode: SceneryViewMode,
|
||||
selectedCheckpoint: '',
|
||||
currentViewCompontent: 'SceneryTimetable',
|
||||
onlineFrom: -1
|
||||
onlineFrom: -1,
|
||||
Status: Status.Data
|
||||
}),
|
||||
|
||||
// activated() {
|
||||
@@ -185,8 +193,6 @@ button.back-btn {
|
||||
&-view {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
&-offline {
|
||||
@@ -215,6 +221,7 @@ button.back-btn {
|
||||
|
||||
width: 100%;
|
||||
max-width: 1700px;
|
||||
min-height: 100vh;
|
||||
|
||||
margin: 1rem 0;
|
||||
text-align: center;
|
||||
|
||||
@@ -21,7 +21,7 @@ import { defineComponent } from 'vue';
|
||||
import StationTable from '../components/StationsView/StationTable.vue';
|
||||
import StationFilterCard from '../components/StationsView/StationFilterCard.vue';
|
||||
import { useStationFiltersStore } from '../store/stationFiltersStore';
|
||||
import { useStore } from '../store/mainStore';
|
||||
import { useMainStore } from '../store/mainStore';
|
||||
import Donation from '../components/Global/Donation.vue';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -37,7 +37,7 @@ export default defineComponent({
|
||||
STORAGE_KEY: 'options_saved',
|
||||
focusedStationName: '',
|
||||
filterStore: useStationFiltersStore(),
|
||||
store: useStore(),
|
||||
store: useMainStore(),
|
||||
|
||||
isDonationModalOpen: false
|
||||
}),
|
||||
|
||||
@@ -21,7 +21,7 @@ import TrainOptions from '../components/TrainsView/TrainOptions.vue';
|
||||
import TrainTable from '../components/TrainsView/TrainTable.vue';
|
||||
import modalTrainMixin from '../mixins/modalTrainMixin';
|
||||
import Train from '../scripts/interfaces/Train';
|
||||
import { useStore } from '../store/mainStore';
|
||||
import { useMainStore } from '../store/mainStore';
|
||||
import { TrainFilter, trainFilters } from '../components/TrainsView/typings';
|
||||
import { filteredTrainList } from '../managers/trainFilterManager';
|
||||
import TrainStats from '../components/TrainsView/TrainStats.vue';
|
||||
@@ -58,7 +58,7 @@ export default defineComponent({
|
||||
}),
|
||||
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const store = useMainStore();
|
||||
const initTrainFilters = [...trainFilters.map((f) => ({ ...f }))];
|
||||
|
||||
const sorterActive = reactive({ id: 'routeDistance', dir: -1 });
|
||||
|
||||
+4
-2
@@ -6,20 +6,22 @@ export default defineConfig({
|
||||
server: {
|
||||
port: 5001
|
||||
},
|
||||
publicDir: 'public',
|
||||
plugins: [
|
||||
vue(),
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
includeAssets: ['/images/*.png', '/fonts/*.woff', '/fonts/*.woff2'],
|
||||
|
||||
workbox: {
|
||||
disableDevLogs: true,
|
||||
globPatterns: ['**/*.{js,css,html,png,svg,jpg}'],
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: new RegExp('^https://stacjownik.spythere.pl/api/getSceneries', 'i'),
|
||||
urlPattern: new RegExp('^https://stacjownik.spythere.eu/api/getSceneries', 'i'),
|
||||
handler: 'NetworkFirst',
|
||||
options: {
|
||||
cacheName: 'sceneries-cache',
|
||||
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200]
|
||||
}
|
||||
|
||||
@@ -4592,9 +4592,9 @@ vite-plugin-pwa@^0.16.5:
|
||||
workbox-window "^7.0.0"
|
||||
|
||||
"vite@^3.1.0 || ^4.0.0", vite@^4.0.0, vite@^4.4.9:
|
||||
version "4.4.9"
|
||||
resolved "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz"
|
||||
integrity sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==
|
||||
version "4.5.1"
|
||||
resolved "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz"
|
||||
integrity sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==
|
||||
dependencies:
|
||||
esbuild "^0.18.10"
|
||||
postcss "^8.4.27"
|
||||
|
||||
Reference in New Issue
Block a user