Compare commits
151 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f28600a7fa | |||
| d59ead87e6 | |||
| 34d91bc800 | |||
| cf9991d8a0 | |||
| 4ffb79d62b | |||
| d9f5edb4fe | |||
| 1b2112430a | |||
| 0a972a23ef | |||
| 6d52724d06 | |||
| 99415c35d3 | |||
| c3f687d439 | |||
| 266edfd6e6 | |||
| d32d5ad91b | |||
| c3481470cb | |||
| 57e88b9abc | |||
| 44ebf53798 | |||
| 145dc72b6b | |||
| b7f3761940 | |||
| ea7c49dfb3 | |||
| 5d6785813a | |||
| a0054aed14 | |||
| 471e6f5216 | |||
| a617eef00e | |||
| 38e700ecd6 | |||
| da1be0e10a | |||
| f49bb12948 | |||
| 02673a3d70 | |||
| 4ddc7345df | |||
| 5d822684c0 | |||
| 69fa15c70a | |||
| 9192067388 | |||
| 2b41e5b857 | |||
| 674680ff14 | |||
| 475bd2ff10 | |||
| 074d1eb155 | |||
| 378393de89 | |||
| 03e61083a7 | |||
| 0b746fce8c | |||
| 5883e710be | |||
| 3d0695a17b | |||
| 4adb76eeb0 | |||
| 4c41076519 | |||
| 77f61d17fd | |||
| 032a84cbcf | |||
| de9851ebcc | |||
| ff78eba927 | |||
| e4c5f6a322 | |||
| 0a78761928 | |||
| 4843043c29 | |||
| 9e1df1fb61 | |||
| 021474cfb0 | |||
| 7d0e68862c | |||
| 653d45dfc6 | |||
| 4a4e1240a4 | |||
| 14ca48a90d | |||
| a02f9804b1 | |||
| c5efc6fbac | |||
| cacd0a1e4e | |||
| 50375099ab | |||
| 6af67ec741 | |||
| c64112c86a | |||
| 0434702d3b | |||
| dd7d1b0bb0 | |||
| 68934a89a4 | |||
| b88a240ec1 | |||
| eaa34f3359 | |||
| febb22e1bc | |||
| 500f3c1223 | |||
| 221e0c7e82 | |||
| ca19f7e397 | |||
| a71ccd3e1a | |||
| d496c70fa8 | |||
| b9868ba52e | |||
| 59bd3fa2ef | |||
| e14d328ed9 | |||
| 36d71292bc | |||
| 2f6e2e7402 | |||
| e959eac6c5 | |||
| 8bedc4dfc6 | |||
| 73563d5db7 | |||
| 3f818069cd | |||
| cdf0b2a426 | |||
| c29ddeb78c | |||
| b81d98cab7 | |||
| 0e45bca5da | |||
| 715e66879f | |||
| 1747e15dc8 | |||
| 6a923a8e1d | |||
| 25a248e95e | |||
| aa7a6b220e | |||
| deb7b68985 | |||
| 633f05f690 | |||
| 73828867da | |||
| 75685c1e0e | |||
| 496ff95236 | |||
| 7e25327832 | |||
| 272c9f50f8 | |||
| 255e07372e | |||
| 279bbfa4db | |||
| a5c829faf5 | |||
| 5fdfaeac5e | |||
| 9beb30e3d5 | |||
| 48582e2eea | |||
| 2e721fb8bf | |||
| f93c1fbfec | |||
| c06e7b6468 | |||
| 22a6d266cb | |||
| 5f8a16401b | |||
| c9be01aa29 | |||
| 4ec058b33c | |||
| 27a5d2a406 | |||
| 58169e26f6 | |||
| fee1f4bbd5 | |||
| 240817acc3 | |||
| db3be87dd8 | |||
| 1665134d6f | |||
| df289ab734 | |||
| f74440ba6f | |||
| a25dbe9fd5 | |||
| 4fff136d6b | |||
| d06f2d5d2e | |||
| 9f68d628d0 | |||
| d64b906dac | |||
| f3e193e68a | |||
| 5640ce9f2b | |||
| 50100eb2f9 | |||
| e478c510b2 | |||
| 7ea558642f | |||
| 493145f7f2 | |||
| 4f72535365 | |||
| 8e3bf80715 | |||
| 6da586d08a | |||
| be53b9c7fb | |||
| 94ed1160a1 | |||
| 859d8d2631 | |||
| 5f3abd73c5 | |||
| d71c8bb6f9 | |||
| a3db13d79c | |||
| 8cb3da66f2 | |||
| 6e07897ac0 | |||
| 726b859f5c | |||
| 651c60707a | |||
| d4fee84603 | |||
| 86539cdf23 | |||
| 69772460b8 | |||
| 6988a83355 | |||
| b6425564c8 | |||
| caf0a9b4c5 | |||
| bd5f433d6e | |||
| 8d9cc721d6 | |||
| cceeffe49d |
@@ -0,0 +1,20 @@
|
|||||||
|
# This file was auto-generated by the Firebase CLI
|
||||||
|
# https://github.com/firebase/firebase-tools
|
||||||
|
|
||||||
|
name: Deploy to Firebase Hosting on merge
|
||||||
|
'on':
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
jobs:
|
||||||
|
build_and_deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- run: npm ci && npm run build
|
||||||
|
- uses: FirebaseExtended/action-hosting-deploy@v0
|
||||||
|
with:
|
||||||
|
repoToken: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_STACJOWNIK_TD2 }}'
|
||||||
|
channelId: live
|
||||||
|
projectId: stacjownik-td2
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# This file was auto-generated by the Firebase CLI
|
||||||
|
# https://github.com/firebase/firebase-tools
|
||||||
|
|
||||||
|
name: Deploy to Firebase Hosting on PR
|
||||||
|
'on': pull_request
|
||||||
|
jobs:
|
||||||
|
build_and_preview:
|
||||||
|
if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- run: npm ci && npm run build
|
||||||
|
- uses: FirebaseExtended/action-hosting-deploy@v0
|
||||||
|
with:
|
||||||
|
repoToken: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
|
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_STACJOWNIK_TD2 }}'
|
||||||
|
projectId: stacjownik-td2
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
/dist
|
|
||||||
/dev-dist
|
/dev-dist
|
||||||
|
/dist
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env.local
|
.env.local
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
"hosting": {
|
"hosting": {
|
||||||
"public": "dist",
|
"public": "dist",
|
||||||
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
|
"ignore": [
|
||||||
|
"firebase.json",
|
||||||
|
"**/.*",
|
||||||
|
"**/node_modules/**"
|
||||||
|
],
|
||||||
"rewrites": [
|
"rewrites": [
|
||||||
{
|
{
|
||||||
"source": "**",
|
"source": "**",
|
||||||
@@ -10,4 +14,3 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||||
|
|
||||||
<meta name="keywords" content="Stacjownik, TD2, Train Driver 2, stacjownik-td2" />
|
<meta name="keywords" content="Stacjownik, TD2, Train Driver 2, stacjownik-td2, stacjownik, td2.info.pl" />
|
||||||
<meta name="description" content="Automatycznie odświeżana strona wyświetlająca stacje w Train Driver 2!" />
|
<meta name="description" content="Pomocnik maszynisty i dyżurnego symulatora Train Driver 2" />
|
||||||
|
|
||||||
<title>Stacjownik</title>
|
<title>Stacjownik</title>
|
||||||
|
|
||||||
@@ -24,21 +24,7 @@
|
|||||||
<link rel="icon" href="favicon-16.png" sizes="16x16" type="image/png" />
|
<link rel="icon" href="favicon-16.png" sizes="16x16" type="image/png" />
|
||||||
<link rel="icon" href="favicon.ico" />
|
<link rel="icon" href="favicon.ico" />
|
||||||
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;700&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@500;700&display=swap" rel="stylesheet" />
|
||||||
|
|
||||||
<script src="https://www.gstatic.com/firebasejs/8.1.1/firebase-app.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const firebaseConfig = {
|
|
||||||
apiKey: 'AIzaSyBI36X2-p7vU1flxoJdCEc0noByyTe1mpw',
|
|
||||||
authDomain: 'stacjownik-td2.firebaseapp.com',
|
|
||||||
databaseURL: 'https://stacjownik-td2.firebaseio.com',
|
|
||||||
projectId: 'stacjownik-td2',
|
|
||||||
storageBucket: 'stacjownik-td2.appspot.com',
|
|
||||||
};
|
|
||||||
|
|
||||||
firebase.initializeApp(firebaseConfig);
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -46,3 +32,4 @@
|
|||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "stacjownik",
|
"name": "stacjownik",
|
||||||
"version": "1.10.8",
|
"version": "1.16.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"deploy": "yarn build && firebase deploy --only hosting",
|
"deploy": "yarn build && firebase deploy --only hosting",
|
||||||
"preview": "vite preview"
|
"preview": "yarn build && vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.12.1",
|
"core-js": "^3.12.1",
|
||||||
@@ -21,12 +21,13 @@
|
|||||||
"vue-router": "^4.0.0-0"
|
"vue-router": "^4.0.0-0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.8.3",
|
"@types/node": "^18.11.17",
|
||||||
"@vitejs/plugin-vue": "^3.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"axios": "^1.1.2",
|
"axios": "^1.2.1",
|
||||||
"typescript": "^4.6.4",
|
"typescript": "^4.9.4",
|
||||||
"vite": "^3.0.0",
|
"vite": "^4.0.3",
|
||||||
"vue-tsc": "^1.0.3"
|
"vite-plugin-pwa": "^0.14.0",
|
||||||
|
"vue-tsc": "^1.0.18"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
|
|||||||
@@ -33,7 +33,8 @@
|
|||||||
.route {
|
.route {
|
||||||
margin: 0 0.2em;
|
margin: 0 0.2em;
|
||||||
|
|
||||||
&-active {
|
&-active,
|
||||||
|
&[data-active='true'] {
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
@@ -45,7 +46,11 @@
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
|
||||||
@include smallScreen() {
|
@include smallScreen() {
|
||||||
font-size: calc(0.55rem + 1vw);
|
font-size: calc(0.55rem + 1.1vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include screenLandscape() {
|
||||||
|
font-size: calc(0.45rem + 0.8vw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +91,11 @@ footer.app_footer {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 1.1em;
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
}
|
||||||
|
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
background: #111;
|
background: #111;
|
||||||
|
|||||||
@@ -6,11 +6,13 @@
|
|||||||
</keep-alive>
|
</keep-alive>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
|
<UpdatePrompt />
|
||||||
|
|
||||||
<AppHeader :current-lang="currentLang" @change-lang="changeLang" />
|
<AppHeader :current-lang="currentLang" @change-lang="changeLang" />
|
||||||
|
|
||||||
<main class="app_main">
|
<main class="app_main">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<keep-alive>
|
<keep-alive exclude="JournalView">
|
||||||
<component :is="Component" :key="$route.name" />
|
<component :is="Component" :key="$route.name" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</router-view>
|
</router-view>
|
||||||
@@ -19,7 +21,10 @@
|
|||||||
<footer class="app_footer">
|
<footer class="app_footer">
|
||||||
©
|
©
|
||||||
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
||||||
{{ new Date().getUTCFullYear() }} | <a :href="releaseURL" target="_blank">v{{ VERSION }}</a>
|
{{ new Date().getUTCFullYear() }} |
|
||||||
|
<a :href="releaseURL" target="_blank">v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}</a>
|
||||||
|
<br />
|
||||||
|
<a href="https://discord.gg/x2mpNN3svk"><img :src="getIcon('discord', 'png')" alt=""> <b>{{ $t('footer.discord') }}</b></a>
|
||||||
|
|
||||||
<div style="display: none">∫ ukryta taktyczna całka do programowania w HTMLu</div>
|
<div style="display: none">∫ ukryta taktyczna całka do programowania w HTMLu</div>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -27,7 +32,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, provide, ref, watch } from 'vue';
|
import { computed, defineComponent, KeepAlive, provide, ref, watch } from 'vue';
|
||||||
|
|
||||||
import Clock from './components/App/Clock.vue';
|
import Clock from './components/App/Clock.vue';
|
||||||
|
|
||||||
@@ -41,6 +46,10 @@ import StorageManager from './scripts/managers/storageManager';
|
|||||||
import imageMixin from './mixins/imageMixin';
|
import imageMixin from './mixins/imageMixin';
|
||||||
import AppHeader from './components/App/AppHeader.vue';
|
import AppHeader from './components/App/AppHeader.vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import UpdatePrompt from './components/App/UpdatePrompt.vue';
|
||||||
|
import { VERSION } from 'vue-i18n';
|
||||||
|
import { RouterView } from 'vue-router';
|
||||||
|
import useCustomSW from './mixins/useCustomSW';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@@ -49,6 +58,7 @@ export default defineComponent({
|
|||||||
SelectBox,
|
SelectBox,
|
||||||
TrainModal,
|
TrainModal,
|
||||||
AppHeader,
|
AppHeader,
|
||||||
|
UpdatePrompt,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [imageMixin],
|
mixins: [imageMixin],
|
||||||
@@ -57,6 +67,8 @@ export default defineComponent({
|
|||||||
const store = useStore();
|
const store = useStore();
|
||||||
store.connectToAPI();
|
store.connectToAPI();
|
||||||
|
|
||||||
|
const { offlineReady } = useCustomSW();
|
||||||
|
|
||||||
const isFilterCardVisible = ref(false);
|
const isFilterCardVisible = ref(false);
|
||||||
|
|
||||||
provide('isFilterCardVisible', isFilterCardVisible);
|
provide('isFilterCardVisible', isFilterCardVisible);
|
||||||
@@ -77,10 +89,30 @@ export default defineComponent({
|
|||||||
|
|
||||||
currentLang: 'pl',
|
currentLang: 'pl',
|
||||||
releaseURL: '',
|
releaseURL: '',
|
||||||
|
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.loadLang();
|
this.loadLang();
|
||||||
|
|
||||||
|
this.store.isOffline = !window.navigator.onLine;
|
||||||
|
|
||||||
|
window.addEventListener('offline', () => {
|
||||||
|
this.store.isOffline = true;
|
||||||
|
|
||||||
|
this.store.apiData = {
|
||||||
|
stations: [],
|
||||||
|
dispatchers: [],
|
||||||
|
trains: [],
|
||||||
|
connectedSocketCount: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.store.setOnlineData();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('online', () => {
|
||||||
|
this.store.isOffline = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted() {
|
async mounted() {
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<rect width="60" height="60" fill="#898989"/>
|
<rect y="-0.00012207" width="60" height="60" fill="#898989"/>
|
||||||
<path d="M30.5 6.04878H35.2195" stroke="#BFBFBF"/>
|
<path d="M29.0126 32.4897V10.2511V9.52028H30.4337V10.2511V57.234H29.0126V32.4897Z" fill="#BFBFBF"/>
|
||||||
<path d="M27.9024 4.00303C25.2115 4.10008 24.2403 6.24494 24 7.41767H32.0488C31.8486 6.16406 30.5934 3.90598 27.9024 4.00303Z" fill="black"/>
|
<path d="M26.955 29.3992V32.9949L29.7672 36.9105" stroke="black" stroke-width="0.61183"/>
|
||||||
<path d="M33.0244 29.6688V5.47793V4.68292H34.4878V5.47793V56.5854H33.0244V32.5H27.5V28.5V28.0163L28.5 28V31.5L31.9268 31.5447H33.0244V29.6688Z" fill="#BFBFBF"/>
|
<rect x="29.0051" y="34.0686" width="1.42857" height="22.8196" fill="white"/>
|
||||||
<path d="M28.1463 29.2683C30.8373 29.1712 31.8085 27.0264 32.0488 25.8537H24C24.2002 27.1073 25.4554 29.3654 28.1463 29.2683Z" fill="black"/>
|
<rect x="29.0051" y="34.0686" width="1.42857" height="5.18627" fill="#FF0000"/>
|
||||||
<path d="M32.0488 25.8537V7.86993V7.41464H24V25.8537H32.0488Z" fill="black"/>
|
<rect x="29.0051" y="54.8137" width="1.42857" height="5.18627" fill="#FF0000"/>
|
||||||
<path d="M25 26V29.5L33.8781 44.9756" stroke="black"/>
|
<rect x="29.0051" y="44.4412" width="1.42857" height="5.18627" fill="#FF0000"/>
|
||||||
<rect x="33.0244" y="31.5447" width="1.46341" height="25.0407" fill="white"/>
|
<rect x="27.8749" y="31.8649" width="3.75" height="2.17823" fill="white"/>
|
||||||
<rect x="33.0244" y="31.5447" width="1.46341" height="5.69106" fill="#FF0000"/>
|
<path d="M33.5 28.5111V8.61545V8.11176H26V28.5111H33.5Z" fill="black"/>
|
||||||
<rect x="33.0244" y="42.9268" width="1.46341" height="5.69106" fill="#FF0000"/>
|
<path d="M29.6364 5.00276C27.1289 5.09112 26.2239 7.044 26 8.11176H33.5C33.3134 6.97036 32.1438 4.91439 29.6364 5.00276Z" fill="black"/>
|
||||||
<rect x="33.0244" y="54.3089" width="1.46341" height="5.69106" fill="#FF0000"/>
|
<path d="M29.8636 31.6201C32.3711 31.5317 33.2761 29.5789 33.5 28.5111H26C26.1865 29.6525 27.3561 31.7085 29.8636 31.6201Z" fill="black"/>
|
||||||
<ellipse cx="27.9024" cy="7.40022" rx="1.46341" ry="1.40022" fill="#212121"/>
|
<ellipse cx="29.887" cy="11.8168" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||||
<ellipse cx="27.9024" cy="11.8343" rx="1.46341" ry="1.40022" fill="#212121"/>
|
<ellipse cx="29.887" cy="8.0135" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||||
<ellipse cx="27.9024" cy="16.2683" rx="1.46341" ry="1.40022" fill="#FF0000"/>
|
<ellipse cx="29.887" cy="15.6151" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||||
<ellipse cx="27.9024" cy="20.7023" rx="1.46341" ry="1.40022" fill="#212121"/>
|
<ellipse cx="29.887" cy="19.6834" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||||
<ellipse cx="27.9024" cy="25.1364" rx="1.46341" ry="1.40022" fill="#212121"/>
|
<ellipse cx="29.887" cy="23.7518" rx="1.38696" ry="1.28474" fill="#212121"/>
|
||||||
|
<ellipse cx="29.887" cy="27.8201" rx="1.38696" ry="1.28474" fill="#00FF0A"/>
|
||||||
|
<ellipse cx="29.887" cy="19.769" rx="1.38696" ry="1.28474" fill="#00FF0A"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
<svg width="144" height="147" viewBox="0 0 144 147" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g filter="url(#filter0_d_1343_19)">
|
||||||
|
<path d="M115.039 101.247C116.397 98.6665 115.405 95.4739 112.824 94.1167C110.243 92.7594 107.05 93.7514 105.693 96.3323L115.039 101.247ZM89.4447 44.0402L94.1179 46.4977L99.0329 37.1513L94.3597 34.6938L89.4447 44.0402ZM105.693 96.3323C95.7398 115.259 72.3278 122.534 53.4008 112.581L48.4858 121.927C72.5746 134.595 102.372 125.336 115.039 101.247L105.693 96.3323ZM53.4008 112.581C34.4739 102.627 27.1993 79.2155 37.1525 60.2885L27.8061 55.3735C15.1383 79.4623 24.397 109.259 48.4858 121.927L53.4008 112.581ZM37.1525 60.2885C47.1057 41.3616 70.5177 34.087 89.4447 44.0402L94.3597 34.6938C70.2709 22.026 40.4738 31.2846 27.8061 55.3735L37.1525 60.2885Z" fill="white"/>
|
||||||
|
<path d="M91.2258 38.7627L101.056 20.0698L116.15 51.8695L81.3956 57.4555L91.2258 38.7627Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d_1343_19" x="18.1328" y="20.0698" width="102.017" height="115.531" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dy="4"/>
|
||||||
|
<feGaussianBlur stdDeviation="2"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1343_19"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1343_19" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M23.75 3.75H22.5V1.25H20V3.75H10V1.25H7.5V3.75H6.25C4.875 3.75 3.75 4.875 3.75 6.25V23.75C3.75 25.125 4.875 26.25 6.25 26.25H23.75C25.125 26.25 26.25 25.125 26.25 23.75V6.25C26.25 4.875 25.125 3.75 23.75 3.75ZM23.75 23.75H6.25V11.25H23.75V23.75ZM6.25 8.75V6.25H23.75V8.75H6.25ZM8.75 13.75H21.25V16.25H8.75V13.75ZM8.75 18.75H17.5V21.25H8.75V18.75Z" fill="#F2E147"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 477 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M23.75 3.75H22.5V1.25H20V3.75H10V1.25H7.5V3.75H6.25C4.875 3.75 3.75 4.875 3.75 6.25V23.75C3.75 25.125 4.875 26.25 6.25 26.25H23.75C25.125 26.25 26.25 25.125 26.25 23.75V6.25C26.25 4.875 25.125 3.75 23.75 3.75ZM23.75 23.75H6.25V11.25H23.75V23.75ZM6.25 8.75V6.25H23.75V8.75H6.25ZM8.75 13.75H21.25V16.25H8.75V13.75ZM8.75 18.75H17.5V21.25H8.75V18.75Z" fill="#66FF6C"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 477 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M23.75 3.75H22.5V1.25H20V3.75H10V1.25H7.5V3.75H6.25C4.875 3.75 3.75 4.875 3.75 6.25V23.75C3.75 25.125 4.875 26.25 6.25 26.25H23.75C25.125 26.25 26.25 25.125 26.25 23.75V6.25C26.25 4.875 25.125 3.75 23.75 3.75ZM23.75 23.75H6.25V11.25H23.75V23.75ZM6.25 8.75V6.25H23.75V8.75H6.25ZM8.75 13.75H21.25V16.25H8.75V13.75ZM8.75 18.75H17.5V21.25H8.75V18.75Z" fill="#898989"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 477 B |
@@ -6,23 +6,15 @@
|
|||||||
<img :src="getIcon('pl')" alt="icon-pl" @click="changeLang('en')" v-if="currentLang == 'pl'" />
|
<img :src="getIcon('pl')" alt="icon-pl" @click="changeLang('en')" v-if="currentLang == 'pl'" />
|
||||||
<img :src="getIcon('en', 'jpg')" alt="icon-en" @click="changeLang('pl')" v-else />
|
<img :src="getIcon('en', 'jpg')" alt="icon-en" @click="changeLang('pl')" v-else />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="icons-bottom">
|
|
||||||
<a href="https://www.paypal.com/paypalme/spythere" target="_blank">
|
|
||||||
<img :src="getIcon('dollar')" alt="icon paypal" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="https://discord.gg/x2mpNN3svk" target="_blank">
|
|
||||||
<img :src="getIcon('discord', 'png')" alt="icon discord" />
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header_body">
|
<div class="header_body">
|
||||||
<StatusIndicator />
|
<StatusIndicator />
|
||||||
|
|
||||||
<span class="header_brand">
|
<span class="header_brand">
|
||||||
<img :src="getImage('stacjownik-header-logo.svg')" alt="Stacjownik" />
|
<router-link to="/">
|
||||||
|
<img :src="getImage('stacjownik-header-logo.svg')" alt="Stacjownik" />
|
||||||
|
</router-link>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="header_info">
|
<span class="header_info">
|
||||||
@@ -31,6 +23,12 @@
|
|||||||
<div class="info_counter">
|
<div class="info_counter">
|
||||||
<img :src="getIcon('dispatcher')" alt="icon dispatcher" />
|
<img :src="getIcon('dispatcher')" alt="icon dispatcher" />
|
||||||
<span class="text--primary">{{ onlineDispatchersCount }}</span>
|
<span class="text--primary">{{ onlineDispatchersCount }}</span>
|
||||||
|
|
||||||
|
<!-- <span class="g-tooltip">
|
||||||
|
<b class="text--primary">{{ factorU }}U</b>
|
||||||
|
<div class="content">Test</div>
|
||||||
|
</span> -->
|
||||||
|
|
||||||
<span class="text--grayed"> / </span>
|
<span class="text--grayed"> / </span>
|
||||||
<span class="text--primary">{{ onlineTrainsCount }}</span>
|
<span class="text--primary">{{ onlineTrainsCount }}</span>
|
||||||
<img :src="getIcon('train')" alt="icon train" />
|
<img :src="getIcon('train')" alt="icon train" />
|
||||||
@@ -48,7 +46,12 @@
|
|||||||
/
|
/
|
||||||
<router-link class="route" active-class="route-active" to="/trains">{{ $t('app.trains') }}</router-link>
|
<router-link class="route" active-class="route-active" to="/trains">{{ $t('app.trains') }}</router-link>
|
||||||
/
|
/
|
||||||
<router-link class="route" active-class="route-active" to="/journal/timetables">
|
<router-link
|
||||||
|
class="route"
|
||||||
|
active-class="route-active"
|
||||||
|
:data-active="$route.path.startsWith('/journal')"
|
||||||
|
to="/journal"
|
||||||
|
>
|
||||||
{{ $t('app.journal') }}
|
{{ $t('app.journal') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
</span>
|
||||||
@@ -66,50 +69,57 @@ import StatusIndicator from './StatusIndicator.vue';
|
|||||||
import Clock from './Clock.vue';
|
import Clock from './Clock.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
emits: ["changeLang"],
|
emits: ['changeLang'],
|
||||||
mixins: [imageMixin],
|
mixins: [imageMixin],
|
||||||
props: {
|
props: {
|
||||||
currentLang: {
|
currentLang: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
setup() {
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
store: useStore(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changeRegion(region: { id: string; value: string }) {
|
||||||
|
this.store.changeRegion(region);
|
||||||
|
},
|
||||||
|
changeLang(lang: string) {
|
||||||
|
this.$emit('changeLang', lang);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
onlineTrainsCount() {
|
||||||
|
return this.store.trainList.filter((train) => train.online).length;
|
||||||
|
},
|
||||||
|
|
||||||
|
onlineDispatchersCount() {
|
||||||
|
return this.store.stationList.filter(
|
||||||
|
(station) => station.onlineInfo && station.onlineInfo.region == this.store.region.id
|
||||||
|
).length;
|
||||||
|
},
|
||||||
|
|
||||||
|
factorU() {
|
||||||
|
return this.onlineDispatchersCount == 0 ? '-' : (this.onlineTrainsCount / this.onlineDispatchersCount).toFixed(2);
|
||||||
|
},
|
||||||
|
|
||||||
|
computedRegions() {
|
||||||
|
return options.regions.map((region) => {
|
||||||
|
const regionStationCount =
|
||||||
|
this.store.apiData.stations?.filter((station) => station.region == region.id && station.isOnline).length || 0;
|
||||||
|
const regionTrainCount =
|
||||||
|
this.store.apiData.trains?.filter((train) => train.region == region.id && train.online).length || 0;
|
||||||
return {
|
return {
|
||||||
store: useStore(),
|
id: region.id,
|
||||||
|
value: `${region.value} <div class='text--grayed'>${regionStationCount} / ${regionTrainCount}</div>`,
|
||||||
|
selectedValue: region.value,
|
||||||
};
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
},
|
||||||
changeRegion(region: {
|
components: { SelectBox, StatusIndicator, Clock },
|
||||||
id: string;
|
|
||||||
value: string;
|
|
||||||
}) {
|
|
||||||
this.store.changeRegion(region);
|
|
||||||
},
|
|
||||||
changeLang(lang: string) {
|
|
||||||
this.$emit("changeLang", lang);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
onlineTrainsCount() {
|
|
||||||
return this.store.trainList.filter((train) => train.online).length;
|
|
||||||
},
|
|
||||||
onlineDispatchersCount() {
|
|
||||||
return this.store.stationList.filter((station) => station.onlineInfo && station.onlineInfo.region == this.store.region.id).length;
|
|
||||||
},
|
|
||||||
computedRegions() {
|
|
||||||
return options.regions.map((region) => {
|
|
||||||
const regionStationCount = this.store.apiData.stations?.filter((station) => station.region == region.id && station.isOnline).length || 0;
|
|
||||||
const regionTrainCount = this.store.apiData.trains?.filter((train) => train.region == region.id && train.online).length || 0;
|
|
||||||
return {
|
|
||||||
id: region.id,
|
|
||||||
value: `${region.value} <div class='text--grayed'>${regionStationCount} / ${regionTrainCount}</div>`,
|
|
||||||
selectedValue: region.value,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: { SelectBox, StatusIndicator, Clock }
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -127,21 +137,20 @@ export default defineComponent({
|
|||||||
|
|
||||||
.header {
|
.header {
|
||||||
&_body {
|
&_body {
|
||||||
max-width: 21em;
|
position: relative;
|
||||||
|
max-width: 20em;
|
||||||
@include smallScreen {
|
|
||||||
max-width: 18em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&_container {
|
&_container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
|
||||||
|
|
||||||
width: 1350px;
|
|
||||||
padding: 0.5em 0.3em 0 0.3em;
|
|
||||||
border-radius: 0 0 1em 1em;
|
border-radius: 0 0 1em 1em;
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_brand {
|
&_brand {
|
||||||
@@ -149,6 +158,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,9 +166,7 @@ export default defineComponent({
|
|||||||
&_info {
|
&_info {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
max-width: 100%;
|
font-size: 1.15em;
|
||||||
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&_links {
|
&_links {
|
||||||
@@ -175,57 +183,20 @@ export default defineComponent({
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
display: flex;
|
padding: 0.5em;
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: flex-end;
|
|
||||||
padding: 0.5em 0.5em;
|
|
||||||
|
|
||||||
@include smallScreen() {
|
@include smallScreen {
|
||||||
right: auto;
|
transform: translateX(85%);
|
||||||
left: 0.75em;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ICONS
|
// ICONS
|
||||||
.icons {
|
.icons-top {
|
||||||
position: relative;
|
img {
|
||||||
|
width: 2.5em;
|
||||||
&-top {
|
cursor: pointer;
|
||||||
img {
|
|
||||||
width: 2.5em;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-bottom {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
a {
|
|
||||||
margin-left: 0.6em;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 1.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
a {
|
|
||||||
margin: 0.25em 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="loading">{{message}}</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from "vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: ["message"],
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.loading {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
min-height: 100%;
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
|
|
||||||
font-size: calc(0.75rem + 1vw);
|
|
||||||
|
|
||||||
color: #fdc62f;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -161,17 +161,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { DataStatus } from '../../scripts/enums/DataStatus';
|
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
import { useStore } from '../../store/store';
|
import { useStore } from '../../store/store';
|
||||||
import { StoreState } from '../../store/storeTypes';
|
import { StoreState } from '../../scripts/interfaces/store/storeTypes';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tooltipActive: false,
|
tooltipActive: false,
|
||||||
indicator: {
|
indicator: {
|
||||||
|
offline: false,
|
||||||
status: DataStatus.Loading,
|
status: DataStatus.Loading,
|
||||||
message: 'data-status.S3',
|
message: 'data-status.S3',
|
||||||
},
|
},
|
||||||
@@ -193,6 +193,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
dataStatus: store.dataStatuses,
|
dataStatus: store.dataStatuses,
|
||||||
|
store,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -206,6 +207,13 @@ export default defineComponent({
|
|||||||
const trainsDataStatus = statuses.trains;
|
const trainsDataStatus = statuses.trains;
|
||||||
const dispatcherDataStatus = statuses.dispatchers;
|
const dispatcherDataStatus = statuses.dispatchers;
|
||||||
|
|
||||||
|
if (this.store.isOffline) {
|
||||||
|
this.setSignalStatus(DataStatus.Initialized);
|
||||||
|
this.indicator.status = DataStatus.Initialized;
|
||||||
|
this.indicator.message = 'data-status.S1-offline';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (connectionStatus == DataStatus.Error) {
|
if (connectionStatus == DataStatus.Error) {
|
||||||
this.setSignalStatus(connectionStatus);
|
this.setSignalStatus(connectionStatus);
|
||||||
this.indicator.status = connectionStatus;
|
this.indicator.status = connectionStatus;
|
||||||
@@ -252,6 +260,10 @@ export default defineComponent({
|
|||||||
this.orangeLight = false;
|
this.orangeLight = false;
|
||||||
this.redBottomLight = false;
|
this.redBottomLight = false;
|
||||||
|
|
||||||
|
if (status == DataStatus.Initialized) {
|
||||||
|
this.redTopLight = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (status == DataStatus.Loaded) {
|
if (status == DataStatus.Loaded) {
|
||||||
this.greenLight = true;
|
this.greenLight = true;
|
||||||
}
|
}
|
||||||
@@ -291,10 +303,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
.status-indicator {
|
.status-indicator {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
transform: translateX(12em);
|
right: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
|
transform: translateX(1.5em);
|
||||||
}
|
}
|
||||||
|
|
||||||
.indicator {
|
.indicator {
|
||||||
@@ -319,7 +332,7 @@ export default defineComponent({
|
|||||||
background-color: #171717;
|
background-color: #171717;
|
||||||
border-radius: 0.75em;
|
border-radius: 0.75em;
|
||||||
|
|
||||||
min-width: 13em;
|
width: 13em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
overflow: none;
|
overflow: none;
|
||||||
|
|
||||||
@@ -343,22 +356,16 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
@include midScreen() {
|
@include midScreen() {
|
||||||
left: 50%;
|
left: auto;
|
||||||
top: 100%;
|
right: 200%;
|
||||||
|
|
||||||
transform: translate(-50%, 0);
|
|
||||||
margin-left: 0;
|
|
||||||
margin-top: 0.75em;
|
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
border-left: 10px solid transparent;
|
|
||||||
border-right: 10px solid transparent;
|
border-right: 10px solid transparent;
|
||||||
border-bottom: 10px solid #171717;
|
border-left: 12px solid #171717;
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
|
||||||
top: 0;
|
transform: translate(100%, -50%);
|
||||||
left: 50%;
|
|
||||||
|
|
||||||
transform: translate(-50%, -100%);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,3 +375,4 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div class="update-prompt">
|
||||||
|
<transition name="prompt-anim">
|
||||||
|
<div class="prompt_content" v-if="!hidePrompt && needRefresh">
|
||||||
|
<div>{{ $t('update.title') }}</div>
|
||||||
|
|
||||||
|
<div class="prompt_actions">
|
||||||
|
<button class="btn btn--filled" @click="updateServiceWorker(true)">{{ $t('update.confirm-button') }}</button>
|
||||||
|
<button class="btn btn--filled" @click="hidePrompt = true">{{ $t('update.later-button') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import useCustomSW from '../../mixins/useCustomSW';
|
||||||
|
|
||||||
|
const hidePrompt = ref(false);
|
||||||
|
const { needRefresh, updateServiceWorker } = useCustomSW();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
|
.update-prompt {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt_content {
|
||||||
|
margin: 1em;
|
||||||
|
padding: 1em;
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: black;
|
||||||
|
|
||||||
|
box-shadow: 0 0 10px 1px $accentCol;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt_actions {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 1em;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation
|
||||||
|
.prompt-anim {
|
||||||
|
&-enter-active,
|
||||||
|
&-leave-active {
|
||||||
|
transition: all 120ms ease-in;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export default defineComponent({
|
|||||||
.loading {
|
.loading {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
<div class="select-box_content">
|
<div class="select-box_content">
|
||||||
<button class="selected" @click="toggleBox">
|
<button class="selected" @click="toggleBox">
|
||||||
<span>{{ computedSelectedItem.selectedValue || computedSelectedItem.value }}</span>
|
<span>{{ computedSelectedItem.selectedValue || computedSelectedItem.value }}</span>
|
||||||
|
|
||||||
|
<div class="arrow">
|
||||||
|
<img :src="listOpen ? getIcon('arrow-asc') : getIcon('arrow-desc')" alt="arrow-icon" />
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul class="options" :ref="(el) => (listRef = el as Element)">
|
<ul class="options" :ref="(el) => (listRef = el as Element)">
|
||||||
@@ -21,10 +25,6 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="arrow">
|
|
||||||
<img :src="listOpen ? getIcon('arrow-asc') : getIcon('arrow-desc')" alt="arrow-icon" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -129,46 +129,22 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.select-box {
|
.select-box {
|
||||||
position: relative;
|
display: flex;
|
||||||
width: auto;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
.arrow {
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: 0;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 1.35em;
|
width: 1.35em;
|
||||||
}
|
}
|
||||||
|
|
||||||
transform: translateY(-50%);
|
|
||||||
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button.selected {
|
button.selected {
|
||||||
background-color: transparent;
|
|
||||||
color: paleturquoise;
|
color: paleturquoise;
|
||||||
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
padding: 0.1em 0.5em;
|
padding: 0.1em 0.5em;
|
||||||
margin-right: 2em;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: #262626;
|
background-color: #262626;
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
<template>
|
||||||
|
<section class="daily-stats">
|
||||||
|
<span :data-active="data.statsStatus">
|
||||||
|
<b v-if="data.statsStatus == DataStatus.Loading">
|
||||||
|
{{ $t('app.loading') }}
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<b v-else-if="data.stats.distanceSum == null">
|
||||||
|
{{ $t('journal.daily-stats-info') }}
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<div v-if="data.stats.totalTimetables">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.timetable-stats-total">
|
||||||
|
<template #count>
|
||||||
|
<b class="text--primary">
|
||||||
|
{{ data.stats.totalTimetables }}
|
||||||
|
{{ $t('journal.timetable-count', data.stats.totalTimetables) }}
|
||||||
|
</b>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #distance>
|
||||||
|
<b class="text--primary"> {{ data.stats.distanceSum?.toFixed(2) }} km </b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="data.stats.timetableId">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.timetable-stats-longest">
|
||||||
|
<template #id>
|
||||||
|
<router-link :to="`/journal/timetables?timetableId=${data.stats.timetableId}`">
|
||||||
|
<b>{{ data.stats.timetableId }}</b>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
<template #author>
|
||||||
|
<router-link :to="`/journal/dispatchers?dispatcherName=${data.stats.timetableAuthor}`">
|
||||||
|
<b>{{ data.stats.timetableAuthor }}</b>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
<template #driver>
|
||||||
|
<b>{{ data.stats.timetableDriver }}</b>
|
||||||
|
</template>
|
||||||
|
<template #distance>
|
||||||
|
<b class="text--primary">{{ data.stats.timetableRouteDistance }} km</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="firstPlaceDispatchers.length == 1">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.timetable-stats-most-active">
|
||||||
|
<template #dispatcher>
|
||||||
|
<router-link :to="`/journal/dispatchers?dispatcherName=${firstPlaceDispatchers[0].name}`">
|
||||||
|
<b>{{ firstPlaceDispatchers[0].name }}</b>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
<template #count>
|
||||||
|
<b class="text--primary">
|
||||||
|
{{ firstPlaceDispatchers[0].count }}
|
||||||
|
{{ $t('journal.timetable-count', firstPlaceDispatchers[0].count) }}
|
||||||
|
</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="firstPlaceDispatchers.length > 1">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.timetable-stats-most-active-many">
|
||||||
|
<template #dispatchers>
|
||||||
|
<span v-for="(disp, i) in firstPlaceDispatchers">
|
||||||
|
<span v-if="i == firstPlaceDispatchers.length - 1"> {{ $t('general.and') }} </span>
|
||||||
|
|
||||||
|
<router-link :to="`/journal/dispatchers?dispatcherName=${disp.name}`">
|
||||||
|
<b>{{ disp.name }}</b>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<span v-if="i < firstPlaceDispatchers.length - 2">, </span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #count>
|
||||||
|
<b class="text--primary">
|
||||||
|
{{ firstPlaceDispatchers[0].count }}
|
||||||
|
{{ $t('journal.timetable-count', firstPlaceDispatchers[0].count) }}
|
||||||
|
</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import axios from 'axios';
|
||||||
|
import { computed, onActivated, onDeactivated, onMounted, reactive, ref } from 'vue';
|
||||||
|
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
|
import { ITimetablesDailyStats, ITimetablesDailyStatsResponse } from '../../scripts/interfaces/api/StatsAPIData';
|
||||||
|
import { URLs } from '../../scripts/utils/apiURLs';
|
||||||
|
import StorageManager from '../../scripts/managers/storageManager';
|
||||||
|
|
||||||
|
const intervalId = ref(-1);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'toggleStatsOpen', value: boolean): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const data = reactive({
|
||||||
|
statsStatus: DataStatus.Loading,
|
||||||
|
|
||||||
|
stats: {
|
||||||
|
totalTimetables: 0,
|
||||||
|
distanceSum: 0,
|
||||||
|
distanceAvg: 0,
|
||||||
|
timetableAuthor: '',
|
||||||
|
timetableDriver: '',
|
||||||
|
timetableId: 0,
|
||||||
|
timetableRouteDistance: 0,
|
||||||
|
|
||||||
|
mostActiveDispatchers: [],
|
||||||
|
} as ITimetablesDailyStats,
|
||||||
|
});
|
||||||
|
|
||||||
|
const firstPlaceDispatchers = computed(() => {
|
||||||
|
if (data.stats.mostActiveDispatchers.length == 0) return [];
|
||||||
|
const maxCount = data.stats.mostActiveDispatchers[0].count;
|
||||||
|
|
||||||
|
return data.stats.mostActiveDispatchers.filter((disp) => disp.count === maxCount);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function fetchDailyTimetableStats() {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
distanceAvg,
|
||||||
|
distanceSum,
|
||||||
|
maxTimetable,
|
||||||
|
totalTimetables,
|
||||||
|
mostActiveDispatchers,
|
||||||
|
}: ITimetablesDailyStatsResponse = await (
|
||||||
|
await axios.get(`${URLs.stacjownikAPI}/api/getDailyTimetableStats`)
|
||||||
|
).data;
|
||||||
|
|
||||||
|
data.stats = {
|
||||||
|
totalTimetables,
|
||||||
|
distanceSum,
|
||||||
|
distanceAvg,
|
||||||
|
timetableAuthor: maxTimetable?.authorName || '',
|
||||||
|
timetableDriver: maxTimetable?.driverName || '',
|
||||||
|
timetableId: maxTimetable?.id || 0,
|
||||||
|
timetableRouteDistance: maxTimetable?.routeDistance || 0,
|
||||||
|
|
||||||
|
mostActiveDispatchers,
|
||||||
|
};
|
||||||
|
|
||||||
|
data.statsStatus = DataStatus.Loaded;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ups! Wystąpił błąd podczas pobierania statystyk rozkładów jazdy...');
|
||||||
|
data.statsStatus = DataStatus.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startFetchingDailyStats() {
|
||||||
|
fetchDailyTimetableStats();
|
||||||
|
|
||||||
|
if (intervalId.value != -1) return;
|
||||||
|
|
||||||
|
intervalId.value = setInterval(fetchDailyTimetableStats, 60000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopFetchingDailyStats() {
|
||||||
|
clearInterval(intervalId.value);
|
||||||
|
intervalId.value = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
startFetchingDailyStats();
|
||||||
|
emit('toggleStatsOpen', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
onDeactivated(() => {
|
||||||
|
stopFetchingDailyStats();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.daily-stats {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.daily-stats > span[data-active='0'] {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="journal-stats" v-if="store.driverStatsData?._sum.routeDistance != null">
|
|
||||||
<h1>
|
|
||||||
{{ $t('journal.stats-title') }} <span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="info-stats">
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>{{ $t('journal.stats-timetables') }}</span>
|
|
||||||
<span>{{ store.driverStatsData._count.fulfilled }} / {{ store.driverStatsData._count._all }}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>{{ $t('journal.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> {{ store.driverStatsData._avg.routeDistance.toFixed(2) }}km </span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>{{ $t('journal.stats-distance') }}</span>
|
|
||||||
<span>
|
|
||||||
{{ store.driverStatsData._sum.currentDistance.toFixed(2) }} /
|
|
||||||
{{ store.driverStatsData._sum.routeDistance.toFixed(2) }}km
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>{{ $t('journal.stats-stations') }}</span>
|
|
||||||
<span>
|
|
||||||
{{ store.driverStatsData._sum.confirmedStopsCount }} /
|
|
||||||
{{ store.driverStatsData._sum.allStopsCount }}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import axios from 'axios';
|
|
||||||
import { computed, defineComponent, ref } from 'vue';
|
|
||||||
import { DriverStatsAPIData } from '../../scripts/interfaces/api/DriverStatsAPIData';
|
|
||||||
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
|
|
||||||
import { URLs } from '../../scripts/utils/apiURLs';
|
|
||||||
import { useStore } from '../../store/store';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
emits: ['closeCard'],
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const store = useStore();
|
|
||||||
return {
|
|
||||||
store,
|
|
||||||
driverStatsName: computed(() => store.driverStatsName),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
test: Math.random(),
|
|
||||||
lastDispatcherName: '',
|
|
||||||
|
|
||||||
lastTimetables: [] as TimetableHistory[],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
driverStatsName(value: string) {
|
|
||||||
this.fetchDispatcherStats();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
async fetchDispatcherStats() {
|
|
||||||
this.store.driverStatsData = undefined;
|
|
||||||
|
|
||||||
if (!this.store.driverStatsName) return;
|
|
||||||
|
|
||||||
const statsData: DriverStatsAPIData = await (
|
|
||||||
await axios.get(`${URLs.stacjownikAPI}/api/getDriverInfo?name=${this.store.driverStatsName}`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
this.store.driverStatsData = statsData;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/JournalStats.scss';
|
|
||||||
</style>
|
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<ul class="journal-list">
|
<transition-group class="journal-list" tag="ul" name="list-anim">
|
||||||
<!-- <transition-group name="journal-list-anim"> -->
|
<li
|
||||||
<li v-for="item in computedDispatcherHistory" :class="{ sticky: typeof item == 'string' }">
|
v-for="item in computedDispatcherHistory"
|
||||||
|
:key="typeof item === 'string' ? item : item.timestampFrom + item.dispatcherId"
|
||||||
|
:class="{ sticky: typeof item == 'string' }"
|
||||||
|
>
|
||||||
<div v-if="typeof item == 'string'" class="journal_day">
|
<div v-if="typeof item == 'string'" class="journal_day">
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</div>
|
</div>
|
||||||
@@ -14,13 +17,25 @@
|
|||||||
@keydown.enter="navigateToScenery(item.stationName, item.isOnline)"
|
@keydown.enter="navigateToScenery(item.stationName, item.isOnline)"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
>
|
>
|
||||||
<span>
|
<span class="item-general">
|
||||||
|
<b
|
||||||
|
v-if="item.dispatcherLevel !== null"
|
||||||
|
class="level-badge dispatcher"
|
||||||
|
:style="calculateExpStyle(item.dispatcherLevel, item.dispatcherIsSupporter)"
|
||||||
|
>
|
||||||
|
{{ item.dispatcherLevel >= 2 ? item.dispatcherLevel : 'L' }}
|
||||||
|
</b>
|
||||||
|
|
||||||
<b class="text--primary">{{ item.dispatcherName }}</b> • <b>{{ item.stationName }}</b>
|
<b class="text--primary">{{ item.dispatcherName }}</b> • <b>{{ item.stationName }}</b>
|
||||||
<span class="text--grayed"> #{{ item.stationHash }} </span>
|
<span class="text--grayed"> #{{ item.stationHash }} </span>
|
||||||
<span class="region-badge" :class="item.region">PL1</span>
|
<span class="region-badge" :class="item.region">PL1</span>
|
||||||
|
<span class="like-count" v-if="item.dispatcherRate">
|
||||||
|
<img :src="getIcon('like')" alt="like icon" />
|
||||||
|
{{ item.dispatcherRate }}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span class="item-time">
|
||||||
<span :data-status="item.isOnline"> {{ item.isOnline ? $t('journal.online-since') : 'OFFLINE' }} </span>
|
<span :data-status="item.isOnline"> {{ item.isOnline ? $t('journal.online-since') : 'OFFLINE' }} </span>
|
||||||
<span>
|
<span>
|
||||||
{{ new Date(item.timestampFrom).toLocaleTimeString('pl-PL', { timeStyle: 'short' }) }}
|
{{ new Date(item.timestampFrom).toLocaleTimeString('pl-PL', { timeStyle: 'short' }) }}
|
||||||
@@ -36,14 +51,15 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<!-- </transition-group> -->
|
</transition-group>
|
||||||
</ul>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
|
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
|
||||||
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -53,7 +69,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [dateMixin],
|
mixins: [dateMixin, styleMixin, imageMixin],
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
computedDispatcherHistory() {
|
computedDispatcherHistory() {
|
||||||
@@ -86,18 +102,11 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/animations.scss';
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
|
@import '../../styles/badge.scss';
|
||||||
@import '../../styles/JournalSection.scss';
|
@import '../../styles/JournalSection.scss';
|
||||||
|
@import '../../styles/variables.scss';
|
||||||
.region-badge {
|
|
||||||
padding: 0.1em 0.5em;
|
|
||||||
border-radius: 0.5em;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
&.eu {
|
|
||||||
background-color: forestgreen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
li.sticky {
|
li.sticky {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
@@ -108,9 +117,12 @@ li.sticky {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
gap: 0.5em 1em;
|
||||||
|
|
||||||
|
line-height: 1.7em;
|
||||||
padding: 0.75em;
|
padding: 0.75em;
|
||||||
|
|
||||||
&.online {
|
&.online {
|
||||||
@@ -126,6 +138,18 @@ li.sticky {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-general {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.level-badge {
|
||||||
|
margin-right: 0.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.journal_day {
|
.journal_day {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
@@ -143,14 +167,17 @@ li.sticky {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include smallScreen() {
|
.like-count {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: $accentCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
.journal_item {
|
.journal_item {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
span {
|
|
||||||
margin-top: 0.25em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<div class="journal-stats">
|
||||||
|
<span v-if="store.driverStatsData">
|
||||||
|
<h3>
|
||||||
|
{{ $t('journal.stats-title') }} <span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="info-stats">
|
||||||
|
<span class="stat-badge">
|
||||||
|
<span>{{ $t('journal.stats-timetables') }}</span>
|
||||||
|
<span>{{ store.driverStatsData._count.fulfilled }} / {{ store.driverStatsData._count._all }}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="stat-badge">
|
||||||
|
<span>{{ $t('journal.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> {{ store.driverStatsData._avg.routeDistance.toFixed(2) }}km </span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="stat-badge">
|
||||||
|
<span>{{ $t('journal.stats-distance') }}</span>
|
||||||
|
<span>
|
||||||
|
{{ store.driverStatsData._sum.currentDistance.toFixed(2) }} /
|
||||||
|
{{ store.driverStatsData._sum.routeDistance.toFixed(2) }}km
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="stat-badge">
|
||||||
|
<span>{{ $t('journal.stats-stations') }}</span>
|
||||||
|
<span>
|
||||||
|
{{ store.driverStatsData._sum.confirmedStopsCount }} /
|
||||||
|
{{ store.driverStatsData._sum.allStopsCount }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<b v-else-if="store.driverStatsStatus == DataStatus.Loading">{{ $t('journal.stats-loading') }}</b>
|
||||||
|
<b v-else-if="store.driverStatsStatus == DataStatus.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 { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
store: useStore(),
|
||||||
|
DataStatus,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/JournalStats.scss';
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<section class="journal-header">
|
||||||
|
<div class="journal-type-options">
|
||||||
|
<router-link class="router-link" active-class="route-active" to="/journal/timetables" exact>
|
||||||
|
{{ $t('journal.section-timetables') }}
|
||||||
|
</router-link>
|
||||||
|
•
|
||||||
|
<router-link class="router-link" active-class="route-active" to="/journal/dispatchers">
|
||||||
|
{{ $t('journal.section-dispatchers') }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.journal-type-options {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
background-color: #2c2c2c;
|
||||||
|
max-width: 18em;
|
||||||
|
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
border-radius: 0 0 0.5em 0.5em;
|
||||||
|
padding: 0.1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.journal-section > section {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.router-link.active {
|
||||||
|
color: gold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,10 +2,26 @@
|
|||||||
<div class="filters-options" @keydown.esc="showOptions = false">
|
<div class="filters-options" @keydown.esc="showOptions = false">
|
||||||
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
||||||
|
|
||||||
<button class="btn--filled btn--image" @click="showOptions = !showOptions" ref="button">
|
<div class="actions-bar">
|
||||||
<img :src="getIcon('filter2')" alt="Open filters" />
|
<button class="filter-button btn--filled btn--image" @click="showOptions = !showOptions" ref="button">
|
||||||
{{ $t('options.filters') }} [F]
|
<img :src="getIcon('filter2')" alt="Open filters" />
|
||||||
</button>
|
{{ $t('options.filters') }} [F]
|
||||||
|
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="filter-button btn--filled btn--image" @click="refreshData">
|
||||||
|
<img :src="getIcon('refresh')" alt="Refresh data" />
|
||||||
|
{{ $t('general.refresh') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<datalist id="search-driver">
|
||||||
|
<option v-for="sugg in driverSuggestions" :value="sugg"></option>
|
||||||
|
</datalist>
|
||||||
|
|
||||||
|
<datalist id="search-dispatcher">
|
||||||
|
<option v-for="sugg in dispatcherSuggestions" :value="sugg"></option>
|
||||||
|
</datalist>
|
||||||
|
|
||||||
<transition name="options-anim">
|
<transition name="options-anim">
|
||||||
<div class="options_wrapper" v-if="showOptions">
|
<div class="options_wrapper" v-if="showOptions">
|
||||||
@@ -13,43 +29,26 @@
|
|||||||
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
||||||
<div class="search_content">
|
<div class="search_content">
|
||||||
<div class="search" v-for="(_, propName) in searchersValues" :key="propName">
|
<div class="search" v-for="(_, propName) in searchersValues" :key="propName">
|
||||||
<label v-if="propName == 'search-date'" for="date">{{ $t('options.search-date') }}</label>
|
<label v-if="propName == 'search-date'" for="date">{{ $t(`options.search-${optionsType}-date`) }}</label>
|
||||||
|
|
||||||
<div class="search-box">
|
<div class="search-box">
|
||||||
<input
|
<input
|
||||||
v-if="propName == 'search-date'"
|
|
||||||
class="search-input"
|
class="search-input"
|
||||||
id="date"
|
|
||||||
type="date"
|
|
||||||
min="2022-02-01"
|
|
||||||
@keydown.enter="onSearchConfirm"
|
|
||||||
v-model="searchersValues[propName]"
|
v-model="searchersValues[propName]"
|
||||||
/>
|
|
||||||
|
|
||||||
<input
|
|
||||||
v-else
|
|
||||||
class="search-input"
|
|
||||||
@keydown.enter="onSearchConfirm"
|
@keydown.enter="onSearchConfirm"
|
||||||
@focus="preventKeyDown = true"
|
@focus="preventKeyDown = true"
|
||||||
@blur="preventKeyDown = false"
|
@blur="preventKeyDown = false"
|
||||||
:placeholder="$t(`options.${propName}`)"
|
:placeholder="$t(`options.${propName}`)"
|
||||||
v-model="searchersValues[propName]"
|
:type="propName == 'search-date' ? 'date' : 'text'"
|
||||||
|
:min="propName == 'search-date' ? '2022-02-01' : undefined"
|
||||||
|
:list="propName.toString()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button class="search-exit">
|
<button class="search-exit" v-if="propName != 'search-date'">
|
||||||
<img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear(propName)" />
|
<img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear(propName)" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="search_actions">
|
|
||||||
<button class="btn--action" @click="onResetButtonClick">
|
|
||||||
{{ $t('options.reset-button') }}
|
|
||||||
</button>
|
|
||||||
<button class="btn--action" @click="onSearchButtonConfirm">
|
|
||||||
{{ $t('options.search-button') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 class="option-title">{{ $t('options.sort-title') }}</h1>
|
<h1 class="option-title">{{ $t('options.sort-title') }}</h1>
|
||||||
@@ -66,15 +65,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 class="option-title" v-if="filters.length != 0">{{ $t('options.filter-title') }}</h1>
|
<h1 class="option-title" v-if="filters.length != 0">{{ $t('options.filter-title') }}</h1>
|
||||||
<div class="options_filters">
|
|
||||||
<button
|
<div class="options_filter-sections" v-if="filters.length != 0 && filterList">
|
||||||
v-for="filter in filters"
|
<section class="filter-section" v-for="section in JournalFilterSection">
|
||||||
class="filter-option btn--option"
|
<p>{{ $t(`options.filter-section-${section}`) }}</p>
|
||||||
:class="{ checked: journalFilterActive.id === filter.id }"
|
|
||||||
:id="filter.id"
|
<div class="options_filters">
|
||||||
@click="onFilterChange(filter)"
|
<button
|
||||||
>
|
v-for="filter in filterList.filter((f) => f.filterSection == section)"
|
||||||
{{ $t(`options.filter-${filter.id}`) }}
|
class="filter-option btn--option"
|
||||||
|
:class="{ checked: filter.isActive }"
|
||||||
|
:id="filter.id"
|
||||||
|
@click="onFilterChange(filter)"
|
||||||
|
>
|
||||||
|
{{ $t(`options.filter-${filter.id}`) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options_actions">
|
||||||
|
<button class="btn--action" @click="onResetButtonClick">
|
||||||
|
{{ $t('options.reset-button') }}
|
||||||
|
</button>
|
||||||
|
<button class="btn--action" @click="onSearchButtonConfirm">
|
||||||
|
{{ $t('options.search-button') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,17 +99,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, inject, Prop, PropType } from 'vue';
|
import axios from 'axios';
|
||||||
|
import { defineComponent, inject, PropType } from 'vue';
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
import keyMixin from '../../mixins/keyMixin';
|
import keyMixin from '../../mixins/keyMixin';
|
||||||
import { DataStatus } from '../../scripts/enums/DataStatus';
|
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
import { JournalTimetableFilter } from '../../types/Journal/JournalTimetablesTypes';
|
import { DriverStatsAPIData } from '../../scripts/interfaces/api/DriverStatsAPIData';
|
||||||
|
import { URLs } from '../../scripts/utils/apiURLs';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
import ActionButton from '../Global/ActionButton.vue';
|
import ActionButton from '../Global/ActionButton.vue';
|
||||||
import SelectBox from '../Global/SelectBox.vue';
|
import SelectBox from '../Global/SelectBox.vue';
|
||||||
|
import { JournalFilterSection } from '../../scripts/enums/JournalFilterType';
|
||||||
|
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { SelectBox, ActionButton },
|
components: { SelectBox, ActionButton },
|
||||||
emits: ['onSearchConfirm', 'onOptionsReset'],
|
emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'],
|
||||||
mixins: [imageMixin, keyMixin],
|
mixins: [imageMixin, keyMixin],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@@ -104,7 +124,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
filters: {
|
filters: {
|
||||||
type: Array as PropType<JournalTimetableFilter[]>,
|
type: Array as PropType<JournalFilter[]>,
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -112,11 +132,29 @@ export default defineComponent({
|
|||||||
type: Number as PropType<DataStatus>,
|
type: Number as PropType<DataStatus>,
|
||||||
default: DataStatus.Initialized,
|
default: DataStatus.Initialized,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
currentOptionsActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
optionsType: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showOptions: false,
|
showOptions: false,
|
||||||
|
JournalFilterSection,
|
||||||
|
|
||||||
|
driverSuggestions: [] as string[],
|
||||||
|
dispatcherSuggestions: [] as string[],
|
||||||
|
|
||||||
|
searchTimeout: 0,
|
||||||
|
store: useStore(),
|
||||||
|
|
||||||
DataStatus,
|
DataStatus,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -125,11 +163,16 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
searchersValues: inject('searchersValues') as { [key: string]: string },
|
searchersValues: inject('searchersValues') as { [key: string]: string },
|
||||||
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
||||||
journalFilterActive: inject('journalFilterActive') as JournalTimetableFilter,
|
// journalFilterActive: inject('journalFilterActive') as JournalFilter,
|
||||||
|
filterList: inject('filterList') as JournalFilter[] | undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
driverStatsName() {
|
||||||
|
return this.store.driverStatsName;
|
||||||
|
},
|
||||||
|
|
||||||
translatedSorterOptions() {
|
translatedSorterOptions() {
|
||||||
return this.$props.sorterOptionIds.map((id) => ({
|
return this.$props.sorterOptionIds.map((id) => ({
|
||||||
id,
|
id,
|
||||||
@@ -138,7 +181,76 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
async driverStatsName(value: string) {
|
||||||
|
await this.fetchDriverStats();
|
||||||
|
|
||||||
|
// if (value) this.store.currentStatsTab = 'driver';
|
||||||
|
},
|
||||||
|
|
||||||
|
async 'searchersValues.search-driver'(value: string | undefined) {
|
||||||
|
clearTimeout(this.searchTimeout);
|
||||||
|
|
||||||
|
if (!value || value == '') return;
|
||||||
|
if (value.length < 3) return;
|
||||||
|
|
||||||
|
this.startSearchTimeout('driver', value);
|
||||||
|
},
|
||||||
|
|
||||||
|
async 'searchersValues.search-dispatcher'(value: string | undefined) {
|
||||||
|
if (!value || value == '') return;
|
||||||
|
if (value.length < 3) return;
|
||||||
|
|
||||||
|
this.startSearchTimeout('dispatcher', value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
async fetchDriverStats() {
|
||||||
|
this.store.driverStatsData = undefined;
|
||||||
|
|
||||||
|
if (!this.store.driverStatsName) {
|
||||||
|
this.store.driverStatsStatus = DataStatus.Initialized;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.store.driverStatsStatus = DataStatus.Loading;
|
||||||
|
|
||||||
|
const statsData: DriverStatsAPIData = await (
|
||||||
|
await axios.get(`${URLs.stacjownikAPI}/api/getDriverInfo?name=${this.store.driverStatsName}`)
|
||||||
|
).data;
|
||||||
|
|
||||||
|
this.store.driverStatsData = statsData;
|
||||||
|
this.store.driverStatsStatus = DataStatus.Loaded;
|
||||||
|
} catch (error) {
|
||||||
|
this.store.driverStatsStatus = DataStatus.Error;
|
||||||
|
console.error('Ups! Wystąpił błąd przy próbie pobrania statystyk maszynisty! :/');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshData() {
|
||||||
|
this.$emit('onRefreshData');
|
||||||
|
},
|
||||||
|
|
||||||
|
startSearchTimeout(type: 'driver' | 'dispatcher', value: string) {
|
||||||
|
if (this[`${type}Suggestions`].includes(value)) return;
|
||||||
|
|
||||||
|
window.clearTimeout(this.searchTimeout);
|
||||||
|
|
||||||
|
this.searchTimeout = setTimeout(async () => {
|
||||||
|
try {
|
||||||
|
const suggestions: string[] = await (
|
||||||
|
await axios.get(`${URLs.stacjownikAPI}/api/get${type}Suggestions?name=${value}`)
|
||||||
|
).data;
|
||||||
|
|
||||||
|
this[`${type}Suggestions`] = suggestions;
|
||||||
|
} catch (error) {
|
||||||
|
this[`${type}Suggestions`] = [];
|
||||||
|
}
|
||||||
|
}, 450);
|
||||||
|
},
|
||||||
|
|
||||||
// Override keyMixin function
|
// Override keyMixin function
|
||||||
onKeyDownFunction() {
|
onKeyDownFunction() {
|
||||||
this.showOptions = !this.showOptions;
|
this.showOptions = !this.showOptions;
|
||||||
@@ -148,18 +260,17 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
focusEnd() {
|
|
||||||
console.log('focus end');
|
|
||||||
},
|
|
||||||
|
|
||||||
onSorterChange(item: { id: string | number; value: string }) {
|
onSorterChange(item: { id: string | number; value: string }) {
|
||||||
this.sorterActive.id = item.id;
|
this.sorterActive.id = item.id;
|
||||||
this.sorterActive.dir = -1;
|
this.sorterActive.dir = -1;
|
||||||
this.$emit('onSearchConfirm');
|
this.$emit('onSearchConfirm');
|
||||||
},
|
},
|
||||||
|
|
||||||
onFilterChange(filter: JournalTimetableFilter) {
|
onFilterChange(filter: JournalFilter) {
|
||||||
this.journalFilterActive = filter;
|
// this.journalFilterActive = filter;
|
||||||
|
this.filterList?.filter((f) => f.filterSection === filter.filterSection).forEach((f) => (f.isActive = false));
|
||||||
|
filter.isActive = true;
|
||||||
|
|
||||||
this.$emit('onSearchConfirm');
|
this.$emit('onSearchConfirm');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
<div class="journal-stats" v-if="!store.isOffline">
|
||||||
|
<div class="tabs">
|
||||||
|
<button
|
||||||
|
v-for="tab in data.tabs"
|
||||||
|
class="btn--filled"
|
||||||
|
:data-selected="tab.name == store.currentStatsTab && areStatsOpen"
|
||||||
|
:data-inactive="tab.inactive"
|
||||||
|
:data-disabled="tab.inactive"
|
||||||
|
:disabled="tab.inactive"
|
||||||
|
@click="onTabButtonClick(tab.name)"
|
||||||
|
>
|
||||||
|
{{ $t(tab.titlePath) }}
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, KeepAlive, onMounted, reactive, Ref, ref, watch } from 'vue';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
|
import JournalDailyStats from './DailyStats.vue';
|
||||||
|
import JournalDriverStats from './JournalDriverStats.vue';
|
||||||
|
import StorageManager from '../../scripts/managers/storageManager';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
type TStatTab = 'daily' | 'driver';
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
const 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,
|
||||||
|
},
|
||||||
|
] as { name: TStatTab; titlePath: string; inactive?: boolean }[],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
function onTabButtonClick(tab: TStatTab) {
|
||||||
|
if (lastClickedTab.value == tab || !areStatsOpen.value) areStatsOpen.value = !areStatsOpen.value;
|
||||||
|
|
||||||
|
if (tab == 'daily') {
|
||||||
|
StorageManager.setBooleanValue('dailyStatsOpen', areStatsOpen.value);
|
||||||
|
lastDailyStatsOpen.value = areStatsOpen.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.currentStatsTab = tab;
|
||||||
|
lastClickedTab.value = tab;
|
||||||
|
|
||||||
|
if (areStatsOpen.value == false) store.currentStatsTab = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/JournalStats.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,284 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section class="journal-timetables">
|
|
||||||
|
|
||||||
<div class="journal_wrapper">
|
|
||||||
<JournalOptions
|
|
||||||
@on-search-confirm="searchHistory"
|
|
||||||
@on-options-reset="resetOptions"
|
|
||||||
:sorter-option-ids="['timetableId', 'beginDate', 'distance', 'total-stops']"
|
|
||||||
:filters="journalTimetableFilters"
|
|
||||||
:data-status="dataStatus"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DriverStats />
|
|
||||||
<!-- <button @click="statsCardOpen = true">Stats</button> -->
|
|
||||||
|
|
||||||
<div class="list_wrapper" @scroll="handleScroll">
|
|
||||||
<!-- <transition name="warning" mode="out-in"> -->
|
|
||||||
<!-- <div :key="dataStatus"> -->
|
|
||||||
<Loading v-if="dataStatus == DataStatus.Initialized || dataStatus == DataStatus.Loading" />
|
|
||||||
|
|
||||||
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
|
|
||||||
{{ $t('app.error') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else-if="timetableHistory.length == 0" class="journal_warning">
|
|
||||||
{{ $t('app.no-result') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<JournalTimetablesList :timetableHistory="timetableHistory" />
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="btn btn--option btn--load-data"
|
|
||||||
v-if="!scrollNoMoreData && scrollDataLoaded && timetableHistory.length >= 15"
|
|
||||||
@click="addHistoryData"
|
|
||||||
>
|
|
||||||
{{ $t('journal.load-data') }}
|
|
||||||
</button>
|
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
import DriverStats from './DriverStats.vue';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
|
||||||
import { JournalTimetableFilter, JournalTimetableSorter } from '../../types/Journal/JournalTimetablesTypes';
|
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
|
||||||
import routerMixin from '../../mixins/routerMixin';
|
|
||||||
import { DataStatus } from '../../scripts/enums/DataStatus';
|
|
||||||
import { JournalFilterType } from '../../scripts/enums/JournalFilterType';
|
|
||||||
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
|
|
||||||
import { URLs } from '../../scripts/utils/apiURLs';
|
|
||||||
import { useStore } from '../../store/store';
|
|
||||||
import JournalOptions from './JournalOptions.vue';
|
|
||||||
import { JorunalTimetableSearchType } from '../../types/Journal/JournalTimetablesTypes';
|
|
||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
|
||||||
import JournalTimetablesList from './JournalTimetablesList.vue';
|
|
||||||
import { journalTimetableFilters } from '../../constants/Journal/JournalTimetablesConsts';
|
|
||||||
|
|
||||||
const TIMETABLES_API_URL = `${URLs.stacjownikAPI}/api/getTimetables`;
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { DriverStats, Loading, JournalOptions, JournalTimetablesList },
|
|
||||||
mixins: [dateMixin, routerMixin, modalTrainMixin, imageMixin],
|
|
||||||
|
|
||||||
name: 'JournalTimetables',
|
|
||||||
|
|
||||||
props: {
|
|
||||||
timetableId: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
currentQuery: '',
|
|
||||||
scrollDataLoaded: true,
|
|
||||||
scrollNoMoreData: false,
|
|
||||||
|
|
||||||
showReturnButton: false,
|
|
||||||
statsCardOpen: false,
|
|
||||||
|
|
||||||
timetableHistory: [] as TimetableHistory[],
|
|
||||||
journalTimetableFilters,
|
|
||||||
|
|
||||||
dataStatus: DataStatus.Initialized,
|
|
||||||
dataErrorMessage: '',
|
|
||||||
|
|
||||||
DataStatus,
|
|
||||||
}),
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const sorterActive: JournalTimetableSorter = reactive({ id: 'timetableId', dir: 1 });
|
|
||||||
const journalFilterActive = ref(journalTimetableFilters[0]);
|
|
||||||
|
|
||||||
const searchersValues = reactive({
|
|
||||||
'search-train': '',
|
|
||||||
'search-driver': '',
|
|
||||||
'search-author': '',
|
|
||||||
'search-date': '',
|
|
||||||
} as JorunalTimetableSearchType);
|
|
||||||
|
|
||||||
const countFromIndex = ref(0);
|
|
||||||
const countLimit = 15;
|
|
||||||
|
|
||||||
provide('searchersValues', searchersValues);
|
|
||||||
provide('sorterActive', sorterActive);
|
|
||||||
provide('journalFilterActive', journalFilterActive);
|
|
||||||
|
|
||||||
const scrollElement: Ref<HTMLElement | null> = ref(null);
|
|
||||||
|
|
||||||
return {
|
|
||||||
sorterActive,
|
|
||||||
journalFilterActive,
|
|
||||||
searchersValues,
|
|
||||||
|
|
||||||
countFromIndex,
|
|
||||||
countLimit,
|
|
||||||
|
|
||||||
scrollElement,
|
|
||||||
store: useStore(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
activated() {
|
|
||||||
if (this.timetableId) {
|
|
||||||
this.searchersValues['search-train'] = `#${this.timetableId}`;
|
|
||||||
this.searchHistory();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
if (!this.timetableId) this.searchHistory();
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
handleScroll(e: Event) {
|
|
||||||
const listElement = e.target as HTMLElement;
|
|
||||||
const scrollTop = listElement.scrollTop;
|
|
||||||
const elementHeight = listElement.scrollHeight - listElement.offsetHeight;
|
|
||||||
|
|
||||||
if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return;
|
|
||||||
|
|
||||||
if (scrollTop > elementHeight * 0.85) this.addHistoryData();
|
|
||||||
},
|
|
||||||
|
|
||||||
resetOptions() {
|
|
||||||
this.searchersValues['search-date'] = '';
|
|
||||||
this.searchersValues['search-driver'] = '';
|
|
||||||
this.searchersValues['search-train'] = '';
|
|
||||||
this.searchersValues['search-author'] = '';
|
|
||||||
|
|
||||||
this.journalFilterActive = this.journalTimetableFilters[0];
|
|
||||||
this.sorterActive.id = 'timetableId';
|
|
||||||
|
|
||||||
this.searchHistory();
|
|
||||||
},
|
|
||||||
|
|
||||||
searchHistory() {
|
|
||||||
this.fetchHistoryData({
|
|
||||||
searchers: this.searchersValues,
|
|
||||||
filter: this.journalFilterActive,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.scrollNoMoreData = false;
|
|
||||||
this.scrollDataLoaded = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
async addHistoryData() {
|
|
||||||
this.scrollDataLoaded = false;
|
|
||||||
|
|
||||||
const countFrom = this.timetableHistory.length;
|
|
||||||
|
|
||||||
const responseData: TimetableHistory[] = await (
|
|
||||||
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}&countFrom=${countFrom}`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
if (!responseData) return;
|
|
||||||
|
|
||||||
if (responseData.length == 0) {
|
|
||||||
this.scrollNoMoreData = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.timetableHistory.push(...responseData);
|
|
||||||
this.scrollDataLoaded = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchHistoryData(
|
|
||||||
props: {
|
|
||||||
searchers?: JorunalTimetableSearchType;
|
|
||||||
filter?: JournalTimetableFilter;
|
|
||||||
} = {}
|
|
||||||
) {
|
|
||||||
this.dataStatus = DataStatus.Loading;
|
|
||||||
|
|
||||||
const queries: string[] = [];
|
|
||||||
|
|
||||||
const driverName = props.searchers?.['search-driver'].trim();
|
|
||||||
const trainNo = props.searchers?.['search-train'].trim();
|
|
||||||
const authorName = props.searchers?.['search-author'].trim();
|
|
||||||
|
|
||||||
const dateString = props.searchers?.['search-date'].trim();
|
|
||||||
const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
|
|
||||||
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
|
|
||||||
|
|
||||||
if (driverName) queries.push(`driverName=${driverName}`);
|
|
||||||
if (trainNo)
|
|
||||||
queries.push(trainNo.startsWith('#') ? `timetableId=${trainNo.replace('#', '')}` : `trainNo=${trainNo}`);
|
|
||||||
if (authorName) queries.push(`authorName=${authorName}`);
|
|
||||||
if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`);
|
|
||||||
|
|
||||||
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
|
|
||||||
if (this.sorterActive.id == 'distance') queries.push('sortBy=routeDistance');
|
|
||||||
else if (this.sorterActive.id == 'total-stops') queries.push('sortBy=allStopsCount');
|
|
||||||
else if (this.sorterActive.id == 'beginDate') queries.push('sortBy=beginDate');
|
|
||||||
else queries.push('sortBy=timetableId');
|
|
||||||
|
|
||||||
queries.push('countLimit=15');
|
|
||||||
|
|
||||||
switch (props.filter?.id) {
|
|
||||||
case JournalFilterType.abandoned:
|
|
||||||
queries.push('fulfilled=0', 'terminated=1');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case JournalFilterType.active:
|
|
||||||
queries.push('terminated=0');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case JournalFilterType.fulfilled:
|
|
||||||
queries.push('fulfilled=1');
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentQuery = queries.join('&');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const responseData: TimetableHistory[] = await (
|
|
||||||
await axios.get(`${TIMETABLES_API_URL}?${this.currentQuery}`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
if (!responseData) {
|
|
||||||
this.dataStatus = DataStatus.Error;
|
|
||||||
this.dataErrorMessage = 'Brak danych!';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!responseData) return;
|
|
||||||
|
|
||||||
// Response data exists
|
|
||||||
this.timetableHistory = responseData;
|
|
||||||
|
|
||||||
// Stats display
|
|
||||||
this.store.driverStatsName =
|
|
||||||
this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim()
|
|
||||||
? this.timetableHistory[0].driverName
|
|
||||||
: '';
|
|
||||||
|
|
||||||
this.dataStatus = DataStatus.Loaded;
|
|
||||||
} catch (error) {
|
|
||||||
this.dataStatus = DataStatus.Error;
|
|
||||||
this.dataErrorMessage = 'Ups! Coś poszło nie tak!';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/JournalSection.scss';
|
|
||||||
</style>
|
|
||||||
@@ -1,25 +1,46 @@
|
|||||||
<template>
|
<template>
|
||||||
<ul class="journal-list">
|
<transition-group class="journal-list" tag="ul" name="list-anim">
|
||||||
<li
|
<li
|
||||||
v-for="{ timetable, sceneryList, ...item } in computedTimetableHistory"
|
v-for="{ timetable, sceneryList, stockHistoryComp, ...item } in computedTimetableHistory"
|
||||||
class="journal_item"
|
class="journal_item"
|
||||||
:key="timetable.timetableId"
|
:key="timetable.id"
|
||||||
|
@click="item.showExtra.value = !item.showExtra.value"
|
||||||
>
|
>
|
||||||
<div class="journal_item-info">
|
<div class="journal_item-info">
|
||||||
<div class="info-top">
|
<div class="info-general">
|
||||||
<span
|
<span
|
||||||
|
class="general-train"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@click="showTimetable(timetable)"
|
@click.stop="showTimetable(timetable)"
|
||||||
@keydown.enter="showTimetable(timetable)"
|
@keydown.enter="showTimetable(timetable)"
|
||||||
style="cursor: pointer"
|
style="cursor: pointer"
|
||||||
>
|
>
|
||||||
<b class="text--primary">{{ timetable.trainCategoryCode }} </b>
|
<span class="text--grayed">#{{ timetable.id }}</span>
|
||||||
<b>{{ timetable.trainNo }}</b>
|
|
||||||
| <span>{{ timetable.driverName }}</span> |
|
<span class="badges" v-if="timetable.skr || timetable.twr">
|
||||||
<span class="text--grayed">#{{ timetable.timetableId }}</span>
|
<span class="train-badge twr" v-if="timetable.twr" :title="$t('general.TWR')">TWR</span>
|
||||||
|
<span class="train-badge skr" v-if="timetable.skr" :title="$t('general.SKR')">SKR</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
<strong class="text--primary">
|
||||||
|
{{ timetable.trainCategoryCode }}
|
||||||
|
</strong>
|
||||||
|
<strong> {{ timetable.trainNo }}</strong>
|
||||||
|
</span>
|
||||||
|
•
|
||||||
|
<strong
|
||||||
|
v-if="timetable.driverLevel !== null"
|
||||||
|
class="level-badge driver"
|
||||||
|
:style="calculateExpStyle(timetable.driverLevel, timetable.driverIsSupporter)"
|
||||||
|
>
|
||||||
|
{{ timetable.driverLevel < 2 ? 'L' : `${timetable.driverLevel}` }}
|
||||||
|
</strong>
|
||||||
|
|
||||||
|
<strong>{{ timetable.driverName }}</strong>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span class="general-time">
|
||||||
<b class="info-date">{{ localeDay(timetable.beginDate, $i18n.locale) }}</b>
|
<b class="info-date">{{ localeDay(timetable.beginDate, $i18n.locale) }}</b>
|
||||||
<b
|
<b
|
||||||
class="info-status"
|
class="info-status"
|
||||||
@@ -47,15 +68,27 @@
|
|||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div class="scenery-list">
|
<div class="scenery-list">
|
||||||
<span v-for="(scenery, i) in sceneryList" :key="scenery.name" :class="{ confirmed: scenery.confirmed }">
|
<span
|
||||||
<span v-if="i > 0"> ></span>
|
v-for="(scenery, i) in sceneryList.filter((_, i) =>
|
||||||
|
!item.showExtra.value ? i == 0 || i == sceneryList.length - 1 : true
|
||||||
|
)"
|
||||||
|
:key="scenery.name"
|
||||||
|
:class="{ confirmed: scenery.confirmed }"
|
||||||
|
>
|
||||||
|
<span v-if="i > 0">
|
||||||
|
>
|
||||||
|
<span v-if="!item.showExtra.value && i == 1 && sceneryList.length > 2"
|
||||||
|
>... (+{{ sceneryList.length - 2 }}) ></span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
{{ scenery.name }}
|
{{ scenery.name }}
|
||||||
|
|
||||||
<!-- Data odjazdu ze stacji początkowej -->
|
<!-- Data odjazdu ze stacji początkowej -->
|
||||||
<span v-if="i == 0" v-html="scenery.beginDateHTML"></span>
|
<span v-if="i == 0" v-html="scenery.beginDateHTML"></span>
|
||||||
|
|
||||||
<!-- Data przyjazdu do stacji końcowej -->
|
<!-- Data przyjazdu do stacji końcowej -->
|
||||||
<span v-if="i == sceneryList.length - 1" v-html="scenery.endDateHTML"> </span>
|
<span
|
||||||
|
v-if="i == sceneryList.length - 1 || (i == 1 && !item.showExtra.value)"
|
||||||
|
v-html="scenery.endDateHTML"
|
||||||
|
></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -72,6 +105,13 @@
|
|||||||
{{ timetable.confirmedStopsCount }} /
|
{{ timetable.confirmedStopsCount }} /
|
||||||
{{ timetable.allStopsCount }}
|
{{ timetable.allStopsCount }}
|
||||||
</span>
|
</span>
|
||||||
|
<span class="text--grayed" v-if="!timetable.fulfilled && timetable.currentSceneryName">
|
||||||
|
•
|
||||||
|
<b>
|
||||||
|
{{ $t(`journal.${timetable.terminated ? 'last-seen-at' : 'currently-at'}`) }}
|
||||||
|
{{ timetable.currentSceneryName.replace(/.[a-zA-Z0-9]+.sc/, '') }}
|
||||||
|
</b>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Nick dyżurnego -->
|
<!-- Nick dyżurnego -->
|
||||||
@@ -80,52 +120,85 @@
|
|||||||
<router-link class="dispatcher-link" :to="`/journal/dispatchers?dispatcherName=${timetable.authorName}`">
|
<router-link class="dispatcher-link" :to="`/journal/dispatchers?dispatcherName=${timetable.authorName}`">
|
||||||
<b>{{ timetable.authorName }}</b>
|
<b>{{ timetable.authorName }}</b>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<span class="text--grayed">
|
||||||
|
({{
|
||||||
|
(new Date(timetable.createdAt).getTime() - new Date(timetable.beginDate).getTime() < 0
|
||||||
|
? new Date(timetable.createdAt)
|
||||||
|
: new Date(timetable.beginDate)
|
||||||
|
).toLocaleString($i18n.locale, { timeStyle: 'short', dateStyle: 'full' })
|
||||||
|
}})
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button class="btn--option btn--show">
|
||||||
v-if="timetable.stockString"
|
|
||||||
class="btn--option btn--show"
|
|
||||||
@click="item.showStock.value = !item.showStock.value"
|
|
||||||
>
|
|
||||||
{{ $t('journal.stock-info') }}
|
{{ $t('journal.stock-info') }}
|
||||||
<img :src="getIcon(`arrow-${item.showStock.value ? 'asc' : 'desc'}`)" alt="Arrow" />
|
<img :src="getIcon(`arrow-${item.showExtra.value ? 'asc' : 'desc'}`)" alt="Arrow" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="info-extended" v-if="timetable.stockString && item.showStock.value">
|
<!-- Dodatkowe informacje -->
|
||||||
|
<div class="info-extended" v-if="timetable.stockString && timetable.stockMass && item.showExtra.value">
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div>
|
<div class="stock-specs">
|
||||||
<span class="badge info-badge">
|
<span class="badge specs-badge">
|
||||||
<span>{{ $t('journal.stock-max-speed') }}</span>
|
<span>{{ $t('journal.stock-max-speed') }}</span>
|
||||||
<span>{{ timetable.maxSpeed }}km/h</span>
|
<span>{{ timetable.maxSpeed }}km/h</span>
|
||||||
</span>
|
</span>
|
||||||
|
<span class="badge specs-badge">
|
||||||
<span class="badge info-badge">
|
|
||||||
<span>{{ $t('journal.stock-length') }}</span>
|
<span>{{ $t('journal.stock-length') }}</span>
|
||||||
<span>{{ timetable.stockLength }}m</span>
|
<span>
|
||||||
|
{{
|
||||||
|
item.currentHistoryIndex.value == 0
|
||||||
|
? timetable.stockLength
|
||||||
|
: stockHistoryComp[item.currentHistoryIndex.value].stockLength || timetable.stockLength
|
||||||
|
}}m
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
<span class="badge specs-badge">
|
||||||
<span class="badge info-badge">
|
|
||||||
<span>{{ $t('journal.stock-mass') }}</span>
|
<span>{{ $t('journal.stock-mass') }}</span>
|
||||||
<span>{{ Math.floor(timetable.stockMass! / 1000) }}t</span>
|
<span>
|
||||||
|
{{
|
||||||
|
Math.floor(
|
||||||
|
(item.currentHistoryIndex.value == 0
|
||||||
|
? timetable.stockMass!
|
||||||
|
: stockHistoryComp[item.currentHistoryIndex.value].stockMass || timetable.stockMass) / 1000
|
||||||
|
)
|
||||||
|
}}t
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="stock-history" v-if="stockHistoryComp.length > 1">
|
||||||
|
<button
|
||||||
|
class="btn--action"
|
||||||
|
v-for="(sh, i) in stockHistoryComp"
|
||||||
|
:data-checked="i == item.currentHistoryIndex.value"
|
||||||
|
@click.stop="item.currentHistoryIndex.value = i"
|
||||||
|
>
|
||||||
|
{{ sh.updatedAt }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul class="stock-list">
|
<ul class="stock-list">
|
||||||
<li v-for="(car, i) in timetable.stockString.split(';')" :key="i">
|
<li
|
||||||
|
v-for="(car, i) in (item.currentHistoryIndex.value == 0
|
||||||
|
? timetable.stockString
|
||||||
|
: stockHistoryComp[item.currentHistoryIndex.value].stockString
|
||||||
|
).split(';')"
|
||||||
|
:key="i"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
@error="onImageError"
|
@error="onImageError"
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${car.split(':')[0]}.png`"
|
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${car.split(':')[0]}.png`"
|
||||||
:alt="car"
|
:alt="car"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div>{{ car.replace(/_/g, ' ').split(':')[0] }}</div>
|
<div>{{ car.replace(/_/g, ' ').split(':')[0] }}</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</transition-group>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -133,6 +206,7 @@ import { defineComponent, PropType, ref } from 'vue';
|
|||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
|
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -143,14 +217,31 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [dateMixin, imageMixin, modalTrainMixin],
|
mixins: [dateMixin, imageMixin, modalTrainMixin, styleMixin],
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
computedTimetableHistory() {
|
computedTimetableHistory() {
|
||||||
return this.timetableHistory.map((timetable) => ({
|
return this.timetableHistory.map((timetable) => ({
|
||||||
timetable,
|
timetable,
|
||||||
sceneryList: this.getSceneryList(timetable),
|
sceneryList: this.getSceneryList(timetable),
|
||||||
showStock: ref(false),
|
stockHistoryComp: timetable.stockHistory
|
||||||
|
.slice()
|
||||||
|
.reverse()
|
||||||
|
.map((h) => {
|
||||||
|
const historyData = h.split('@');
|
||||||
|
|
||||||
|
return {
|
||||||
|
updatedAt: new Date(Number(historyData[0])).toLocaleTimeString(this.$i18n.locale, {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
}),
|
||||||
|
stockString: historyData[1],
|
||||||
|
stockMass: Number(historyData[2]) || undefined,
|
||||||
|
stockLength: Number(historyData[3]) || undefined,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
showExtra: ref(false),
|
||||||
|
currentHistoryIndex: ref(0),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -180,16 +271,12 @@ export default defineComponent({
|
|||||||
this.$i18n.locale
|
this.$i18n.locale
|
||||||
)}</span>)`;
|
)}</span>)`;
|
||||||
|
|
||||||
const abandonedDateHTML = ` (porz. ${this.localeTime(
|
return { name, confirmed: i < timetable.confirmedStopsCount, beginDateHTML, endDateHTML };
|
||||||
timetable.fulfilled ? timetable.scheduledEndDate : timetable.endDate,
|
|
||||||
this.$i18n.locale
|
|
||||||
)})`;
|
|
||||||
|
|
||||||
return { name, confirmed: i < timetable.confirmedStopsCount, beginDateHTML, endDateHTML, abandonedDateHTML };
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
showTimetable(timetable: TimetableHistory) {
|
showTimetable(timetable: TimetableHistory) {
|
||||||
|
if (!timetable) return;
|
||||||
if (timetable.terminated) return;
|
if (timetable.terminated) return;
|
||||||
|
|
||||||
this.selectModalTrain(timetable.driverName + timetable.trainNo.toString());
|
this.selectModalTrain(timetable.driverName + timetable.trainNo.toString());
|
||||||
@@ -204,11 +291,16 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/animations.scss';
|
||||||
@import '../../styles/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
@import '../../styles/badge.scss';
|
@import '../../styles/badge.scss';
|
||||||
@import '../../styles/JournalSection.scss';
|
@import '../../styles/JournalSection.scss';
|
||||||
|
|
||||||
|
.journal_item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
margin: 0.25em 0;
|
margin: 0.25em 0;
|
||||||
}
|
}
|
||||||
@@ -235,10 +327,14 @@ hr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-top {
|
&-general {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-route {
|
&-route {
|
||||||
@@ -250,6 +346,13 @@ hr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.general-train {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
ul.stock-list {
|
ul.stock-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
@@ -262,6 +365,45 @@ ul.stock-list {
|
|||||||
color: #aaa;
|
color: #aaa;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li > img {
|
||||||
|
vertical-align: text-bottom;
|
||||||
|
max-height: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-specs {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
|
||||||
|
.specs-badge {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
span:last-child {
|
||||||
|
color: black;
|
||||||
|
background-color: $accentCol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badges {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
|
|
||||||
|
// badge.scss
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-history {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
button[data-checked='true'] {
|
||||||
|
color: $accentCol;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenery-list {
|
.scenery-list {
|
||||||
@@ -282,26 +424,18 @@ ul.stock-list {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-badge {
|
|
||||||
span:last-child {
|
|
||||||
color: black;
|
|
||||||
background-color: $accentCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
.info-top {
|
.info-general {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
span {
|
|
||||||
margin: 0.1em auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-extended {
|
.info-extended {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.general-train {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.info-route {
|
.info-route {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -310,5 +444,13 @@ ul.stock-list {
|
|||||||
.btn--show {
|
.btn--show {
|
||||||
margin: 1em auto 0 auto;
|
margin: 1em auto 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stock-specs {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-history {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,25 +5,31 @@
|
|||||||
<div class="list-warning" v-else-if="dispatcherHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
<div class="list-warning" v-else-if="dispatcherHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
||||||
|
|
||||||
<ul class="history-list" v-else>
|
<ul class="history-list" v-else>
|
||||||
<li class="list-item" v-for="historyItem in dispatcherHistoryList">
|
<li class="list-item" v-for="item in dispatcherHistoryList">
|
||||||
<div>
|
<router-link class="item-general" :to="`/journal/dispatchers?dispatcherName=${item.dispatcherName}`">
|
||||||
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
|
<span class="text--grayed">#{{ item.stationHash }} </span>
|
||||||
<span class="text--grayed">#{{ historyItem.stationHash }} </span>
|
<b
|
||||||
<b>{{ historyItem.dispatcherName }}</b>
|
v-if="item.dispatcherLevel !== null"
|
||||||
</router-link>
|
class="level-badge dispatcher"
|
||||||
</div>
|
:style="calculateExpStyle(item.dispatcherLevel, item.dispatcherIsSupporter)"
|
||||||
|
>
|
||||||
|
{{ item.dispatcherLevel >= 2 ? item.dispatcherLevel : 'L' }}
|
||||||
|
</b>
|
||||||
|
|
||||||
<div v-if="historyItem.timestampTo">
|
<b>{{ item.dispatcherName }}</b>
|
||||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
</router-link>
|
||||||
|
|
||||||
{{ timestampToString(historyItem.timestampFrom) }}
|
<div v-if="item.timestampTo">
|
||||||
- {{ timestampToString(historyItem.timestampTo) }} ({{ calculateDuration(historyItem.currentDuration) }})
|
<b>{{ $d(item.timestampFrom) }}</b>
|
||||||
|
|
||||||
|
{{ timestampToString(item.timestampFrom) }}
|
||||||
|
- {{ timestampToString(item.timestampTo) }} ({{ calculateDuration(item.currentDuration) }})
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dispatcher-online" v-else>
|
<div class="dispatcher-online" v-else>
|
||||||
{{ $t('journal.online-since') }}
|
{{ $t('journal.online-since') }}
|
||||||
<b>{{ timestampToString(historyItem.timestampFrom) }}</b>
|
<b>{{ timestampToString(item.timestampFrom) }}</b>
|
||||||
({{ calculateDuration(historyItem.currentDuration) }})
|
({{ calculateDuration(item.currentDuration) }})
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -39,10 +45,11 @@ import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIDa
|
|||||||
import Station from '../../scripts/interfaces/Station';
|
import Station from '../../scripts/interfaces/Station';
|
||||||
import { URLs } from '../../scripts/utils/apiURLs';
|
import { URLs } from '../../scripts/utils/apiURLs';
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryDispatchersHistory',
|
name: 'SceneryDispatchersHistory',
|
||||||
mixins: [dateMixin],
|
mixins: [dateMixin, styleMixin],
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>,
|
type: Object as PropType<Station>,
|
||||||
@@ -55,7 +62,7 @@ export default defineComponent({
|
|||||||
dataStatus: DataStatus.Loading,
|
dataStatus: DataStatus.Loading,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
activated() {
|
||||||
this.fetchAPIData();
|
this.fetchAPIData();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -96,6 +103,13 @@ export default defineComponent({
|
|||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-general {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
.dispatcher-online {
|
.dispatcher-online {
|
||||||
color: springgreen;
|
color: springgreen;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-header">
|
<section class="info-header">
|
||||||
<a class="scenery-name" :href="station.generalInfo?.url">
|
<a class="scenery-name" :href="station.generalInfo?.url" target="_blank">
|
||||||
{{ station.name }}
|
{{ station.name }}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<div class="scenery-abbrev">{{ $t('scenery.abbrev') }} <b>{{ station.generalInfo?.abbr }}</b></div>
|
||||||
|
|
||||||
<div class="scenery-hash" v-if="station.onlineInfo?.hash">#{{ station.onlineInfo.hash }}</div>
|
<div class="scenery-hash" v-if="station.onlineInfo?.hash">#{{ station.onlineInfo.hash }}</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
@@ -28,16 +30,20 @@ export default defineComponent({
|
|||||||
|
|
||||||
.scenery-name {
|
.scenery-name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
font-size: 3em;
|
font-size: 3em;
|
||||||
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scenery-abbrev {
|
||||||
|
font-size: 1.3em;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
.scenery-hash {
|
.scenery-hash {
|
||||||
|
margin-top: 0.5em;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="scenery-info">
|
<div class="scenery-info">
|
||||||
<section v-if="!timetableOnly">
|
<section v-if="!timetableOnly">
|
||||||
<div class="info-general" v-if="station.generalInfo">
|
<div class="scenery-info-general" v-if="station.generalInfo">
|
||||||
<scenery-info-icons :station="station" />
|
<scenery-info-icons :station="station" />
|
||||||
|
|
||||||
<div class="general-list">
|
<div class="scenery-general-list">
|
||||||
<span>
|
<span>
|
||||||
<b>{{ $t('availability.title') }}:</b> {{ $t(`availability.${station.generalInfo.availability}`) }}
|
<b>{{ $t('availability.title') }}:</b> {{ $t(`availability.${station.generalInfo.availability}`) }}
|
||||||
|
|
||||||
<span v-if="station.generalInfo.reqLevel > -1">
|
<span v-if="station.generalInfo.reqLevel > -1">
|
||||||
- {{ $tc('scenery.req-level', station.generalInfo.reqLevel, { lvl: station.generalInfo.reqLevel }) }}
|
- {{ $t('scenery.req-level', { lvl: station.generalInfo.reqLevel }, station.generalInfo.reqLevel) }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -26,26 +26,33 @@
|
|||||||
</span>
|
</span>
|
||||||
<span v-if="station.generalInfo.project">
|
<span v-if="station.generalInfo.project">
|
||||||
• <b>{{ $t('scenery.project-title') }}: </b>
|
• <b>{{ $t('scenery.project-title') }}: </b>
|
||||||
<b style="color: salmon">{{ station.generalInfo.project }}</b>
|
<a
|
||||||
|
style="color: salmon; text-decoration: underline; font-weight: bold"
|
||||||
|
:href="station.generalInfo.projectUrl"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ station.generalInfo.project }}
|
||||||
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<scenery-info-routes :station="station" />
|
<scenery-info-routes :station="station" />
|
||||||
|
|
||||||
<div class="scenery-authors" v-if="station.generalInfo.authors && station.generalInfo.authors.length > 0">
|
<div class="scenery-authors" v-if="station.generalInfo.authors && station.generalInfo.authors.length > 0">
|
||||||
<b> {{ $tc('scenery.authors-title', station.generalInfo.authors.length) }}: </b>
|
<b>
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
'scenery.authors-title',
|
||||||
|
{ authors: station.generalInfo.authors.length },
|
||||||
|
station.generalInfo.authors.length
|
||||||
|
)
|
||||||
|
}}:
|
||||||
|
</b>
|
||||||
{{ station.generalInfo.authors.join(', ') }}
|
{{ station.generalInfo.authors.join(', ') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br />
|
|
||||||
<div class="scenery-topic" v-if="station.generalInfo.url">
|
|
||||||
<a :href="station.generalInfo.url" target="_blank">
|
|
||||||
> {{ $t('scenery.forum-topic', { name: station.name }) }} <
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin: 2em 0; height: 2px; background-color: white" />
|
<div style="margin: 2em 0; height: 2px; background-color: white"></div>
|
||||||
|
|
||||||
<!-- info dispatcher -->
|
<!-- info dispatcher -->
|
||||||
<scenery-info-dispatcher :station="station" :onlineFrom="onlineFrom" />
|
<scenery-info-dispatcher :station="station" :onlineFrom="onlineFrom" />
|
||||||
@@ -72,7 +79,6 @@ import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue';
|
|||||||
import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
|
import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
|
||||||
import Station from '../../scripts/interfaces/Station';
|
import Station from '../../scripts/interfaces/Station';
|
||||||
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
SceneryInfoDispatcher,
|
SceneryInfoDispatcher,
|
||||||
@@ -125,11 +131,11 @@ h3.section-header {
|
|||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-general {
|
.scenery-info-general {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-list {
|
.scenery-general-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-dispatcher">
|
<section class="info-dispatcher">
|
||||||
<div class="dispatcher" v-if="station.onlineInfo">
|
<div class="dispatcher" v-if="station.onlineInfo">
|
||||||
<span class="dispatcher_level" :style="calculateExpStyle(station.onlineInfo.dispatcherExp)">
|
<span
|
||||||
|
class="dispatcher_level"
|
||||||
|
:style="calculateExpStyle(station.onlineInfo.dispatcherExp, station.onlineInfo.dispatcherIsSupporter)"
|
||||||
|
>
|
||||||
{{ station.onlineInfo.dispatcherExp > 1 ? station.onlineInfo.dispatcherExp : 'L' }}
|
{{ station.onlineInfo.dispatcherExp > 1 ? station.onlineInfo.dispatcherExp : 'L' }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
<b>{{ $t('scenery.one-way-routes') }}</b>
|
<b>{{ $t('scenery.one-way-routes') }}</b>
|
||||||
|
|
||||||
<ul class="routes-list">
|
<ul class="routes-list">
|
||||||
<li
|
<li v-for="route in station.generalInfo.routes.oneWay" @click="setActiveShowLength(route.name)">
|
||||||
v-for="route in station.generalInfo.routes.oneWay"
|
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"> {{ route.name }}</span>
|
||||||
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"
|
<span v-if="route.speed" class="speed">
|
||||||
>
|
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
|
||||||
{{ route.name }}
|
</span>
|
||||||
<b v-if="route.SBL">SBL</b>
|
<span v-if="route.SBL" class="sbl">SBL</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -18,41 +18,15 @@
|
|||||||
<b>{{ $t('scenery.two-way-routes') }}</b>
|
<b>{{ $t('scenery.two-way-routes') }}</b>
|
||||||
|
|
||||||
<ul class="routes-list">
|
<ul class="routes-list">
|
||||||
<li
|
<li v-for="(route, i) in station.generalInfo.routes.twoWay" @click="setActiveShowLength(route.name)">
|
||||||
v-for="route in station.generalInfo.routes.twoWay"
|
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{ route.name }}</span>
|
||||||
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"
|
<span v-if="route.speed" class="speed">
|
||||||
>
|
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
|
||||||
{{ route.name }} <b v-if="route.SBL">SBL</b>
|
</span>
|
||||||
|
<span v-if="route.SBL" class="sbl">SBL</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div
|
|
||||||
class="route-info"
|
|
||||||
:class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"
|
|
||||||
v-for="route in [...station.generalInfo.routes.oneWay, ...station.generalInfo.routes.twoWay].filter(
|
|
||||||
(route) => route.name != '-'
|
|
||||||
)"
|
|
||||||
:key="route.name"
|
|
||||||
:title="`Szlak ${route.name}: ${route.isInternal ? 'wewnętrzny' : 'zewnętrzny'}, ${
|
|
||||||
route.tracks == 2 ? 'dwutorowy' : 'jednotorowy'
|
|
||||||
}, ${route.catenary ? 'zelektryfikowany' : 'niezelektryfikowany'} z ${route.SBL ? 'SBL' : 'PBL'} ${
|
|
||||||
route.TWB ? 'i blokadą dwukierunkową' : ''
|
|
||||||
}`"
|
|
||||||
> -->
|
|
||||||
<!-- <span class="track-name">
|
|
||||||
<b>{{ route.name }}</b>
|
|
||||||
</span> -->
|
|
||||||
<!--
|
|
||||||
<span class="track-specs">
|
|
||||||
{{ route.tracks }}tor
|
|
||||||
<img v-if="route.catenary" :src="icons.trackCatenary" alt="icon track catenary" />
|
|
||||||
<img v-else :src="icons.trackNoCatenary" alt="icon track no catenary" />
|
|
||||||
|
|
||||||
<img v-if="route.TWB" :src="icons.trackTWB" alt="icon track twb" />
|
|
||||||
<img v-if="route.SBL" :src="icons.trackSBL" alt="icon track sbl" />
|
|
||||||
</span> -->
|
|
||||||
<!-- </div> -->
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -67,6 +41,19 @@ export default defineComponent({
|
|||||||
default: {},
|
default: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
setActiveShowLength(name: string) {
|
||||||
|
if (this.activeShowLength.includes(name)) this.activeShowLength.splice(this.activeShowLength.indexOf(name), 1);
|
||||||
|
else this.activeShowLength.push(name);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeShowLength: [] as string[],
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -91,23 +78,51 @@ export default defineComponent({
|
|||||||
ul.routes-list {
|
ul.routes-list {
|
||||||
margin: 0.45em 0.25em;
|
margin: 0.45em 0.25em;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
background-color: #007599;
|
margin: 0.5em 0.25em;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
padding: 0.2em 0.25em;
|
user-select: none;
|
||||||
margin-left: 0.25em;
|
-moz-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
|
||||||
&.no-catenary {
|
span {
|
||||||
background-color: #686868;
|
padding: 0.2em 0.25em;
|
||||||
}
|
background-color: #007599;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
&.internal {
|
&.no-catenary {
|
||||||
text-decoration: underline;
|
background-color: #686868;
|
||||||
}
|
}
|
||||||
|
|
||||||
b {
|
&.internal {
|
||||||
color: var(--clr-primary);
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.speed {
|
||||||
|
background-color: #404040;
|
||||||
|
color: #cfcfcf;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.sbl {
|
||||||
|
color: var(--clr-primary);
|
||||||
|
background-color: #404040;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-radius: 0 0.5em 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-radius: 0.5em 0 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:only-child {
|
||||||
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,37 @@
|
|||||||
<section class="scenery-timetable">
|
<section class="scenery-timetable">
|
||||||
<div class="timetable-header">
|
<div class="timetable-header">
|
||||||
<h3>
|
<h3>
|
||||||
<img :src="getIcon('timetable')" alt="icon-timetable" />
|
<img :src="getIcon('timetable')" alt="icon-timetable" />
|
||||||
<span>{{ $t('scenery.timetables') }}</span>
|
<span>{{ $t('scenery.timetables') }}</span>
|
||||||
|
|
||||||
<span class="text--primary">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
|
<span>
|
||||||
<span> / </span>
|
<span class="text--primary">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
|
||||||
<span class="text--grayed">
|
<span> / </span>
|
||||||
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }}
|
<span class="text--grayed">
|
||||||
|
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="header_links">
|
||||||
|
<a
|
||||||
|
:href="`https://pragotron-td2.web.app/board?name=${station.name}`"
|
||||||
|
target="_blank"
|
||||||
|
:title="$t('scenery.pragotron-link')"
|
||||||
|
>
|
||||||
|
<img :src="getIcon('pragotron')" alt="icon-pragotron" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
:href="`https://tablice-td2.web.app/?station=${station.name}`"
|
||||||
|
target="_blank"
|
||||||
|
:title="$t('scenery.tablice-link')"
|
||||||
|
>
|
||||||
|
<img :src="getIcon('tablice', 'ico')" alt="icon-tablice" />
|
||||||
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="timetable-checkpoints" v-if="station && station.generalInfo?.checkpoints">
|
<div class="timetable-checkpoints" v-if="station?.generalInfo?.checkpoints">
|
||||||
<span v-for="(cp, i) in station.generalInfo.checkpoints" :key="i">
|
<span v-for="(cp, i) in station.generalInfo.checkpoints" :key="i">
|
||||||
{{ (i > 0 && '•') || '' }}
|
{{ (i > 0 && '•') || '' }}
|
||||||
|
|
||||||
@@ -41,7 +61,7 @@
|
|||||||
{{ $t('scenery.no-timetables') }}
|
{{ $t('scenery.no-timetables') }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<transition-group name="timetables-anim">
|
<transition-group name="list-anim">
|
||||||
<div
|
<div
|
||||||
class="timetable-item"
|
class="timetable-item"
|
||||||
v-for="(scheduledTrain, i) in computedScheduledTrains"
|
v-for="(scheduledTrain, i) in computedScheduledTrains"
|
||||||
@@ -168,7 +188,6 @@ import { useStore } from '../../store/store';
|
|||||||
import imageMixin from '../../mixins/imageMixin';
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
|
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
|
||||||
import ScheduledTrain from '../../scripts/interfaces/ScheduledTrain';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryTimetable',
|
name: 'SceneryTimetable',
|
||||||
@@ -272,22 +291,7 @@ export default defineComponent({
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
@import '../../styles/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
@import '../../styles/animations.scss';
|
||||||
.timetables-anim-move,
|
|
||||||
.timetables-anim-enter-active,
|
|
||||||
.timetables-anim-leave-active {
|
|
||||||
transition: all 250ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timetables-anim-enter-from,
|
|
||||||
.timetables-anim-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(30px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.timetables-anim-leave-active {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scenery-timetable {
|
.scenery-timetable {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -296,24 +300,36 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.timetable-header {
|
.timetable-header {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
|
|
||||||
background-color: #181818;
|
background-color: #181818;
|
||||||
|
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 25px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
gap: 0.5em;
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header_links {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.timetable {
|
.timetable {
|
||||||
&-count {
|
&-count {
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
@@ -370,7 +386,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
padding: 0.75em 0;
|
|
||||||
|
margin-top: 0.5em;
|
||||||
|
|
||||||
button.checkpoint_item {
|
button.checkpoint_item {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
@@ -486,21 +503,6 @@ export default defineComponent({
|
|||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenery-timetable-list-anim {
|
|
||||||
&-enter-from,
|
|
||||||
&-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-active {
|
|
||||||
transition: all 100ms ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-leave-active {
|
|
||||||
transition: all 100ms ease-out 100ms;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
.timetable-item {
|
.timetable-item {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
@@ -2,33 +2,44 @@
|
|||||||
<section class="scenery-timetables-history scenery-section">
|
<section class="scenery-timetables-history scenery-section">
|
||||||
<Loading v-if="dataStatus != 2" />
|
<Loading v-if="dataStatus != 2" />
|
||||||
|
|
||||||
<div class="list-warning" v-else-if="sceneryHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
<table v-else-if="sceneryHistoryList.length">
|
||||||
<ul class="history-list" v-else>
|
<thead>
|
||||||
<li class="list-item" v-for="historyItem in sceneryHistoryList">
|
<th>{{ $t('scenery.timetables-history-id') }}</th>
|
||||||
<div>
|
<th>{{ $t('scenery.timetables-history-number') }}</th>
|
||||||
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
|
<th>{{ $t('scenery.timetables-history-route') }}</th>
|
||||||
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
|
<th>{{ $t('scenery.timetables-history-driver') }}</th>
|
||||||
</div>
|
<th>{{ $t('scenery.timetables-history-author') }}</th>
|
||||||
|
<th>{{ $t('scenery.timetables-history-date') }}</th>
|
||||||
|
</thead>
|
||||||
|
|
||||||
<div>
|
<tbody>
|
||||||
<router-link :to="`/journal/timetables?timetableId=${historyItem.timetableId}`">
|
<tr v-for="historyItem in sceneryHistoryList">
|
||||||
<span class="text--grayed"> #{{ historyItem.timetableId }} </span>
|
<td>
|
||||||
<b class="text--primary"> {{ historyItem.trainCategoryCode }} {{ historyItem.trainNo }}</b>
|
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">#{{ historyItem.id }}</router-link>
|
||||||
<div>{{ historyItem.driverName }}</div>
|
</td>
|
||||||
</router-link>
|
<td>
|
||||||
</div>
|
<b class="text--primary">{{ historyItem.trainCategoryCode }}</b> <br />
|
||||||
|
{{ historyItem.trainNo }}
|
||||||
|
</td>
|
||||||
|
<td>{{ historyItem.route.replace('|', ' -> ') }}</td>
|
||||||
|
<td>{{ historyItem.driverName }}</td>
|
||||||
|
<td>
|
||||||
|
<router-link
|
||||||
|
v-if="historyItem.authorName"
|
||||||
|
:to="`/journal/dispatchers?dispatcherName=${historyItem.authorName}`"
|
||||||
|
>{{ historyItem.authorName }}
|
||||||
|
</router-link>
|
||||||
|
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
|
||||||
|
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
<div>{{ historyItem.route.replace('|', ' -> ') }}</div>
|
<div class="list-warning" v-else>{{ $t('scenery.history-list-empty') }}</div>
|
||||||
<!-- <div>{{ historyItem.routeDistance }} km</div> -->
|
|
||||||
<div>
|
|
||||||
{{ $t('scenery.timetable-author-title') }}:
|
|
||||||
<b v-if="historyItem.authorName">{{ historyItem.authorName }}</b>
|
|
||||||
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <div v-if="historyItem.authorId">{{ historyItem.authorName }}</div> -->
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -57,16 +68,16 @@ export default defineComponent({
|
|||||||
dataStatus: DataStatus.Loading,
|
dataStatus: DataStatus.Loading,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
activated() {
|
||||||
this.fetchAPIData();
|
this.fetchAPIData();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchAPIData(countFrom = 0, countLimit = 15) {
|
async fetchAPIData(countFrom = 0, countLimit = 15) {
|
||||||
try {
|
try {
|
||||||
const requestString = `${URLs.stacjownikAPI}/api/getSceneryTimetables?name=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
const requestString = `${URLs.stacjownikAPI}/api/getIssuedTimetables?name=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
||||||
const historyAPIData: SceneryTimetableHistory = await (await axios.get(requestString)).data;
|
const historyAPIData: SceneryTimetableHistory = await (await axios.get(requestString)).data;
|
||||||
|
|
||||||
this.sceneryHistoryList = historyAPIData.sceneryTimetables;
|
this.sceneryHistoryList = historyAPIData.timetables;
|
||||||
this.dataStatus = DataStatus.Loaded;
|
this.dataStatus = DataStatus.Loaded;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -91,17 +102,29 @@ export default defineComponent({
|
|||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-item {
|
table {
|
||||||
display: grid;
|
width: 100%;
|
||||||
grid-template-columns: 1fr 2fr 2fr 1fr;
|
border-collapse: collapse;
|
||||||
gap: 1em;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
background-color: #353535;
|
thead {
|
||||||
padding: 0.5em;
|
position: sticky;
|
||||||
margin: 0.5em 0;
|
top: 0;
|
||||||
|
background-color: #222222;
|
||||||
|
}
|
||||||
|
|
||||||
line-height: 1.5em;
|
th {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
background-color: #353535;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 0.75em;
|
||||||
|
border-bottom: solid 5px #111;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
|
|||||||
@@ -1,47 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="general-status">
|
<div class="general-status">
|
||||||
<span :class="scheduledTrain.stopStatus">
|
<span :class="computedScheduledTrain.stopStatus" :title="computedScheduledTrain.stopStatusDescription">
|
||||||
<span v-if="scheduledTrain.stopStatus == 'arriving'">
|
{{ computedScheduledTrain.stopStatusIndicator }}
|
||||||
<span v-if="scheduledTrain.prevDepartureLine">({{ scheduledTrain.prevDepartureLine }})</span>
|
|
||||||
{{ scheduledTrain.prevStationName }}
|
|
||||||
><span v-if="scheduledTrain.nextArrivalLine"> ({{ scheduledTrain.nextArrivalLine }}) </span>
|
|
||||||
{{ scheduledTrain.nextStationName || '---' }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else-if="scheduledTrain.stopStatus == 'departed'">
|
|
||||||
>> <span v-if="scheduledTrain.nextArrivalLine"> ({{ scheduledTrain.nextArrivalLine }}) </span>
|
|
||||||
{{ scheduledTrain.nextStationName }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else-if="scheduledTrain.stopStatus == 'departed-away'">
|
|
||||||
>>>
|
|
||||||
<span v-if="scheduledTrain.nextArrivalLine"> ({{ scheduledTrain.nextArrivalLine }}) </span>
|
|
||||||
{{ scheduledTrain.nextStationName }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else-if="scheduledTrain.stopStatus == 'online'">
|
|
||||||
>
|
|
||||||
<span v-if="scheduledTrain.nextArrivalLine">
|
|
||||||
({{ scheduledTrain.nextArrivalLine }}) {{ scheduledTrain.nextStationName }}
|
|
||||||
</span>
|
|
||||||
<span v-else-if="!scheduledTrain.nextStationName">{{ $t('timetables.end') }}</span>
|
|
||||||
<span v-else>{{ scheduledTrain.nextStationName }}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else-if="scheduledTrain.stopStatus == 'stopped'">
|
|
||||||
>
|
|
||||||
<span v-if="scheduledTrain.nextArrivalLine"> ({{ scheduledTrain.nextArrivalLine }}) </span>
|
|
||||||
{{ scheduledTrain.nextStationName }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else-if="scheduledTrain.stopStatus == 'terminated'">X {{ $t('timetables.terminated') }}</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import ScheduledTrain from '../../scripts/interfaces/ScheduledTrain';
|
import { ScheduledTrain, StopStatus } from '../../scripts/interfaces/ScheduledTrain';
|
||||||
|
|
||||||
|
interface ScheduledTrainComp extends ScheduledTrain {
|
||||||
|
stopStatusIndicator: string;
|
||||||
|
stopStatusDescription: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -50,6 +22,58 @@ export default defineComponent({
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
computedScheduledTrain(): ScheduledTrainComp {
|
||||||
|
const { prevDepartureLine, prevStationName, stopStatus, nextArrivalLine, nextStationName } = this.scheduledTrain;
|
||||||
|
|
||||||
|
const prevDepartureIndicator = prevDepartureLine ? `(${prevDepartureLine}) ${prevStationName}` : '---';
|
||||||
|
const nextArrivalIndicator = nextArrivalLine ? `(${nextArrivalLine}) ${nextStationName}` : '---';
|
||||||
|
|
||||||
|
let stopStatusDescription = '',
|
||||||
|
stopStatusIndicator = '';
|
||||||
|
|
||||||
|
switch (stopStatus) {
|
||||||
|
case StopStatus.arriving:
|
||||||
|
stopStatusIndicator = `${this.$t('timetables.from')}: ${prevDepartureIndicator}`;
|
||||||
|
stopStatusDescription = this.$t('timetables.desc-arriving', { prevStationName, prevDepartureLine });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StopStatus.online:
|
||||||
|
case StopStatus.stopped:
|
||||||
|
stopStatusIndicator = nextArrivalLine
|
||||||
|
? `${this.$t('timetables.to')}: ${nextArrivalIndicator}`
|
||||||
|
: `${this.$t('timetables.desc-end')}`;
|
||||||
|
stopStatusDescription = nextArrivalLine
|
||||||
|
? this.$t(`timetables.desc-${stopStatus}`, { nextStationName, nextArrivalLine })
|
||||||
|
: '';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StopStatus.departed:
|
||||||
|
stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`;
|
||||||
|
stopStatusDescription = this.$t('timetables.desc-departed', { nextStationName, nextArrivalLine });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StopStatus['departed-away']:
|
||||||
|
stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`;
|
||||||
|
stopStatusDescription = this.$t('timetables.desc-departed-away', { nextStationName, nextArrivalLine });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case StopStatus.terminated:
|
||||||
|
stopStatusIndicator = `X ${this.$t('timetables.desc-terminated')}`;
|
||||||
|
stopStatusDescription = this.$t('timetables.desc-terminated');
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...this.scheduledTrain,
|
||||||
|
stopStatusDescription,
|
||||||
|
stopStatusIndicator,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -86,3 +110,4 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="btn--action" :class="option.section" :data-selected="option.value" @click="handleChange">
|
<button
|
||||||
|
class="btn--action"
|
||||||
|
:class="option.section"
|
||||||
|
:data-selected="option.value"
|
||||||
|
@click="handleLeftClick"
|
||||||
|
@dblclick="handleDbClick"
|
||||||
|
>
|
||||||
{{ $t(`filters.${option.id}`) }}
|
{{ $t(`filters.${option.id}`) }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
@@ -29,20 +35,48 @@ export default defineComponent({
|
|||||||
filterStore: useStationFiltersStore(),
|
filterStore: useStationFiltersStore(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
handleChange() {
|
handleLeftClick() {
|
||||||
this.option.value = !this.option.value;
|
this.option.value = !this.option.value;
|
||||||
|
this.filterStore.lastClickedFilterId = '';
|
||||||
|
|
||||||
this.filterStore.changeFilterValue({
|
this.filterStore.changeFilterValue({
|
||||||
name: this.option.name,
|
name: this.option.name,
|
||||||
value: !this.option.value,
|
value: !this.option.value,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleDbClick(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
this.filterStore.lastClickedFilterId = this.option.id;
|
||||||
|
this.option.value = true;
|
||||||
|
|
||||||
|
this.filterStore.changeFilterValue({
|
||||||
|
name: this.option.name,
|
||||||
|
value: !this.option.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filterStore.inputs.options
|
||||||
|
.filter((option) => {
|
||||||
|
return option.section == this.option.section && option.id != this.option.id;
|
||||||
|
})
|
||||||
|
.forEach((option) => {
|
||||||
|
this.filterStore.changeFilterValue({
|
||||||
|
name: option.name,
|
||||||
|
value: this.option.value,
|
||||||
|
});
|
||||||
|
|
||||||
|
option.value = !this.option.value;
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
$realityCol: #e03b07;
|
||||||
$accessCol: #e03b07;
|
$accessCol: #e03b07;
|
||||||
$controlCol: #0085ff;
|
$controlCol: #0085ff;
|
||||||
$signalCol: #bf7c00;
|
$signalCol: #bf7c00;
|
||||||
@@ -51,56 +85,17 @@ $saveCol: #28a826;
|
|||||||
$routesCol: #9049c0;
|
$routesCol: #9049c0;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
width: 100%;
|
padding: 0.25em;
|
||||||
padding: 0.4em;
|
border-radius: 0;
|
||||||
border-radius: 0.4em;
|
|
||||||
|
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
outline: 1px solid white;
|
outline: 1px solid white;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-selected='true'] {
|
&[data-selected='true'] {
|
||||||
&.access {
|
background-color: forestgreen;
|
||||||
background-color: $accessCol;
|
font-weight: bold;
|
||||||
box-shadow: 0 0 6px 1px $accessCol;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.control {
|
|
||||||
background-color: $controlCol;
|
|
||||||
box-shadow: 0 0 6px 1px $controlCol;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.signals {
|
|
||||||
background-color: $signalCol;
|
|
||||||
box-shadow: 0 0 6px 1px $signalCol;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.routes {
|
|
||||||
background-color: $routesCol;
|
|
||||||
box-shadow: 0 0 6px 1px $routesCol;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.status {
|
|
||||||
background-color: $statusCol;
|
|
||||||
box-shadow: 0 0 6px 1px $statusCol;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.save {
|
|
||||||
background-color: $saveCol;
|
|
||||||
box-shadow: 0 0 6px 1px $saveCol;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.troll {
|
|
||||||
background-color: firebrick;
|
|
||||||
box-shadow: 0 0 6px 1px firebrick;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mode {
|
|
||||||
background-color: lightgreen;
|
|
||||||
color: black;
|
|
||||||
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<datalist id="sceneries">
|
<datalist id="sceneries">
|
||||||
<option v-for="scenery in store.stationList" :value="scenery.name"></option>
|
<option v-for="scenery in sortedStationList" :value="scenery.name"></option>
|
||||||
</datalist>
|
</datalist>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,15 +26,28 @@
|
|||||||
<div class="card" v-if="isVisible" tabindex="0" ref="cardEl">
|
<div class="card" v-if="isVisible" tabindex="0" ref="cardEl">
|
||||||
<div class="card_content">
|
<div class="card_content">
|
||||||
<div class="card_title flex">{{ $t('filters.title') }}</div>
|
<div class="card_title flex">{{ $t('filters.title') }}</div>
|
||||||
|
<p class="card_info" v-html="$t('filters.desc')"></p>
|
||||||
|
|
||||||
<section class="card_options">
|
<section class="card_options">
|
||||||
<filter-option
|
<div class="option-section" v-for="section in filterStore.inputs.optionSections">
|
||||||
v-for="(option, i) in filterStore.inputs.options"
|
<h3 class="text--primary">
|
||||||
:option="option"
|
{{ $t(`filters.sections.${section}`) }}
|
||||||
:key="i"
|
|
||||||
@optionChange="handleChange"
|
<button @click="filterStore.resetSectionOptions(section)">RESET</button>
|
||||||
/>
|
</h3>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="section-inputs">
|
||||||
|
<filter-option
|
||||||
|
v-for="(option, i) in filterStore.inputs.options.filter((o) => o.section == section)"
|
||||||
|
:option="option"
|
||||||
|
:key="i"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card_timestamp" style="text-align: center">
|
<section class="card_timestamp" style="text-align: center">
|
||||||
<div>{{ $t('filters.minimum-hours-title') }}</div>
|
<div>{{ $t('filters.minimum-hours-title') }}</div>
|
||||||
<span class="clock">
|
<span class="clock">
|
||||||
@@ -80,18 +93,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card_actions">
|
|
||||||
<div class="action-buttons">
|
|
||||||
<button class="btn--action" style="width: 100%" @click="saveFilters" :data-selected="saveOptions">
|
|
||||||
{{ $t('filters.save') }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="btn--action" @click="resetFilters">{{ $t('filters.reset') }}</button>
|
|
||||||
<button class="btn--action" @click="closeCard">{{ $t('filters.close') }}</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<section class="card_actions">
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button class="btn--action" style="width: 100%" @click="saveFilters" :data-selected="saveOptions">
|
||||||
|
{{ $t('filters.save') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn--action"
|
||||||
|
@click="resetFilters"
|
||||||
|
:disabled="filterStore.areFiltersAtDefault"
|
||||||
|
:data-disabled="filterStore.areFiltersAtDefault"
|
||||||
|
>
|
||||||
|
{{ $t('filters.reset') }}
|
||||||
|
</button>
|
||||||
|
<button class="btn--action" @click="closeCard">{{ $t('filters.close') }}</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</section>
|
</section>
|
||||||
@@ -150,6 +170,14 @@ export default defineComponent({
|
|||||||
this.currentRegion = this.store.region;
|
this.currentRegion = this.store.region;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
sortedStationList() {
|
||||||
|
return this.store.stationList
|
||||||
|
.filter((s) => s.name.toLocaleLowerCase().includes(this.chosenSearchScenery.toLocaleLowerCase()))
|
||||||
|
.sort((s1, s2) => (s1.name > s2.name ? 1 : -1));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
chosenSearchScenery(value: string) {
|
chosenSearchScenery(value: string) {
|
||||||
const chosenStation = this.store.stationList.find(({ name }) => name == value);
|
const chosenStation = this.store.stationList.find(({ name }) => name == value);
|
||||||
@@ -173,15 +201,6 @@ export default defineComponent({
|
|||||||
this.isVisible = !this.isVisible;
|
this.isVisible = !this.isVisible;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleChange(change: { name: string; value: boolean }) {
|
|
||||||
this.filterStore.changeFilterValue({
|
|
||||||
name: change.name,
|
|
||||||
value: !change.value,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.saveOptions) StorageManager.setBooleanValue(change.name, change.value);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleInput(e: Event) {
|
handleInput(e: Event) {
|
||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement;
|
||||||
|
|
||||||
@@ -273,6 +292,14 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr auto;
|
||||||
|
|
||||||
|
&_info {
|
||||||
|
background-color: #111;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
&_controls {
|
&_controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
@@ -284,13 +311,13 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
&_content {
|
&_content {
|
||||||
|
padding: 1em 0.5em;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
|
overflow: auto;
|
||||||
max-height: 90vh;
|
|
||||||
|
|
||||||
padding: 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&_title {
|
&_title {
|
||||||
@@ -301,18 +328,6 @@ export default defineComponent({
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_options {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
||||||
grid-template-rows: repeat(4, 1fr);
|
|
||||||
gap: 0.5em;
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(8em, 1fr));
|
|
||||||
grid-template-rows: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&_regions {
|
&_regions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -383,6 +398,9 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
&_actions {
|
&_actions {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
.filter-option {
|
.filter-option {
|
||||||
max-width: 50%;
|
max-width: 50%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -401,14 +419,42 @@ export default defineComponent({
|
|||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
&[data-selected='true'] {
|
&[data-selected='true'] {
|
||||||
background-color: lightgreen;
|
background-color: forestgreen;
|
||||||
color: black;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card_options {
|
||||||
|
.option-section h3 {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.25em;
|
||||||
|
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0.15em;
|
||||||
|
color: coral;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-inputs {
|
||||||
|
display: grid;
|
||||||
|
// flex-wrap: wrap;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
// grid-template-rows: repeat(3, 1fr);
|
||||||
|
gap: 0.5em;
|
||||||
|
margin: 1em 0;
|
||||||
|
|
||||||
|
// @include smallScreen() {
|
||||||
|
// grid-template-columns: repeat(auto-fit, minmax(8em, 1fr));
|
||||||
|
// grid-template-rows: auto;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -8,26 +8,36 @@
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th v-for="(id, i) in headIds" :key="id" @click="() => changeSorter(i)">
|
<th
|
||||||
|
v-for="(headerName, i) in headIds"
|
||||||
|
:key="headerName"
|
||||||
|
@click="changeSorter(headerName)"
|
||||||
|
class="header-text"
|
||||||
|
>
|
||||||
<span class="header_wrapper">
|
<span class="header_wrapper">
|
||||||
<div v-html="$t(`sceneries.${id}`)"></div>
|
<div v-html="$t(`sceneries.${headerName}`)"></div>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
class="sort-icon"
|
class="sort-icon"
|
||||||
v-if="sorterActive.index == i"
|
v-if="sorterActive.headerName == headerName"
|
||||||
:src="sorterActive.dir == 1 ? getIcon('arrow-asc') : getIcon('arrow-desc')"
|
:src="sorterActive.dir == 1 ? getIcon('arrow-asc') : getIcon('arrow-desc')"
|
||||||
alt="sort icon"
|
alt="sort icon"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
<th v-for="(id, i) in headIconsIds" :key="id" @click="() => changeSorter(i + 7)">
|
<th
|
||||||
|
v-for="(headerName, i) in headIconsIds"
|
||||||
|
:key="headerName"
|
||||||
|
@click="changeSorter(headerName)"
|
||||||
|
class="header-image"
|
||||||
|
>
|
||||||
<span class="header_wrapper">
|
<span class="header_wrapper">
|
||||||
<img :src="getIcon(id)" :alt="id" :title="$t(`sceneries.${id}s`)" />
|
<img :src="getIcon(headerName)" :alt="headerName" :title="$t(`sceneries.${headerName}`)" />
|
||||||
|
|
||||||
<img
|
<img
|
||||||
class="sort-icon"
|
class="sort-icon"
|
||||||
v-if="sorterActive.index == i + 7"
|
v-if="sorterActive.headerName == headerName"
|
||||||
:src="sorterActive.dir == 1 ? getIcon('arrow-asc') : getIcon('arrow-desc')"
|
:src="sorterActive.dir == 1 ? getIcon('arrow-asc') : getIcon('arrow-desc')"
|
||||||
alt="sort icon"
|
alt="sort icon"
|
||||||
/>
|
/>
|
||||||
@@ -100,7 +110,10 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_dispatcher-exp">
|
<td class="station_dispatcher-exp">
|
||||||
<span v-if="station.onlineInfo" :style="calculateExpStyle(station.onlineInfo.dispatcherExp)">
|
<span
|
||||||
|
v-if="station.onlineInfo"
|
||||||
|
:style="calculateExpStyle(station.onlineInfo.dispatcherExp, station.onlineInfo.dispatcherIsSupporter)"
|
||||||
|
>
|
||||||
{{ 2 > station.onlineInfo.dispatcherExp ? 'L' : station.onlineInfo.dispatcherExp }}
|
{{ 2 > station.onlineInfo.dispatcherExp ? 'L' : station.onlineInfo.dispatcherExp }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -187,25 +200,31 @@
|
|||||||
|
|
||||||
<td class="station_users" :class="{ inactive: !station.onlineInfo }">
|
<td class="station_users" :class="{ inactive: !station.onlineInfo }">
|
||||||
<span>
|
<span>
|
||||||
<span class="highlight">{{ station.onlineInfo?.currentUsers || '0' }}</span>
|
<span class="highlight">{{ station.onlineInfo?.currentUsers || 0 }}</span>
|
||||||
/
|
/
|
||||||
<span>{{ station.onlineInfo?.maxUsers || '0' }}</span>
|
<span class="highlight">{{ station.onlineInfo?.maxUsers || 0 }}</span>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_spawns" :class="{ inactive: !station.onlineInfo }">
|
<td class="station_spawns" :class="{ inactive: !station.onlineInfo }">
|
||||||
<span class="highlight">{{ station.onlineInfo?.spawns.length || '0' }}</span>
|
<span>{{ station.onlineInfo?.spawns.length || 0 }}</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_schedules" :class="{ inactive: !station.onlineInfo }">
|
<td class="station_schedules" style="width: 30px" :class="{ inactive: !station.onlineInfo }">
|
||||||
<span>
|
<span class="highlight">
|
||||||
<span class="highlight">
|
{{ station.onlineInfo?.scheduledTrains?.length || 0 }}
|
||||||
{{ station.onlineInfo?.scheduledTrains?.length || '0' }}
|
</span>
|
||||||
</span>
|
</td>
|
||||||
/
|
|
||||||
<span style="color: #bbb">
|
<td class="station_schedules" style="width: 30px" :class="{ inactive: !station.onlineInfo }">
|
||||||
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }}
|
<span style="color: #ccc">
|
||||||
</span>
|
{{ station.onlineInfo?.scheduledTrains?.filter((train) => !train.stopInfo.confirmed).length || 0 }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station_schedules" style="width: 30px" :class="{ inactive: !station.onlineInfo }">
|
||||||
|
<span style="color: #66ff6c">
|
||||||
|
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || 0 }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -233,6 +252,7 @@ import Station from '../../scripts/interfaces/Station';
|
|||||||
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
||||||
import { useStore } from '../../store/store';
|
import { useStore } from '../../store/store';
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
|
import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationHeaderNames';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -246,8 +266,8 @@ export default defineComponent({
|
|||||||
mixins: [styleMixin, dateMixin, stationInfoMixin, returnBtnMixin, imageMixin],
|
mixins: [styleMixin, dateMixin, stationInfoMixin, returnBtnMixin, imageMixin],
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
headIds: ['station', 'min-lvl', 'status', 'dispatcher', 'dispatcher-lvl', 'routes', 'general'],
|
headIconsIds,
|
||||||
headIconsIds: ['user', 'spawn', 'timetable'],
|
headIds,
|
||||||
lastSelectedStationName: '',
|
lastSelectedStationName: '',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -288,8 +308,10 @@ export default defineComponent({
|
|||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
},
|
},
|
||||||
|
|
||||||
changeSorter(i: number) {
|
changeSorter(headerName: HeadIdsTypes) {
|
||||||
this.stationFiltersStore.changeSorter(i);
|
if (headerName == 'general' || headerName == 'routes') return;
|
||||||
|
|
||||||
|
this.stationFiltersStore.changeSorter(headerName);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -346,9 +368,15 @@ table {
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
min-width: 75px;
|
&.header-text {
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
padding: 0.5em;
|
&.header-image {
|
||||||
|
min-width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding: 0.5em 0.25em;
|
||||||
background-color: $bgCol;
|
background-color: $bgCol;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|
||||||
|
|||||||
@@ -2,18 +2,23 @@
|
|||||||
<div class="train-info" tabindex="0">
|
<div class="train-info" tabindex="0">
|
||||||
<section class="train-route">
|
<section class="train-route">
|
||||||
<div class="train_general">
|
<div class="train_general">
|
||||||
<span>
|
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
||||||
<span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span>
|
<span class="timetable-id" v-if="train.timetableData">#{{ train.timetableData.timetableId }}</span>
|
||||||
|
|
||||||
<span class="timetable_warnings">
|
<span class="timetable_warnings" v-if="train.timetableData?.TWR || train.timetableData?.SKR">
|
||||||
<span class="train-badge twr" v-if="train.timetableData?.TWR">TWR</span>
|
<span class="train-badge twr" v-if="train.timetableData?.TWR" :title="$t('general.TWR')">TWR</span>
|
||||||
<span class="train-badge skr" v-if="train.timetableData?.SKR">SKR</span>
|
<span class="train-badge skr" v-if="train.timetableData?.SKR" :title="$t('general.SKR')">SKR</span>
|
||||||
</span>
|
|
||||||
<strong v-if="train.timetableData">{{ train.timetableData.category }} </strong>
|
|
||||||
<strong>{{ train.trainNo }}</strong>
|
|
||||||
<span> | {{ train.driverName }} </span>
|
|
||||||
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<strong>
|
||||||
|
<span v-if="train.timetableData" class="text--primary">{{ train.timetableData.category }} </span>
|
||||||
|
<span class="train-number">{{ train.trainNo }}</span>
|
||||||
|
</strong>
|
||||||
|
<span>•</span>
|
||||||
|
<b class="level-badge driver" :style="calculateExpStyle(train.driverLevel, train.isSupporter)">
|
||||||
|
{{ train.driverLevel < 2 ? 'L' : `${train.driverLevel}` }}
|
||||||
|
</b>
|
||||||
|
<span>{{ train.driverName }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="timetable_route" v-if="train.timetableData">
|
<div class="timetable_route" v-if="train.timetableData">
|
||||||
@@ -36,9 +41,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="timetable_progress" style="margin-top: 0.5em" v-if="train.timetableData">
|
<div class="timetable_progress" style="margin-top: 0.5em" v-if="train.timetableData">
|
||||||
<!-- <span> </span> -->
|
|
||||||
<span class="timetable_progress-bar">
|
<span class="timetable_progress-bar">
|
||||||
<!-- {{ confirmedPercentage(train.timetableData.followingStops) }}% -->
|
|
||||||
<span class="bar-bg"></span>
|
<span class="bar-bg"></span>
|
||||||
<span
|
<span
|
||||||
class="bar-fg"
|
class="bar-fg"
|
||||||
@@ -90,6 +93,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
import trainInfoMixin from '../../mixins/trainInfoMixin';
|
import trainInfoMixin from '../../mixins/trainInfoMixin';
|
||||||
import Train from '../../scripts/interfaces/Train';
|
import Train from '../../scripts/interfaces/Train';
|
||||||
|
|
||||||
@@ -106,12 +110,13 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [trainInfoMixin, imageMixin],
|
mixins: [trainInfoMixin, imageMixin, styleMixin],
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
|
@import '../../styles/badge.scss';
|
||||||
|
|
||||||
.image-warning {
|
.image-warning {
|
||||||
height: 1em;
|
height: 1em;
|
||||||
@@ -145,7 +150,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.timetable-id {
|
.timetable-id {
|
||||||
margin-right: 0.3em;
|
|
||||||
color: #d2d2d2;
|
color: #d2d2d2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,9 +159,7 @@ export default defineComponent({
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
width: 1.25em;
|
padding: 0 0.25em;
|
||||||
height: 1.25em;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.timetable_stops {
|
.timetable_stops {
|
||||||
@@ -168,30 +170,21 @@ export default defineComponent({
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
gap: 0.25em;
|
||||||
|
margin-right: 1.5em;
|
||||||
}
|
}
|
||||||
.train-status-badges {
|
.train-status-badges {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
gap: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.train-badge {
|
.train-driver {
|
||||||
padding: 0.15em 0.35em;
|
&.supporter {
|
||||||
margin-right: 0.3em;
|
color: orange;
|
||||||
|
text-shadow: orange 0 0 5px;
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
font-size: 0.9em;
|
|
||||||
|
|
||||||
&.twr {
|
|
||||||
background-color: var(--clr-twr);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.skr {
|
|
||||||
background-color: var(--clr-skr);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.offline {
|
|
||||||
background-color: #b83b2d;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +196,8 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.timetable_warnings {
|
.timetable_warnings {
|
||||||
color: black;
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timetable_progress {
|
.timetable_progress {
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
<div class="filters-options" @keydown.esc="showOptions = false">
|
<div class="filters-options" @keydown.esc="showOptions = false">
|
||||||
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
||||||
|
|
||||||
<button class="btn--filled btn--image" @click="toggleShowOptions" ref="button">
|
<button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button">
|
||||||
<img :src="getIcon('filter2')" alt="Open filters" />
|
<img :src="getIcon('filter2')" alt="Open filters" />
|
||||||
{{ $t('options.filters') }} [F]
|
{{ $t('options.filters') }} [F]
|
||||||
|
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<transition name="options-anim">
|
<transition name="options-anim">
|
||||||
@@ -42,29 +43,34 @@
|
|||||||
|
|
||||||
<h1 class="option-title">{{ $t('options.sort-title') }}</h1>
|
<h1 class="option-title">{{ $t('options.sort-title') }}</h1>
|
||||||
<div class="options_sorters">
|
<div class="options_sorters">
|
||||||
<div v-for="opt in translatedSorterOptions">
|
<button
|
||||||
|
v-for="opt in translatedSorterOptions"
|
||||||
|
class="sort-option btn--option"
|
||||||
|
:data-selected="opt.id == sorterActive.id"
|
||||||
|
@click="onSorterChange(opt)"
|
||||||
|
>
|
||||||
|
{{ opt.value.toUpperCase() }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="option-title" v-if="trainFilterList.length != 0">{{ $t('options.filter-title') }}</h1>
|
||||||
|
|
||||||
|
<div class="options_filters">
|
||||||
|
<div v-for="section in Object.keys(TrainFilterSection)">
|
||||||
<button
|
<button
|
||||||
class="sort-option btn--option"
|
class="btn--option"
|
||||||
:data-selected="opt.id == sorterActive.id"
|
v-for="filter in trainFilterList.filter((f) => f.section == section)"
|
||||||
@click="onSorterChange(opt)"
|
:data-inactive="!filter.isActive"
|
||||||
|
@click="onFilterChange(filter)"
|
||||||
>
|
>
|
||||||
{{ opt.value.toUpperCase() }}
|
{{ $t(`options.filter-${filter.id}`) }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 class="option-title" v-if="trainFilterList.length != 0">{{ $t('options.filter-title') }}</h1>
|
<div class="filter-actions">
|
||||||
<div class="options_filters">
|
<div></div>
|
||||||
<div class="filter-option" v-for="filter in trainFilterList">
|
<button class="btn--action" @click="resetAllFilters">{{ $t('options.filter-reset') }}</button>
|
||||||
<button class="btn--option" :data-disabled="!filter.isActive" @click="onFilterChange(filter)">
|
|
||||||
{{ $t(`options.filter-${filter.id}`) }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="filter-actions">
|
|
||||||
<button class="btn--action" @click="clearAllFilters">{{ $t('options.filter-clear') }}</button>
|
|
||||||
<button class="btn--action" @click="resetAllFilters">{{ $t('options.filter-reset') }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,9 +82,10 @@
|
|||||||
import { defineComponent, inject, PropType } from 'vue';
|
import { defineComponent, inject, PropType } from 'vue';
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
import keyMixin from '../../mixins/keyMixin';
|
import keyMixin from '../../mixins/keyMixin';
|
||||||
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes';
|
|
||||||
import ActionButton from '../Global/ActionButton.vue';
|
import ActionButton from '../Global/ActionButton.vue';
|
||||||
import SelectBox from '../Global/SelectBox.vue';
|
import SelectBox from '../Global/SelectBox.vue';
|
||||||
|
import { TrainFilterSection } from '../../scripts/enums/TrainFilterType';
|
||||||
|
import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { SelectBox, ActionButton },
|
components: { SelectBox, ActionButton },
|
||||||
@@ -89,11 +96,18 @@ export default defineComponent({
|
|||||||
type: Array as PropType<Array<string>>,
|
type: Array as PropType<Array<string>>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
currentOptionsActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showOptions: false,
|
showOptions: false,
|
||||||
|
lastSelectedFilter: null as TrainFilter | null,
|
||||||
|
TrainFilterSection,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -136,7 +150,11 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onFilterChange(filter: TrainFilter) {
|
onFilterChange(filter: TrainFilter) {
|
||||||
|
// if (this.lastSelectedFilter?.id === filter.id)
|
||||||
|
// this.trainFilterList.forEach((tf) => (tf.isActive = filter.id === tf.id));
|
||||||
|
|
||||||
filter.isActive = !filter.isActive;
|
filter.isActive = !filter.isActive;
|
||||||
|
this.lastSelectedFilter = filter;
|
||||||
},
|
},
|
||||||
|
|
||||||
clearAllFilters() {
|
clearAllFilters() {
|
||||||
@@ -172,13 +190,24 @@ export default defineComponent({
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-option {
|
.options_sorters {
|
||||||
|
display: flex;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options_filters > div {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
color: white;
|
width: 100%;
|
||||||
|
color: springgreen;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
&[data-disabled='true'] {
|
&[data-inactive='true'] {
|
||||||
color: #888;
|
color: #aaa;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,7 +219,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
|
||||||
button {
|
> * {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ import dateMixin from '../../mixins/dateMixin';
|
|||||||
import imageMixin from '../../mixins/imageMixin';
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
import Train from '../../scripts/interfaces/Train';
|
import Train from '../../scripts/interfaces/Train';
|
||||||
import TrainStop from '../../scripts/interfaces/TrainStop';
|
import TrainStop from '../../scripts/interfaces/TrainStop';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
import StopDate from '../Global/StopDate.vue';
|
import StopDate from '../Global/StopDate.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -106,6 +107,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
return {
|
return {
|
||||||
|
store: useStore(),
|
||||||
|
|
||||||
lastConfirmed: computed(() => {
|
lastConfirmed: computed(() => {
|
||||||
return props.train.timetableData!.followingStops.findIndex(
|
return props.train.timetableData!.followingStops.findIndex(
|
||||||
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed && !stops[i + 1]?.stopped
|
(stop, i, stops) => stop.confirmed && !stops[i + 1]?.confirmed && !stops[i + 1]?.stopped
|
||||||
@@ -199,7 +202,6 @@ ul.stock-list {
|
|||||||
|
|
||||||
img {
|
img {
|
||||||
max-height: 60px;
|
max-height: 60px;
|
||||||
max-width: 320px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,3 +426,4 @@ ul.stop_list > li.stop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -2,18 +2,17 @@
|
|||||||
<div class="train-table">
|
<div class="train-table">
|
||||||
<transition name="anim" mode="out-in">
|
<transition name="anim" mode="out-in">
|
||||||
<div :key="store.dataStatuses.trains">
|
<div :key="store.dataStatuses.trains">
|
||||||
<Loading v-if="trains.length == 0 && store.dataStatuses.trains == 0" />
|
<div class="table-info" v-if="store.isOffline">
|
||||||
|
{{ $t('app.offline') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="table-info no-trains" v-if="trains.length == 0 && store.dataStatuses.trains != 0">
|
<Loading v-else-if="trains.length == 0 && store.dataStatuses.trains == 0" />
|
||||||
|
|
||||||
|
<div class="table-info no-trains" v-else-if="trains.length == 0 && store.dataStatuses.trains != 0">
|
||||||
{{ $t('trains.no-trains') }}
|
{{ $t('trains.no-trains') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="timeouts-warning" v-if="trainNumbersWithTimeouts.length != 0">
|
<transition-group name="list-anim" tag="ul" class="train-list" v-else>
|
||||||
<b class="warning-timeout">?</b>
|
|
||||||
{{ $t('trains.timeout') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="train-list">
|
|
||||||
<li
|
<li
|
||||||
class="train-row"
|
class="train-row"
|
||||||
v-for="train in currentTrains"
|
v-for="train in currentTrains"
|
||||||
@@ -23,7 +22,7 @@
|
|||||||
>
|
>
|
||||||
<TrainInfo :train="train" />
|
<TrainInfo :train="train" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,18 +66,9 @@ export default defineComponent({
|
|||||||
id: string | number;
|
id: string | number;
|
||||||
dir: number;
|
dir: number;
|
||||||
},
|
},
|
||||||
distanceLimitExceeded: computed(
|
|
||||||
() => props.trains.findIndex(({ timetableData }) => timetableData && timetableData.routeDistance > 200) != -1
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
trainNumbersWithTimeouts() {
|
|
||||||
return this.store.trainList.filter((train) => train.isTimeout).map((train) => train.trainNo);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
activated() {
|
activated() {
|
||||||
const query = this.$route.query;
|
const query = this.$route.query;
|
||||||
if (query.trainNo && query.driverName) {
|
if (query.trainNo && query.driverName) {
|
||||||
@@ -94,6 +84,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
|
@import '../../styles/animations.scss';
|
||||||
|
|
||||||
.anim {
|
.anim {
|
||||||
&-enter-from,
|
&-enter-from,
|
||||||
@@ -154,7 +145,7 @@ img.train-image {
|
|||||||
|
|
||||||
.train {
|
.train {
|
||||||
&-list {
|
&-list {
|
||||||
overflow: auto;
|
position: relative;
|
||||||
|
|
||||||
@include smallScreen() {
|
@include smallScreen() {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,28 +1,46 @@
|
|||||||
import { JournalFilterType } from "../../scripts/enums/JournalFilterType";
|
import { JournalFilterSection, JournalFilterType } from '../../scripts/enums/JournalFilterType';
|
||||||
import { JournalTimetableFilter } from "../../types/Journal/JournalTimetablesTypes";
|
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
|
||||||
|
|
||||||
export const journalTimetableFilters: JournalTimetableFilter[] = [
|
export const journalTimetableFilters: JournalFilter[] = [
|
||||||
{
|
{
|
||||||
id: JournalFilterType.all,
|
id: JournalFilterType.ALL,
|
||||||
filterSection: 'timetable-status',
|
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: JournalFilterType.active,
|
id: JournalFilterType.ACTIVE,
|
||||||
filterSection: 'timetable-status',
|
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||||
isActive: false,
|
isActive: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: JournalFilterType.fulfilled,
|
id: JournalFilterType.FULFILLED,
|
||||||
filterSection: 'timetable-status',
|
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||||
isActive: false,
|
isActive: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: JournalFilterType.abandoned,
|
id: JournalFilterType.ABANDONED,
|
||||||
filterSection: 'timetable-status',
|
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.TWR_SKR,
|
||||||
|
filterSection: JournalFilterSection.TWRSKR,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.TWR,
|
||||||
|
filterSection: JournalFilterSection.TWRSKR,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.SKR,
|
||||||
|
filterSection: JournalFilterSection.TWRSKR,
|
||||||
isActive: false,
|
isActive: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,33 +1,58 @@
|
|||||||
import { TrainFilterType } from '../../scripts/enums/TrainFilterType';
|
import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType';
|
||||||
import { TrainFilter } from '../../types/Trains/TrainOptionsTypes';
|
import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter';
|
||||||
|
|
||||||
export const trainFilters: TrainFilter[] = [
|
export const trainFilters: TrainFilter[] = [
|
||||||
{
|
{
|
||||||
id: TrainFilterType.twr,
|
id: TrainFilterType.twr,
|
||||||
|
section: TrainFilterSection.TRAIN_TYPE,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: TrainFilterType.skr,
|
id: TrainFilterType.skr,
|
||||||
|
section: TrainFilterSection.TRAIN_TYPE,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: TrainFilterType.common,
|
||||||
|
section: TrainFilterSection.TRAIN_TYPE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: TrainFilterType.passenger,
|
id: TrainFilterType.passenger,
|
||||||
|
section: TrainFilterSection.TIMETABLE_TYPE,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: TrainFilterType.freight,
|
id: TrainFilterType.freight,
|
||||||
|
section: TrainFilterSection.TIMETABLE_TYPE,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: TrainFilterType.other,
|
id: TrainFilterType.other,
|
||||||
|
section: TrainFilterSection.TIMETABLE_TYPE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: TrainFilterType.withComments,
|
||||||
|
section: TrainFilterSection.COMMENTS,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: TrainFilterType.comments,
|
id: TrainFilterType.noComments,
|
||||||
|
section: TrainFilterSection.COMMENTS,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: TrainFilterType.withTimetable,
|
||||||
|
section: TrainFilterSection.TIMETABLE,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: TrainFilterType.noTimetable,
|
id: TrainFilterType.noTimetable,
|
||||||
|
section: TrainFilterSection.TIMETABLE,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -37,6 +62,10 @@ export const sorterOptions = [
|
|||||||
id: 'distance',
|
id: 'distance',
|
||||||
value: 'kilometraż',
|
value: 'kilometraż',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'id',
|
||||||
|
value: 'id rozkładu',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'progress',
|
id: 'progress',
|
||||||
value: 'przebyta trasa',
|
value: 'przebyta trasa',
|
||||||
|
|||||||
@@ -1,41 +1,38 @@
|
|||||||
{
|
{
|
||||||
|
"optionSections": ["reality", "package-access", "access", "control", "addons", "blockades", "signals", "status"],
|
||||||
|
|
||||||
"options": [
|
"options": [
|
||||||
{
|
|
||||||
"id": "default",
|
|
||||||
"name": "default",
|
|
||||||
"iconName": "td2",
|
|
||||||
"section": "access",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "not-default",
|
|
||||||
"name": "notDefault",
|
|
||||||
"iconName": "",
|
|
||||||
"section": "access",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "real",
|
"id": "real",
|
||||||
"name": "real",
|
"name": "real",
|
||||||
"iconName": "lock",
|
"section": "reality",
|
||||||
"section": "access",
|
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "fictional",
|
"id": "fictional",
|
||||||
"name": "fictional",
|
"name": "fictional",
|
||||||
"iconName": "user",
|
"section": "reality",
|
||||||
"section": "access",
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "default",
|
||||||
|
"name": "default",
|
||||||
|
"section": "package-access",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "not-default",
|
||||||
|
"name": "notDefault",
|
||||||
|
"section": "package-access",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "non-public",
|
"id": "non-public",
|
||||||
"name": "nonPublic",
|
"name": "nonPublic",
|
||||||
"iconName": "user",
|
|
||||||
"section": "access",
|
"section": "access",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -43,7 +40,6 @@
|
|||||||
{
|
{
|
||||||
"id": "unavailable",
|
"id": "unavailable",
|
||||||
"name": "unavailable",
|
"name": "unavailable",
|
||||||
"iconName": "user",
|
|
||||||
"section": "access",
|
"section": "access",
|
||||||
"value": false,
|
"value": false,
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
@@ -51,7 +47,6 @@
|
|||||||
{
|
{
|
||||||
"id": "abandoned",
|
"id": "abandoned",
|
||||||
"name": "abandoned",
|
"name": "abandoned",
|
||||||
"iconName": "user",
|
|
||||||
"section": "access",
|
"section": "access",
|
||||||
"value": false,
|
"value": false,
|
||||||
"defaultValue": false
|
"defaultValue": false
|
||||||
@@ -59,7 +54,6 @@
|
|||||||
{
|
{
|
||||||
"id": "SPK",
|
"id": "SPK",
|
||||||
"name": "SPK",
|
"name": "SPK",
|
||||||
"iconName": "SPK",
|
|
||||||
"section": "control",
|
"section": "control",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -67,7 +61,6 @@
|
|||||||
{
|
{
|
||||||
"id": "SCS",
|
"id": "SCS",
|
||||||
"name": "SCS",
|
"name": "SCS",
|
||||||
"iconName": "SCS",
|
|
||||||
"section": "control",
|
"section": "control",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -75,15 +68,21 @@
|
|||||||
{
|
{
|
||||||
"id": "SPE",
|
"id": "SPE",
|
||||||
"name": "SPE",
|
"name": "SPE",
|
||||||
"iconName": "SPE",
|
"section": "control",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "SPK-M",
|
||||||
|
"name": "mechaniczne+SPK",
|
||||||
"section": "control",
|
"section": "control",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "manual",
|
"id": "SCS-M",
|
||||||
"name": "ręczne",
|
"name": "mechaniczne+SCS",
|
||||||
"iconName": "ręczne",
|
|
||||||
"section": "control",
|
"section": "control",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -91,7 +90,27 @@
|
|||||||
{
|
{
|
||||||
"id": "mechanical",
|
"id": "mechanical",
|
||||||
"name": "mechaniczne",
|
"name": "mechaniczne",
|
||||||
"iconName": "mechaniczne",
|
"section": "control",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "SPK-R",
|
||||||
|
"name": "ręczne+SPK",
|
||||||
|
"section": "control",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "SCS-R",
|
||||||
|
"name": "ręczne+SCS",
|
||||||
|
"section": "control",
|
||||||
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "manual",
|
||||||
|
"name": "ręczne",
|
||||||
"section": "control",
|
"section": "control",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -99,23 +118,34 @@
|
|||||||
{
|
{
|
||||||
"id": "SUP",
|
"id": "SUP",
|
||||||
"name": "SUP",
|
"name": "SUP",
|
||||||
"iconName": "SUP",
|
"section": "addons",
|
||||||
"section": "control",
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "noSUP",
|
||||||
|
"name": "noSUP",
|
||||||
|
"section": "addons",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "SBL",
|
"id": "SBL",
|
||||||
"name": "SBL",
|
"name": "SBL",
|
||||||
"iconName": "SBL",
|
"section": "blockades",
|
||||||
"section": "routes",
|
"value": true,
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "PBL",
|
||||||
|
"name": "PBL",
|
||||||
|
"section": "blockades",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "modern",
|
"id": "modern",
|
||||||
"name": "współczesna",
|
"name": "współczesna",
|
||||||
"iconName": "współczesna",
|
|
||||||
"section": "signals",
|
"section": "signals",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -123,7 +153,6 @@
|
|||||||
{
|
{
|
||||||
"id": "semaphores",
|
"id": "semaphores",
|
||||||
"name": "kształtowa",
|
"name": "kształtowa",
|
||||||
"iconName": "kształtowa",
|
|
||||||
"section": "signals",
|
"section": "signals",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -131,7 +160,6 @@
|
|||||||
{
|
{
|
||||||
"id": "mixed",
|
"id": "mixed",
|
||||||
"name": "mieszana",
|
"name": "mieszana",
|
||||||
"iconName": "mieszana",
|
|
||||||
"section": "signals",
|
"section": "signals",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -139,7 +167,6 @@
|
|||||||
{
|
{
|
||||||
"id": "historical",
|
"id": "historical",
|
||||||
"name": "historyczna",
|
"name": "historyczna",
|
||||||
"iconName": "historyczna",
|
|
||||||
"section": "signals",
|
"section": "signals",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -148,7 +175,6 @@
|
|||||||
{
|
{
|
||||||
"id": "free",
|
"id": "free",
|
||||||
"name": "free",
|
"name": "free",
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "status",
|
"section": "status",
|
||||||
"value": false,
|
"value": false,
|
||||||
@@ -157,7 +183,6 @@
|
|||||||
{
|
{
|
||||||
"id": "occupied",
|
"id": "occupied",
|
||||||
"name": "occupied",
|
"name": "occupied",
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "status",
|
"section": "status",
|
||||||
"value": true,
|
"value": true,
|
||||||
@@ -166,7 +191,6 @@
|
|||||||
{
|
{
|
||||||
"id": "endingStatus",
|
"id": "endingStatus",
|
||||||
"name": "endingStatus",
|
"name": "endingStatus",
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "status",
|
"section": "status",
|
||||||
"value": true,
|
"value": true,
|
||||||
@@ -175,7 +199,6 @@
|
|||||||
{
|
{
|
||||||
"id": "afkStatus",
|
"id": "afkStatus",
|
||||||
"name": "afkStatus",
|
"name": "afkStatus",
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "status",
|
"section": "status",
|
||||||
"value": true,
|
"value": true,
|
||||||
@@ -184,7 +207,6 @@
|
|||||||
{
|
{
|
||||||
"id": "noSpaceStatus",
|
"id": "noSpaceStatus",
|
||||||
"name": "noSpaceStatus",
|
"name": "noSpaceStatus",
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "status",
|
"section": "status",
|
||||||
"value": true,
|
"value": true,
|
||||||
@@ -193,7 +215,6 @@
|
|||||||
{
|
{
|
||||||
"id": "unavailableStatus",
|
"id": "unavailableStatus",
|
||||||
"name": "unavailableStatus",
|
"name": "unavailableStatus",
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "status",
|
"section": "status",
|
||||||
"value": true,
|
"value": true,
|
||||||
@@ -254,7 +275,6 @@
|
|||||||
{
|
{
|
||||||
"id": "include-selected",
|
"id": "include-selected",
|
||||||
"name": "include-selected",
|
"name": "include-selected",
|
||||||
"iconName": "",
|
|
||||||
"section": "mode",
|
"section": "mode",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
@@ -262,7 +282,6 @@
|
|||||||
{
|
{
|
||||||
"id": "save",
|
"id": "save",
|
||||||
"name": "save",
|
"name": "save",
|
||||||
"iconName": "",
|
|
||||||
"section": "mode",
|
"section": "mode",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"general": {
|
||||||
|
"and": " and ",
|
||||||
|
"refresh": "REFRESH",
|
||||||
|
"TWR": "High risk freight train",
|
||||||
|
"SKR": "Train with exceeded gauge"
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"sceneries": "SCENERIES",
|
"sceneries": "SCENERIES",
|
||||||
"trains": "TRAINS",
|
"trains": "TRAINS",
|
||||||
@@ -8,15 +14,21 @@
|
|||||||
"error": "An error occured while loading data!",
|
"error": "An error occured while loading data!",
|
||||||
"no-result": "No results for current search!",
|
"no-result": "No results for current search!",
|
||||||
"migration-warning": "Stacjownik services will be unavailable 2/06/2022 between 1-3am (CEST time) due to the migration of API hostings!",
|
"migration-warning": "Stacjownik services will be unavailable 2/06/2022 between 1-3am (CEST time) due to the migration of API hostings!",
|
||||||
"migration-confirm": "Roger that!"
|
"migration-confirm": "Roger that!",
|
||||||
|
"offline": "App is in the offline mode!"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"discord": "Stacjownik Discord server"
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"title": "New Stacjownik version is available!",
|
"title": "New version of the app is available!",
|
||||||
"paragraph1": "Enjoy the application and may the green signal be with you!",
|
"paragraph1": "Enjoy the application and may the green signal be with you!",
|
||||||
"release-link": "Click here to browse version changelog (GitHub)",
|
"release-link": "Click here to browse version changelog (GitHub)",
|
||||||
"confirm-button": "Understood!"
|
"confirm-button": "UPDATE NOW",
|
||||||
|
"later-button": "LATER"
|
||||||
},
|
},
|
||||||
"data-status": {
|
"data-status": {
|
||||||
|
"S1-offline": "<b>S1 signal</b> <br> The app is working in offline mode!",
|
||||||
"S1a-connection": "<b>S1a signal</b> <br> Cannot connect with Stacjownik API service!",
|
"S1a-connection": "<b>S1a signal</b> <br> Cannot connect with Stacjownik API service!",
|
||||||
"S1a-sceneries": "<b>S1a signal</b> <br> Cannot load online stations data!",
|
"S1a-sceneries": "<b>S1a signal</b> <br> Cannot load online stations data!",
|
||||||
"S2": "<b>S2 signal</b> <br> All data loaded successfully!",
|
"S2": "<b>S2 signal</b> <br> All data loaded successfully!",
|
||||||
@@ -28,7 +40,7 @@
|
|||||||
"desc": {
|
"desc": {
|
||||||
"control-type": "Control type: ",
|
"control-type": "Control type: ",
|
||||||
"signals-type": "Signals type: ",
|
"signals-type": "Signals type: ",
|
||||||
"SBL": "This scenery has automatic line blockade system on following routes: ",
|
"SBL": "This scenery has automatic block signalling (ABS/SBL) system on following routes: ",
|
||||||
"SUP": "Requires the SUP application (level crossing remote control simulator)",
|
"SUP": "Requires the SUP application (level crossing remote control simulator)",
|
||||||
"TWB-all": "This scenery has two-way route blockade on all routes",
|
"TWB-all": "This scenery has two-way route blockade on all routes",
|
||||||
"TWB-routes": "This scenery has two-way route blockade on following routes: ",
|
"TWB-routes": "This scenery has two-way route blockade on following routes: ",
|
||||||
@@ -87,47 +99,71 @@
|
|||||||
"search-dispatcher": "Dispatcher name",
|
"search-dispatcher": "Dispatcher name",
|
||||||
"search-station": "Scenery name",
|
"search-station": "Scenery name",
|
||||||
"search-author": "Timetable author name",
|
"search-author": "Timetable author name",
|
||||||
"search-date": "Timetable date (CEST / GMT+2)",
|
"search-issuedFrom": "Origin scenery name",
|
||||||
|
"search-timetables-date": "Timetable date (UTC+2 / CEST)",
|
||||||
|
"search-dispatchers-date": "Service date (UTC+2 / CEST)",
|
||||||
|
"search-date": "Date (UTC+2 / CEST)",
|
||||||
|
|
||||||
"sort-mass": "mass",
|
"sort-mass": "mass",
|
||||||
"sort-speed": "speed",
|
"sort-speed": "speed",
|
||||||
"sort-length": "length",
|
"sort-length": "length",
|
||||||
"sort-distance": "distance",
|
"sort-routeDistance": "route distance",
|
||||||
"sort-timetable": "train no.",
|
"sort-timetable": "train no.",
|
||||||
"sort-progress": "route progress",
|
"sort-progress": "route progress",
|
||||||
"sort-delay": "current delay",
|
"sort-delay": "current delay",
|
||||||
|
"sort-id": "timetable id",
|
||||||
|
|
||||||
"sort-total-stops": "total stops",
|
"sort-allStopsCount": "total stops",
|
||||||
"sort-beginDate": "date",
|
"sort-beginDate": "date",
|
||||||
"sort-timetableId": "timetable ID",
|
"sort-timetableId": "timetable ID",
|
||||||
"sort-timestampFrom": "date",
|
"sort-timestampFrom": "date",
|
||||||
"sort-duration": "duration",
|
"sort-duration": "duration",
|
||||||
|
|
||||||
"filter-comments": "COMMENTS",
|
"filter-noComments": "NO COMMENTS",
|
||||||
"filter-twr": "TWR",
|
"filter-withComments": "COMMENTS",
|
||||||
"filter-skr": "SKR",
|
"filter-twr": "HIGH RISK CARGO",
|
||||||
|
"filter-skr": "EXCEEDED GAUGE",
|
||||||
|
"filter-twr-skr": "ALL TYPES",
|
||||||
|
"filter-common": "NO WARNINGS",
|
||||||
"filter-passenger": "PASSENGER",
|
"filter-passenger": "PASSENGER",
|
||||||
"filter-freight": "FREIGHT",
|
"filter-freight": "FREIGHT",
|
||||||
"filter-other": "OTHER",
|
"filter-other": "OTHER",
|
||||||
"filter-noTimetable": "NO TIMETABLE",
|
"filter-noTimetable": "NO TIMETABLE",
|
||||||
|
"filter-withTimetable": "TIMETABLE",
|
||||||
|
|
||||||
"filter-reset": "RESET FILTERS",
|
"filter-reset": "RESET FILTERS",
|
||||||
"filter-clear": "CLEAR FILTERS",
|
"filter-clear": "CLEAR FILTERS",
|
||||||
|
|
||||||
|
"filter-section-timetable-status": "TIMETABLE STATUS",
|
||||||
|
"filter-section-twrskr": "WARNINGS",
|
||||||
|
|
||||||
"filter-all": "ALL ENTRIES",
|
"filter-all": "ALL ENTRIES",
|
||||||
"filter-abandoned": "ABANDONED",
|
"filter-abandoned": "ABANDONED",
|
||||||
"filter-fulfilled": "FULFILLED",
|
"filter-fulfilled": "FULFILLED",
|
||||||
"filter-active": "ACTIVE"
|
"filter-active": "ACTIVE"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
|
"desc": " • Left mouse click: select / unselect chosen filter <br /> • Double left click: unselect all filters but chosen from a <b class='text--primary'>group</b> <br /> • <span style='color: coral'>RESET</span>: reset all filters from a <b class='text--primary'>group</b>",
|
||||||
|
|
||||||
|
"sections": {
|
||||||
|
"reality": "SCENERY REALITY",
|
||||||
|
"package-access": "IN-GAME AVAILABILITY",
|
||||||
|
"access": "GENERAL AVAILABILITY",
|
||||||
|
"control": "CONTROLS",
|
||||||
|
"signals": "SIGNALLING",
|
||||||
|
"addons": "ADDITIONAL PROGRAMS",
|
||||||
|
"blockades": "BLOCK SIGNALLING",
|
||||||
|
"status": "ONLINE STATUS"
|
||||||
|
},
|
||||||
|
|
||||||
"endingStatus": "ENDS SOON",
|
"endingStatus": "ENDS SOON",
|
||||||
"afkStatus": "AFK",
|
"afkStatus": "AFK",
|
||||||
"noSpaceStatus": "NO SPACE",
|
"noSpaceStatus": "NO SPACE",
|
||||||
"unavailableStatus": "UNAVAILABLE",
|
"unavailableStatus": "UNAVAILABLE",
|
||||||
|
|
||||||
"title": "STATION FILTER",
|
"title": "STATION FILTERS",
|
||||||
"default": "DEFAULT",
|
"default": "IN-GAME",
|
||||||
"not-default": "OTHER",
|
"not-default": "ADDITIONAL",
|
||||||
"real": "REAL",
|
"real": "REAL",
|
||||||
"fictional": "FICTIONAL",
|
"fictional": "FICTIONAL",
|
||||||
"unavailable": "UNSUPPORTED",
|
"unavailable": "UNSUPPORTED",
|
||||||
@@ -135,12 +171,22 @@
|
|||||||
"abandoned": "ABANDONED",
|
"abandoned": "ABANDONED",
|
||||||
|
|
||||||
"SPK": "SPK",
|
"SPK": "SPK",
|
||||||
|
"SPK-R": "SPK + MANUAL",
|
||||||
|
"SPK-M": "SPK + MECH.",
|
||||||
"SCS": "SCS",
|
"SCS": "SCS",
|
||||||
|
"SCS-R": "SCS + MANUAL",
|
||||||
|
"SCS-M": "SCS + MECH.",
|
||||||
"SPE": "SPE",
|
"SPE": "SPE",
|
||||||
|
|
||||||
"manual": "MANUAL",
|
"manual": "MANUAL",
|
||||||
"mechanical": "MECHANICAL",
|
"mechanical": "MECHANICAL",
|
||||||
"SUP": "SUP",
|
|
||||||
"SBL": "SBL",
|
"SUP": "SUP (RASP-UZK)",
|
||||||
|
"noSUP": "WITHOUT SUP",
|
||||||
|
|
||||||
|
"SBL": "AUTOMATIC (SBL)",
|
||||||
|
"PBL": "SEMIAUTOMATIC (PBL)",
|
||||||
|
|
||||||
"modern": "MODERN",
|
"modern": "MODERN",
|
||||||
"semaphores": "SEMAPHORES",
|
"semaphores": "SEMAPHORES",
|
||||||
"mixed": "MIXED",
|
"mixed": "MIXED",
|
||||||
@@ -161,7 +207,7 @@
|
|||||||
"hour": "h",
|
"hour": "h",
|
||||||
"no-limit": "NO LIMIT",
|
"no-limit": "NO LIMIT",
|
||||||
"include-selected": "INCLUDE SELECTED",
|
"include-selected": "INCLUDE SELECTED",
|
||||||
"save": "SAVE FILTERS",
|
"save": "REMEMBER FILTERS",
|
||||||
"reset": "RESET FILTERS",
|
"reset": "RESET FILTERS",
|
||||||
"close": "CLOSE FILTERS"
|
"close": "CLOSE FILTERS"
|
||||||
},
|
},
|
||||||
@@ -173,9 +219,11 @@
|
|||||||
"dispatcher-lvl": "Dispatcher\nlevel",
|
"dispatcher-lvl": "Dispatcher\nlevel",
|
||||||
"routes": "Routes\ndouble / single",
|
"routes": "Routes\ndouble / single",
|
||||||
"general": "General info",
|
"general": "General info",
|
||||||
"users": "Drivers online",
|
"user": "Drivers online",
|
||||||
"spawns": "Spawns online",
|
"spawn": "Spawns online",
|
||||||
"timetables": "Active timetables",
|
"timetableAll": "Active timetables",
|
||||||
|
"timetableConfirmed": "Confirmed timetables",
|
||||||
|
"timetableUnconfirmed": "Unconfirmed timetables",
|
||||||
"no-stations": "No stations to show here!",
|
"no-stations": "No stations to show here!",
|
||||||
"scenery-search": "Search for scenery..."
|
"scenery-search": "Search for scenery..."
|
||||||
},
|
},
|
||||||
@@ -246,20 +294,39 @@
|
|||||||
"minutes": "{minutes} mins",
|
"minutes": "{minutes} mins",
|
||||||
"hours": "{hours}h {minutes} mins",
|
"hours": "{hours}h {minutes} mins",
|
||||||
|
|
||||||
"stock-info": "STOCK INFO",
|
"stock-info": "EXTRA INFO",
|
||||||
"stock-length": "Length",
|
"stock-length": "Length",
|
||||||
"stock-mass": "Mass",
|
"stock-mass": "Mass",
|
||||||
"stock-max-speed": "Maximum registered speed",
|
"stock-max-speed": "Max. speed",
|
||||||
|
|
||||||
"load-data": "Load further data...",
|
"load-data": "Load further data...",
|
||||||
|
|
||||||
|
"last-seen-at": "Last seen at",
|
||||||
|
"currently-at": "Currently at",
|
||||||
|
|
||||||
"stats-title": "DRIVING STATISTICS OF",
|
"stats-title": "DRIVING STATISTICS OF",
|
||||||
|
|
||||||
"stats-timetables": "TIMETABLES",
|
"stats-timetables": "TIMETABLES",
|
||||||
"stats-longest-timetable": "LONGEST TIMETABLE",
|
"stats-longest-timetable": "LONGEST TIMETABLE",
|
||||||
"stats-avg-timetable": "AVERAGE TIMETABLE LENGTH",
|
"stats-avg-timetable": "AVERAGE TIMETABLE LENGTH",
|
||||||
"stats-distance": "DISTANCE",
|
"stats-distance": "DISTANCE",
|
||||||
"stats-stations": "STATIONS"
|
"stats-stations": "STATIONS",
|
||||||
|
|
||||||
|
"timetable-stats-total": "Today, dispatchers made so far {count} with total distance of {distance}",
|
||||||
|
"timetable-stats-longest": "The longest timetable today is #{id} made by {author} for {driver} - {distance}",
|
||||||
|
"timetable-stats-most-active": "The most active dispatcher today is {dispatcher} who created {count}",
|
||||||
|
"timetable-stats-most-active-many": "The most active dispatchers today are {dispatchers} who created {count} each",
|
||||||
|
|
||||||
|
"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!",
|
||||||
|
|
||||||
|
"stats-loading": "Fetching statistics...",
|
||||||
|
"stats-error": "Oops! An unexpected error occurred while trying to fetch statistics! :/"
|
||||||
},
|
},
|
||||||
"scenery": {
|
"scenery": {
|
||||||
"users": "PLAYERS ONLINE",
|
"users": "PLAYERS ONLINE",
|
||||||
@@ -274,22 +341,33 @@
|
|||||||
"history-btn": "View the dispatcher history",
|
"history-btn": "View the dispatcher history",
|
||||||
"info-btn": "Return to the scenery view",
|
"info-btn": "Return to the scenery view",
|
||||||
"authors-title": "Scenery author | Scenery authors",
|
"authors-title": "Scenery author | Scenery authors",
|
||||||
|
"abbrev": "Station symbol:",
|
||||||
"lines-title": "Real lines",
|
"lines-title": "Real lines",
|
||||||
"project-title": "Project name",
|
"project-title": "Project name",
|
||||||
"one-way-routes": "One way routes",
|
"one-way-routes": "One way routes",
|
||||||
"two-way-routes": "Two way routes",
|
"two-way-routes": "Two way routes",
|
||||||
|
|
||||||
"option-active-timetables": "Active timetables",
|
"option-active-timetables": "Active timetables",
|
||||||
"option-timetables-history": "Scenery timetables history",
|
"option-timetables-history": "Timetables history",
|
||||||
"option-dispatchers-history": "Scenery dispatchers history",
|
"option-dispatchers-history": "Dispatchers history",
|
||||||
|
|
||||||
"timetable-author-title": "Issued by",
|
"timetable-author-title": "Issued by",
|
||||||
"timetable-author-unknown": "Author unknown",
|
"timetable-author-unknown": "Author unknown",
|
||||||
|
|
||||||
|
"timetables-history-id": "ID",
|
||||||
|
"timetables-history-number": "Number",
|
||||||
|
"timetables-history-route": "Route",
|
||||||
|
"timetables-history-driver": "Driver",
|
||||||
|
"timetables-history-author": "TT author",
|
||||||
|
"timetables-history-date": "Date",
|
||||||
|
|
||||||
"req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required",
|
"req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required",
|
||||||
"history-list-empty": "No recorded scenery history!",
|
"history-list-empty": "No recorded scenery history!",
|
||||||
|
|
||||||
"forum-topic": "Official {name} forum topic"
|
"forum-topic": "Official {name} forum topic",
|
||||||
|
|
||||||
|
"pragotron-link": "Timetable pallet board (beta)",
|
||||||
|
"tablice-link": "Timetable summary board (by Thundo)"
|
||||||
},
|
},
|
||||||
"availability": {
|
"availability": {
|
||||||
"title": "Availability",
|
"title": "Availability",
|
||||||
@@ -304,7 +382,19 @@
|
|||||||
"end": "Timetable terminates here",
|
"end": "Timetable terminates here",
|
||||||
"terminated": "Timetable terminated",
|
"terminated": "Timetable terminated",
|
||||||
"begins": "BEGINS HERE",
|
"begins": "BEGINS HERE",
|
||||||
"terminates": "TERMINATES\nHERE"
|
"terminates": "TERMINATES\nHERE",
|
||||||
|
|
||||||
|
"from": "FROM",
|
||||||
|
"to": "TO",
|
||||||
|
|
||||||
|
"desc-arriving": "The train is not here yet. It's going to come from: {prevStationName} (szlak {prevDepartureLine})",
|
||||||
|
"desc-online": "The train is at the station. It's going to leave to: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-stopped": "The train is at the station and is stopped. It's going to leave towards: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-next-arrival": "Leaves towards: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-departed": "The train is at the station and it's been departed. Leaves towards: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-departed-away": "The train has been departed to: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-end": "The train terminates here",
|
||||||
|
"desc-terminated": "The train has been terminated"
|
||||||
},
|
},
|
||||||
"history": {
|
"history": {
|
||||||
"title": "TIMETABLE JOURNAL",
|
"title": "TIMETABLE JOURNAL",
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
|
"general": {
|
||||||
|
"and": " oraz ",
|
||||||
|
"refresh": "ODŚWIEŻ",
|
||||||
|
"TWR": "Towar niebezpieczny wysokiego ryzyka",
|
||||||
|
"SKR": "Przekroczona skrajnia"
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"sceneries": "SCENERIE",
|
"sceneries": "SCENERIE",
|
||||||
"trains": "POCIĄGI",
|
"trains": "POCIĄGI",
|
||||||
@@ -8,17 +14,21 @@
|
|||||||
"error": "Wystąpił problem z załadowaniem danych!",
|
"error": "Wystąpił problem z załadowaniem danych!",
|
||||||
"no-result": "Brak wyników o podanych kryteriach!",
|
"no-result": "Brak wyników o podanych kryteriach!",
|
||||||
"migration-warning": "Usługi Stacjownika będą niedostępne w godzinach 1:00-3:00 2 czerwca 2022r. z powodu migracji hostingów API!",
|
"migration-warning": "Usługi Stacjownika będą niedostępne w godzinach 1:00-3:00 2 czerwca 2022r. z powodu migracji hostingów API!",
|
||||||
"migration-confirm": "Przyjąłem!"
|
"migration-confirm": "Przyjąłem!",
|
||||||
|
"offline": "Aplikacja w trybie offline!"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"discord": "Serwer Discord Stacjownika"
|
||||||
},
|
},
|
||||||
|
|
||||||
"update": {
|
"update": {
|
||||||
"title": "Nowa wersja Stacjownika jest dostępna!",
|
"title": "Nowa wersja Stacjownika jest dostępna!",
|
||||||
"paragraph1": "Miłego korzystania z aplikacji i niech S2 będzie z wami!",
|
"paragraph1": "Miłego korzystania z aplikacji i niech S2 będzie z wami!",
|
||||||
"release-link": "Kliknij, aby przejrzeć listę zmian (GitHub)",
|
"release-link": "Kliknij, aby przejrzeć listę zmian (GitHub)",
|
||||||
"confirm-button": "Przyjąłem!"
|
"confirm-button": "ZAKTUALIZUJ",
|
||||||
|
"later-button": "PÓŹNIEJ"
|
||||||
},
|
},
|
||||||
|
|
||||||
"data-status": {
|
"data-status": {
|
||||||
|
"S1-offline": "<b>Sygnał S1</b> <br> Aplikacja działa w trybie offline!",
|
||||||
"S1a-connection": "<b>Sygnał S1a</b> <br> Błąd podczas próby połączenia się z API Stacjownika!",
|
"S1a-connection": "<b>Sygnał S1a</b> <br> Błąd podczas próby połączenia się z API Stacjownika!",
|
||||||
"S1a-sceneries": "<b>Sygnał S1a</b> <br> Błąd podczas pobierania danych o sceneriach online!",
|
"S1a-sceneries": "<b>Sygnał S1a</b> <br> Błąd podczas pobierania danych o sceneriach online!",
|
||||||
"S2": "<b>Sygnał S2</b> <br> Pomyślnie załadowano dane!",
|
"S2": "<b>Sygnał S2</b> <br> Pomyślnie załadowano dane!",
|
||||||
@@ -89,14 +99,18 @@
|
|||||||
"search-dispatcher": "Nick dyżurnego",
|
"search-dispatcher": "Nick dyżurnego",
|
||||||
"search-station": "Nazwa scenerii",
|
"search-station": "Nazwa scenerii",
|
||||||
"search-author": "Nick autora rozkładu jazdy",
|
"search-author": "Nick autora rozkładu jazdy",
|
||||||
"search-date": "Data rozkładu jazdy (czas polski)",
|
"search-issuedFrom": "Sceneria początkowa",
|
||||||
|
"search-timetables-date": "Data rozkładu jazdy (UTC+2 / CEST)",
|
||||||
|
"search-dispatchers-date": "Data służby (UTC+2 / CEST)",
|
||||||
|
"search-date": "Data (UTC+2 / CEST)",
|
||||||
|
|
||||||
"sort-distance": "kilometraż",
|
"sort-routeDistance": "kilometraż",
|
||||||
"sort-total-stops": "stacje",
|
"sort-allStopsCount": "stacje",
|
||||||
"sort-beginDate": "data",
|
"sort-beginDate": "data",
|
||||||
"sort-timetableId": "ID rozkładu",
|
"sort-timetableId": "ID rozkładu",
|
||||||
"sort-timestampFrom": "data",
|
"sort-timestampFrom": "data",
|
||||||
"sort-duration": "czas dyżuru",
|
"sort-duration": "czas dyżuru",
|
||||||
|
"sort-id": "id rozkładu",
|
||||||
|
|
||||||
"sort-mass": "masa",
|
"sort-mass": "masa",
|
||||||
"sort-speed": "prędkość",
|
"sort-speed": "prędkość",
|
||||||
@@ -106,23 +120,43 @@
|
|||||||
"sort-delay": "opóźnienie",
|
"sort-delay": "opóźnienie",
|
||||||
"sort-comments": "uwagi ekspl.",
|
"sort-comments": "uwagi ekspl.",
|
||||||
|
|
||||||
"filter-comments": "UWAGI EKSPLOATACYJNE",
|
"filter-withComments": "UWAGI EKSPLOATACYJNE",
|
||||||
"filter-twr": "TWR",
|
"filter-noComments": "BEZ UWAG",
|
||||||
"filter-skr": "PRZEKR. SKRAJNIA",
|
"filter-twr": "WYS. RYZYKA",
|
||||||
|
"filter-skr": "SKRAJNIA",
|
||||||
|
"filter-twr-skr": "WSZYSTKIE",
|
||||||
|
"filter-common": "ZWYKŁE",
|
||||||
"filter-passenger": "PASAŻERSKIE",
|
"filter-passenger": "PASAŻERSKIE",
|
||||||
"filter-freight": "TOWAROWE",
|
"filter-freight": "TOWAROWE",
|
||||||
"filter-other": "INNE",
|
"filter-other": "INNE",
|
||||||
"filter-noTimetable": "BEZ RJ",
|
"filter-noTimetable": "BEZ RJ",
|
||||||
|
"filter-withTimetable": "ROZKŁAD JAZDY",
|
||||||
|
|
||||||
"filter-reset": "ZRESETUJ FILTRY",
|
"filter-reset": "ZRESETUJ FILTRY",
|
||||||
"filter-clear": "WYŁĄCZ FILTRY",
|
"filter-clear": "WYŁĄCZ FILTRY",
|
||||||
|
|
||||||
|
"filter-section-timetable-status": "STATUS ROZKŁADU JAZDY",
|
||||||
|
"filter-section-twrskr": "UWAGI",
|
||||||
|
|
||||||
"filter-all": "WSZYSTKIE",
|
"filter-all": "WSZYSTKIE",
|
||||||
"filter-abandoned": "PORZUCONE",
|
"filter-abandoned": "PORZUCONE",
|
||||||
"filter-fulfilled": "WYPEŁNIONE",
|
"filter-fulfilled": "WYPEŁNIONE",
|
||||||
"filter-active": "AKTYWNE"
|
"filter-active": "AKTYWNE"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
|
"desc": " • Kliknięcie: zaznaczenie / odznaczenie filtru <br /> • Podwójne kliknięcie: odznaczenie reszty filtrów z <b class='text--primary'>grupy</b> <br /> • <span style='color: coral'>RESET</span>: zresetowanie filtrów z <b class='text--primary'>grupy</b>",
|
||||||
|
|
||||||
|
"sections": {
|
||||||
|
"reality": "FIKCYJNOŚĆ SCENERII",
|
||||||
|
"package-access": "DOSTĘPNOŚĆ W PACZCE",
|
||||||
|
"access": "DOSTĘPNOŚĆ OGÓLNA",
|
||||||
|
"control": "TYP STEROWANIA",
|
||||||
|
"signals": "TYP SYGNALIZACJI",
|
||||||
|
"addons": "DODATKOWE PROGRAMY",
|
||||||
|
"blockades": "BLOKADY LINIOWE",
|
||||||
|
"status": "STATUS ONLINE"
|
||||||
|
},
|
||||||
|
|
||||||
"endingStatus": "KOŃCZY",
|
"endingStatus": "KOŃCZY",
|
||||||
"afkStatus": "Z/W",
|
"afkStatus": "Z/W",
|
||||||
"noSpaceStatus": "BRAK MIEJSCA",
|
"noSpaceStatus": "BRAK MIEJSCA",
|
||||||
@@ -138,18 +172,29 @@
|
|||||||
"abandoned": "WYCOFANA",
|
"abandoned": "WYCOFANA",
|
||||||
|
|
||||||
"SPK": "SPK",
|
"SPK": "SPK",
|
||||||
|
"SPK-R": "SPK + RĘCZNE",
|
||||||
|
"SPK-M": "SPK + MECH.",
|
||||||
"SCS": "SCS",
|
"SCS": "SCS",
|
||||||
|
"SCS-R": "SCS + RĘCZNE",
|
||||||
|
"SCS-M": "SCS + MECH.",
|
||||||
"SPE": "SPE",
|
"SPE": "SPE",
|
||||||
"manual": "RĘCZNE",
|
"manual": "RĘCZNE",
|
||||||
"SUP": "SUP",
|
|
||||||
"SBL": "SBL",
|
"SUP": "SUP (RASP-UZK)",
|
||||||
|
"noSUP": "BEZ SUP",
|
||||||
|
|
||||||
|
"SBL": "SAMOCZYNNA",
|
||||||
|
"PBL": "PÓŁSAMOCZYNNA",
|
||||||
|
|
||||||
"mechanical": "MECHANICZNE",
|
"mechanical": "MECHANICZNE",
|
||||||
"modern": "WSPÓŁCZESNA",
|
"modern": "WSPÓŁCZESNA",
|
||||||
"semaphores": "KSZTAŁTOWA",
|
"semaphores": "KSZTAŁTOWA",
|
||||||
"mixed": "MIESZANA",
|
"mixed": "MIESZANA",
|
||||||
"historical": "HISTORYCZNA",
|
"historical": "HISTORYCZNA",
|
||||||
|
|
||||||
"free": "WOLNA",
|
"free": "WOLNA",
|
||||||
"occupied": "ZAJĘTA",
|
"occupied": "ZAJĘTA",
|
||||||
|
|
||||||
"sliders": {
|
"sliders": {
|
||||||
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
|
"min-lvl": "MIN. WYMAGANY POZIOM DYŻURNEGO",
|
||||||
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
|
"max-lvl": "MAKS. WYMAGANY POZIOM DYŻURNEGO",
|
||||||
@@ -158,27 +203,31 @@
|
|||||||
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
|
"routes-2t-cat": "SZLAKI DWUTOROWE ZELEKTR. (MINIMUM)",
|
||||||
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
|
"routes-2t-other": "SZLAKI DWUTOROWE NIEZELEKTR. (MINIMUM)"
|
||||||
},
|
},
|
||||||
|
|
||||||
"authors-search": "Szukaj autora (uwzględnia inne filtry)",
|
"authors-search": "Szukaj autora (uwzględnia inne filtry)",
|
||||||
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
|
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
|
||||||
"now": "TERAZ",
|
"now": "TERAZ",
|
||||||
"hour": " godz.",
|
"hour": " godz.",
|
||||||
"no-limit": "BEZ LIMITU",
|
"no-limit": "BEZ LIMITU",
|
||||||
"include-selected": "POKAŻ ZAZNACZONE",
|
"include-selected": "POKAŻ ZAZNACZONE",
|
||||||
"save": "ZAPISZ FILTRY",
|
"save": "ZAPAMIĘTAJ FILTRY",
|
||||||
"reset": "RESETUJ FILTRY",
|
"reset": "RESETUJ FILTRY",
|
||||||
"close": "ZAMKNIJ FILTRY"
|
"close": "ZAMKNIJ FILTRY"
|
||||||
},
|
},
|
||||||
"sceneries": {
|
"sceneries": {
|
||||||
"station": "Stacja",
|
"station": "Stacja",
|
||||||
|
"abbr": "Skrót\nposterunku",
|
||||||
"min-lvl": "Min. poziom\ndyżurnego",
|
"min-lvl": "Min. poziom\ndyżurnego",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"dispatcher": "Dyżurny",
|
"dispatcher": "Dyżurny",
|
||||||
"dispatcher-lvl": "Poziom\ndyżurnego",
|
"dispatcher-lvl": "Poziom\ndyżurnego",
|
||||||
"routes": "Szlaki\n2tor / 1tor",
|
"routes": "Szlaki\n2tor / 1tor",
|
||||||
"general": "Informacje\nogólne",
|
"general": "Informacje\nogólne",
|
||||||
"users": "Maszyniści online",
|
"user": "Maszyniści online",
|
||||||
"spawns": "Otwarte spawny",
|
"spawn": "Otwarte spawny",
|
||||||
"timetables": "Aktywne rozkłady jazdy",
|
"timetableAll": "Aktywne rozkłady jazdy",
|
||||||
|
"timetableConfirmed": "Zatwierdzone rozkłady jazdy",
|
||||||
|
"timetableUnconfirmed": "Niezatwierdzone rozkłady jazdy",
|
||||||
"no-stations": "Brak stacji do wyświetlenia!",
|
"no-stations": "Brak stacji do wyświetlenia!",
|
||||||
"scenery-search": "Wyszukaj scenerię..."
|
"scenery-search": "Wyszukaj scenerię..."
|
||||||
},
|
},
|
||||||
@@ -250,20 +299,39 @@
|
|||||||
"timetable-fulfilled": "WYPEŁNIONY",
|
"timetable-fulfilled": "WYPEŁNIONY",
|
||||||
"timetable-abandoned": "PORZUCONY",
|
"timetable-abandoned": "PORZUCONY",
|
||||||
|
|
||||||
"stock-info": "INFORMACJE O SKŁADZIE",
|
"stock-info": "DODATKOWE INFORMACJE",
|
||||||
"stock-length": "Długość",
|
"stock-length": "Długość",
|
||||||
"stock-mass": "Masa",
|
"stock-mass": "Masa",
|
||||||
"stock-max-speed": "Maks. zarejestrowana prędkość",
|
"stock-max-speed": "Prędkość maks.",
|
||||||
|
|
||||||
"load-data": "Pobierz dalszą historię...",
|
"load-data": "Pobierz dalszą historię...",
|
||||||
|
|
||||||
"stats-title": "STATYSTYKI MASZYNISTY",
|
"stats-title": "STATYSTYKI MASZYNISTY",
|
||||||
|
|
||||||
|
"last-seen-at": "Ostatnio widziany na: ",
|
||||||
|
"currently-at": "Obecnie na scenerii: ",
|
||||||
|
|
||||||
"stats-timetables": "ROZKŁADY JAZDY",
|
"stats-timetables": "ROZKŁADY JAZDY",
|
||||||
"stats-longest-timetable": "NAJDŁUŻSZY RJ",
|
"stats-longest-timetable": "NAJDŁUŻSZY RJ",
|
||||||
"stats-avg-timetable": "ŚREDNIA DŁUGOŚĆ RJ",
|
"stats-avg-timetable": "ŚREDNIA DŁUGOŚĆ RJ",
|
||||||
"stats-distance": "DYSTANS",
|
"stats-distance": "DYSTANS",
|
||||||
"stats-stations": "STACJE"
|
"stats-stations": "STACJE",
|
||||||
|
|
||||||
|
"timetable-stats-total": "Dyżurni stworzyli dziś {count} o łącznym dystansie {distance}",
|
||||||
|
"timetable-stats-longest": "Najdłuższym rozkładem jazdy jest dzisiaj #{id} stworzony przez dyżurnego {author} dla maszynisty {driver} - {distance}",
|
||||||
|
"timetable-stats-most-active": "Dzisiejszym najaktywniejszym dyżurnym jest {dispatcher}, który stworzył {count}",
|
||||||
|
"timetable-stats-most-active-many": "Dzisiejszymi najaktywniejszymi dyżurnymi są {dispatchers}, którzy stworzyli po {count}",
|
||||||
|
|
||||||
|
"timetable-count": "rozkład jazdy | rozkładów jazdy",
|
||||||
|
|
||||||
|
"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!",
|
||||||
|
|
||||||
|
"stats-loading": "Pobieranie statystyk...",
|
||||||
|
"stats-error": "Ups! Wystąpił błąd podczas próby pobrania statystyk! :/"
|
||||||
},
|
},
|
||||||
"scenery": {
|
"scenery": {
|
||||||
"users": "GRACZE ONLINE",
|
"users": "GRACZE ONLINE",
|
||||||
@@ -276,24 +344,35 @@
|
|||||||
"no-scenery": "Ups! Ta sceneria nie istnieje!",
|
"no-scenery": "Ups! Ta sceneria nie istnieje!",
|
||||||
"return-btn": "Wróć na stronę główną",
|
"return-btn": "Wróć na stronę główną",
|
||||||
"history-btn": "Przejdź do widoku historii dyżurnych ruchu",
|
"history-btn": "Przejdź do widoku historii dyżurnych ruchu",
|
||||||
"info-btn": "Wróc do widoku scenerii",
|
"info-btn": "Wróć do widoku scenerii",
|
||||||
"authors-title": "Autor scenerii | Autorzy scenerii",
|
"authors-title": "Autor scenerii | Autorzy scenerii",
|
||||||
|
"abbrev": "Skrót posterunku:",
|
||||||
"lines-title": "Rzeczywiste linie",
|
"lines-title": "Rzeczywiste linie",
|
||||||
"project-title": "Projekt",
|
"project-title": "Projekt",
|
||||||
"one-way-routes": "Szlaki jednotorowe",
|
"one-way-routes": "Szlaki jednotorowe",
|
||||||
"two-way-routes": "Szlaki dwutorowe",
|
"two-way-routes": "Szlaki dwutorowe",
|
||||||
|
|
||||||
"option-active-timetables": "Aktywne rozkłady jazdy",
|
"option-active-timetables": "Aktywne rozkłady jazdy",
|
||||||
"option-timetables-history": "Historia rozkładów scenerii",
|
"option-timetables-history": "Historia rozkładów",
|
||||||
"option-dispatchers-history": "Historia dyżurów scenerii",
|
"option-dispatchers-history": "Historia dyżurów",
|
||||||
|
|
||||||
"timetable-author-title": "Wydany przez",
|
"timetable-author-title": "Wydany przez",
|
||||||
"timetable-author-unknown": "Autor nieznany",
|
"timetable-author-unknown": "Autor nieznany",
|
||||||
|
|
||||||
|
"timetables-history-id": "ID",
|
||||||
|
"timetables-history-number": "Numer",
|
||||||
|
"timetables-history-route": "Trasa",
|
||||||
|
"timetables-history-driver": "Maszynista",
|
||||||
|
"timetables-history-author": "Autor RJ",
|
||||||
|
"timetables-history-date": "Data",
|
||||||
|
|
||||||
"req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego",
|
"req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego",
|
||||||
"history-list-empty": "Brak historii dla tej scenerii!",
|
"history-list-empty": "Brak historii dla tej scenerii!",
|
||||||
|
|
||||||
"forum-topic": "Oficjalny wątek scenerii {name}"
|
"forum-topic": "Oficjalny wątek scenerii {name}",
|
||||||
|
|
||||||
|
"pragotron-link": "Paletowa tablica informacyjna (beta)",
|
||||||
|
"tablice-link": "Tablica informacyjna zbiorcza (autorstwa Thundo)"
|
||||||
},
|
},
|
||||||
"availability": {
|
"availability": {
|
||||||
"title": "Dostępność",
|
"title": "Dostępność",
|
||||||
@@ -308,7 +387,19 @@
|
|||||||
"end": "Koniec rozkładu jazdy",
|
"end": "Koniec rozkładu jazdy",
|
||||||
"terminated": "Rozkład jazdy zakończony",
|
"terminated": "Rozkład jazdy zakończony",
|
||||||
"begins": "ROZPOCZYNA\nBIEG",
|
"begins": "ROZPOCZYNA\nBIEG",
|
||||||
"terminates": "KOŃCZY BIEG"
|
"terminates": "KOŃCZY BIEG",
|
||||||
|
|
||||||
|
"from": "Z",
|
||||||
|
"to": "DO",
|
||||||
|
|
||||||
|
"desc-arriving": "Pociągu nie ma jeszcze na tej scenerii. Przyjedzie z: {prevStationName} (szlak {prevDepartureLine})",
|
||||||
|
"desc-online": "Pociąg jest na tej scenerii. Odjedzie do: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-stopped": "Pociąg jest na tej scenerii i odbywa postój. Odjedzie do: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-next-arrival": "Odjeżdża do: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-departed": "Pociąg jest na tej scenerii i został odprawiony. Odjeżdża do: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-departed-away": "Pociąg został odprawiony i odjechał do: {nextStationName} (szlak {nextArrivalLine})",
|
||||||
|
"desc-end": "Pociąg kończy bieg",
|
||||||
|
"desc-terminated": "Pociąg skończył bieg"
|
||||||
},
|
},
|
||||||
"history": {
|
"history": {
|
||||||
"title": "DZIENNIK ROZKŁADÓW JAZDY"
|
"title": "DZIENNIK ROZKŁADÓW JAZDY"
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { createPinia } from 'pinia';
|
|||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
locale: 'pl',
|
locale: 'pl',
|
||||||
|
legacy: false,
|
||||||
|
warnHtmlMessage: false,
|
||||||
fallbackLocale: 'pl',
|
fallbackLocale: 'pl',
|
||||||
messages: {
|
messages: {
|
||||||
en: enLang,
|
en: enLang,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { defineComponent } from 'vue';
|
|||||||
import { useStore } from '../store/store';
|
import { useStore } from '../store/store';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useStore(),
|
store: useStore(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,10 +5,16 @@ export default defineComponent({
|
|||||||
calculateExpStyle(exp: number, isSupporter = false): string {
|
calculateExpStyle(exp: number, isSupporter = false): string {
|
||||||
const bgColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 85%, 50%)`) : '#666';
|
const bgColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 85%, 50%)`) : '#666';
|
||||||
|
|
||||||
const fontColor = exp > 15 || exp == -1 ? 'white' : 'black';
|
const fontColor = exp > 14 || exp == -1 ? 'white' : 'black';
|
||||||
const boxShadow = isSupporter ? `box-shadow: 0 0 10px 2px ${bgColor};` : '';
|
const boxShadow = isSupporter ? `box-shadow: 0 0 6px 2px ${bgColor};` : '';
|
||||||
|
|
||||||
return `background-color: ${bgColor}; color: ${fontColor}; ${boxShadow}`;
|
return `background-color: ${bgColor}; color: ${fontColor}; ${boxShadow};`;
|
||||||
|
},
|
||||||
|
|
||||||
|
calculateTextExpStyle(exp: number, isSupporter = false): string {
|
||||||
|
const textColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 75%, 50%)`) : '#666';
|
||||||
|
|
||||||
|
return `color: ${textColor}; ${isSupporter ? 'text-shadow: 0 0 6px ' + textColor : ''};`;
|
||||||
},
|
},
|
||||||
|
|
||||||
statusClasses(occupiedTo: string) {
|
statusClasses(occupiedTo: string) {
|
||||||
@@ -41,6 +47,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return className;
|
return className;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { useRegisterSW } from 'virtual:pwa-register/vue';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const { needRefresh, updateServiceWorker, offlineReady } = useRegisterSW({
|
||||||
|
immediate: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
needRefresh,
|
||||||
|
updateServiceWorker,
|
||||||
|
offlineReady,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||||||
import JournalDispatchersVue from '../components/JournalView/JournalDispatchers.vue';
|
import JournalDispatchersVue from '../views/JournalDispatchers.vue';
|
||||||
import JournalTimetablesVue from '../components/JournalView/JournalTimetables.vue';
|
import JournalTimetablesVue from '../views/JournalTimetables.vue';
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
@@ -21,32 +21,23 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/journal',
|
path: '/journal',
|
||||||
name: 'JournalView',
|
redirect: '/journal/timetables'
|
||||||
component: () => import('../views/JournalView.vue'),
|
},
|
||||||
children: [
|
{
|
||||||
{
|
path: '/journal/timetables',
|
||||||
path: '',
|
name: 'JournalTimetables',
|
||||||
name: 'JournalTimetables',
|
component: JournalTimetablesVue,
|
||||||
component: JournalTimetablesVue,
|
props: (route) => ({
|
||||||
alias: '/timetables',
|
trainNo: route.query.trainNo,
|
||||||
},
|
driverName: route.query.driverName,
|
||||||
{
|
timetableId: route.query.timetableId,
|
||||||
path: 'dispatchers',
|
}),
|
||||||
name: 'JournalDispatchers',
|
},
|
||||||
component: JournalDispatchersVue,
|
{
|
||||||
props: (route) => ({ sceneryName: route.query.sceneryName, dispatcherName: route.query.dispatcherName }),
|
path: '/journal/dispatchers',
|
||||||
},
|
name: 'JournalDispatchers',
|
||||||
{
|
component: JournalDispatchersVue,
|
||||||
path: 'timetables',
|
props: (route) => ({ sceneryName: route.query.sceneryName, dispatcherName: route.query.dispatcherName }),
|
||||||
name: 'JournalTimetables',
|
|
||||||
component: JournalTimetablesVue,
|
|
||||||
props: (route) => ({
|
|
||||||
trainNo: route.query.trainNo,
|
|
||||||
driverName: route.query.driverName,
|
|
||||||
timetableId: route.query.timetableId,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/:catchAll(.*)',
|
path: '/:catchAll(.*)',
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import Filter from "../../interfaces/Filter";
|
||||||
|
|
||||||
|
export const filterInitStates: Filter = {
|
||||||
|
default: false,
|
||||||
|
notDefault: false,
|
||||||
|
real: false,
|
||||||
|
fictional: false,
|
||||||
|
SPK: false,
|
||||||
|
SCS: false,
|
||||||
|
SPE: false,
|
||||||
|
SUP: false,
|
||||||
|
noSUP: false,
|
||||||
|
ręczne: false,
|
||||||
|
'ręczne+SPK': false,
|
||||||
|
'ręczne+SCS': false,
|
||||||
|
mechaniczne: false,
|
||||||
|
'mechaniczne+SPK': false,
|
||||||
|
'mechaniczne+SCS': false,
|
||||||
|
współczesna: false,
|
||||||
|
kształtowa: false,
|
||||||
|
historyczna: false,
|
||||||
|
mieszana: false,
|
||||||
|
SBL: false,
|
||||||
|
PBL: false,
|
||||||
|
minLevel: 0,
|
||||||
|
maxLevel: 20,
|
||||||
|
minOneWayCatenary: 0,
|
||||||
|
minOneWay: 0,
|
||||||
|
minTwoWayCatenary: 0,
|
||||||
|
minTwoWay: 0,
|
||||||
|
'include-selected': false,
|
||||||
|
'no-1track': false,
|
||||||
|
'no-2track': false,
|
||||||
|
free: true,
|
||||||
|
occupied: false,
|
||||||
|
ending: false,
|
||||||
|
nonPublic: false,
|
||||||
|
unavailable: true,
|
||||||
|
abandoned: true,
|
||||||
|
afkStatus: false,
|
||||||
|
endingStatus: false,
|
||||||
|
noSpaceStatus: false,
|
||||||
|
unavailableStatus: false,
|
||||||
|
unsignedStatus: false,
|
||||||
|
|
||||||
|
authors: '',
|
||||||
|
|
||||||
|
onlineFromHours: 0,
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export const headIds = ['station', 'min-lvl', 'status', 'dispatcher', 'dispatcher-lvl', 'routes', 'general'] as const;
|
||||||
|
|
||||||
|
export const headIconsIds = ['user', 'spawn', 'timetableAll', 'timetableUnconfirmed', 'timetableConfirmed'] as const;
|
||||||
|
|
||||||
|
export type HeadIdsTypes = typeof headIds[number] | typeof headIconsIds[number];
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
export const enum JournalFilterType {
|
export const enum JournalFilterType {
|
||||||
active = "active",
|
ACTIVE = 'active',
|
||||||
fulfilled = "fulfilled",
|
FULFILLED = 'fulfilled',
|
||||||
abandoned = "abandoned",
|
ABANDONED = 'abandoned',
|
||||||
all = "all"
|
ALL = 'all',
|
||||||
|
TWR = 'twr',
|
||||||
|
SKR = 'skr',
|
||||||
|
TWR_SKR = 'twr-skr',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum JournalFilterSection {
|
||||||
|
TIMETABLE_STATUS = 'timetable-status',
|
||||||
|
TWRSKR = 'twrskr',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
export const enum TrainFilterType {
|
export enum TrainFilterSection {
|
||||||
comments = "comments",
|
TRAIN_TYPE = 'TRAIN_TYPE',
|
||||||
twr = "twr",
|
TIMETABLE_TYPE = 'TIMETABLE_TYPE',
|
||||||
skr = "skr",
|
COMMENTS = 'COMMENTS',
|
||||||
passenger = "passenger",
|
TIMETABLE = 'TIMETABLE',
|
||||||
freight = "freight",
|
}
|
||||||
other = "other",
|
|
||||||
noTimetable = "noTimetable"
|
export const enum TrainFilterType {
|
||||||
|
noComments = 'noComments',
|
||||||
|
withComments = 'withComments',
|
||||||
|
|
||||||
|
twr = 'twr',
|
||||||
|
skr = 'skr',
|
||||||
|
common = 'common',
|
||||||
|
|
||||||
|
passenger = 'passenger',
|
||||||
|
freight = 'freight',
|
||||||
|
other = 'other',
|
||||||
|
noTimetable = 'noTimetable',
|
||||||
|
withTimetable = 'withTimetable',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
export default interface Filter {
|
export default interface Filter {
|
||||||
[key: string]: (boolean | number | string),
|
[key: string]: boolean | number | string;
|
||||||
default: boolean;
|
default: boolean;
|
||||||
notDefault: boolean;
|
notDefault: boolean;
|
||||||
real: boolean;
|
real: boolean;
|
||||||
fictional: boolean;
|
fictional: boolean;
|
||||||
"SPK": boolean;
|
SPK: boolean;
|
||||||
"SCS": boolean;
|
SCS: boolean;
|
||||||
"SPE": boolean;
|
SPE: boolean;
|
||||||
"SUP": boolean;
|
SUP: boolean;
|
||||||
|
noSUP: boolean;
|
||||||
ręczne: boolean;
|
ręczne: boolean;
|
||||||
|
'ręczne+SPK': boolean;
|
||||||
|
'ręczne+SCS': boolean;
|
||||||
mechaniczne: boolean;
|
mechaniczne: boolean;
|
||||||
"SBL": boolean;
|
'mechaniczne+SPK': boolean;
|
||||||
|
'mechaniczne+SCS': boolean;
|
||||||
|
SBL: boolean;
|
||||||
|
PBL: boolean;
|
||||||
współczesna: boolean;
|
współczesna: boolean;
|
||||||
kształtowa: boolean;
|
kształtowa: boolean;
|
||||||
historyczna: boolean;
|
historyczna: boolean;
|
||||||
|
|||||||
@@ -1,32 +1,41 @@
|
|||||||
import TrainStop from "./TrainStop";
|
import TrainStop from './TrainStop';
|
||||||
|
|
||||||
export default interface ScheduledTrain {
|
export enum StopStatus {
|
||||||
trainId: string;
|
'arriving' = 'arriving',
|
||||||
trainNo: number;
|
'departed' = 'departed',
|
||||||
|
'departed-away' = 'departed-away',
|
||||||
driverName: string;
|
'online' = 'online',
|
||||||
driverId: number;
|
'stopped' = 'stopped',
|
||||||
currentStationName: string;
|
'terminated' = 'terminated',
|
||||||
currentStationHash: string;
|
}
|
||||||
category: string;
|
|
||||||
stopInfo: TrainStop;
|
export interface ScheduledTrain {
|
||||||
|
trainId: string;
|
||||||
terminatesAt: string;
|
trainNo: number;
|
||||||
beginsAt: string;
|
|
||||||
|
driverName: string;
|
||||||
prevStationName: string;
|
driverId: number;
|
||||||
nextStationName: string;
|
currentStationName: string;
|
||||||
|
currentStationHash: string;
|
||||||
arrivingLine: string | null;
|
category: string;
|
||||||
departureLine: string | null;
|
stopInfo: TrainStop;
|
||||||
|
|
||||||
prevDepartureLine: string | null;
|
terminatesAt: string;
|
||||||
nextArrivalLine: string | null;
|
beginsAt: string;
|
||||||
|
|
||||||
signal: string;
|
prevStationName: string;
|
||||||
connectedTrack: string;
|
nextStationName: string;
|
||||||
|
|
||||||
stopLabel: string;
|
arrivingLine: string | null;
|
||||||
stopStatus: string;
|
departureLine: string | null;
|
||||||
stopStatusID: number;
|
|
||||||
|
prevDepartureLine: string | null;
|
||||||
|
nextArrivalLine: string | null;
|
||||||
|
|
||||||
|
signal: string;
|
||||||
|
connectedTrack: string;
|
||||||
|
|
||||||
|
stopLabel: string;
|
||||||
|
stopStatus: StopStatus;
|
||||||
|
stopStatusID: number;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Availability } from '../../store/storeTypes';
|
import { Availability } from './store/storeTypes';
|
||||||
import ScheduledTrain from './ScheduledTrain';
|
import {ScheduledTrain} from './ScheduledTrain';
|
||||||
import StationRoutes from './StationRoutes';
|
import StationRoutes from './StationRoutes';
|
||||||
|
|
||||||
export default interface Station {
|
export default interface Station {
|
||||||
@@ -8,12 +8,15 @@ export default interface Station {
|
|||||||
generalInfo?: {
|
generalInfo?: {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
abbr: string;
|
||||||
|
|
||||||
reqLevel: number;
|
reqLevel: number;
|
||||||
// supportersOnly: boolean;
|
// supportersOnly: boolean;
|
||||||
|
|
||||||
|
|
||||||
lines: string;
|
lines: string;
|
||||||
project: string;
|
project: string;
|
||||||
|
projectUrl?: string;
|
||||||
|
|
||||||
signalType: string;
|
signalType: string;
|
||||||
controlType: string;
|
controlType: string;
|
||||||
|
|||||||
@@ -1,27 +1,30 @@
|
|||||||
export default interface StationRoutes {
|
export default interface StationRoutes {
|
||||||
oneWay:
|
oneWay: {
|
||||||
{
|
name: string;
|
||||||
name: string;
|
catenary: boolean;
|
||||||
catenary: boolean;
|
SBL: boolean;
|
||||||
SBL: boolean;
|
TWB: boolean;
|
||||||
TWB: boolean;
|
isInternal: boolean;
|
||||||
isInternal: boolean;
|
tracks: number;
|
||||||
tracks: number;
|
speed: number;
|
||||||
}[];
|
length: number;
|
||||||
|
}[];
|
||||||
|
|
||||||
twoWay: {
|
twoWay: {
|
||||||
name: string;
|
name: string;
|
||||||
catenary: boolean;
|
catenary: boolean;
|
||||||
SBL: boolean;
|
SBL: boolean;
|
||||||
TWB: boolean;
|
TWB: boolean;
|
||||||
isInternal: boolean;
|
isInternal: boolean;
|
||||||
tracks: number;
|
tracks: number;
|
||||||
}[];
|
speed: number;
|
||||||
|
length: number;
|
||||||
|
}[];
|
||||||
|
|
||||||
/* [catenary, noCatenary] */
|
/* [catenary, noCatenary] */
|
||||||
oneWayCatenaryRouteNames: string[];
|
oneWayCatenaryRouteNames: string[];
|
||||||
oneWayNoCatenaryRouteNames: string[];
|
oneWayNoCatenaryRouteNames: string[];
|
||||||
twoWayCatenaryRouteNames: string[];
|
twoWayCatenaryRouteNames: string[];
|
||||||
twoWayNoCatenaryRouteNames: string[];
|
twoWayNoCatenaryRouteNames: string[];
|
||||||
sblRouteNames: string[];
|
sblRouteNames: string[];
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@ export default interface Train {
|
|||||||
driverId: number;
|
driverId: number;
|
||||||
trainNo: number;
|
trainNo: number;
|
||||||
driverName: string;
|
driverName: string;
|
||||||
|
driverLevel: number;
|
||||||
currentStationName: string;
|
currentStationName: string;
|
||||||
currentStationHash: string;
|
currentStationHash: string;
|
||||||
locoURL: string;
|
locoURL: string;
|
||||||
@@ -22,6 +23,7 @@ export default interface Train {
|
|||||||
cars: string[];
|
cars: string[];
|
||||||
|
|
||||||
isTimeout: boolean;
|
isTimeout: boolean;
|
||||||
|
isSupporter: boolean;
|
||||||
|
|
||||||
timetableData?: {
|
timetableData?: {
|
||||||
timetableId: number;
|
timetableId: number;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export default interface TrainStop {
|
export default interface TrainStop {
|
||||||
stopName: string;
|
stopName: string;
|
||||||
stopNameRAW: string;
|
stopNameRAW: string;
|
||||||
stopType: string;
|
stopType: string;
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { TrainFilterSection, TrainFilterType } from '../../enums/TrainFilterType'
|
||||||
|
|
||||||
|
export interface TrainFilter {
|
||||||
|
id: TrainFilterType;
|
||||||
|
section: TrainFilterSection;
|
||||||
|
isActive: boolean;
|
||||||
|
}
|
||||||
@@ -4,6 +4,9 @@ export interface DispatcherHistory {
|
|||||||
currentDuration: number;
|
currentDuration: number;
|
||||||
dispatcherId: number;
|
dispatcherId: number;
|
||||||
dispatcherName: string;
|
dispatcherName: string;
|
||||||
|
dispatcherLevel: number | null;
|
||||||
|
dispatcherRate: number;
|
||||||
|
dispatcherIsSupporter: boolean;
|
||||||
isOnline: boolean;
|
isOnline: boolean;
|
||||||
lastOnlineTimestamp: number;
|
lastOnlineTimestamp: number;
|
||||||
region: string;
|
region: string;
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { TimetableHistory } from './TimetablesAPIData';
|
||||||
|
|
||||||
|
export interface ITimetablesDailyStats {
|
||||||
|
totalTimetables: number;
|
||||||
|
distanceSum: number;
|
||||||
|
distanceAvg: number;
|
||||||
|
|
||||||
|
timetableId: number;
|
||||||
|
timetableAuthor: string;
|
||||||
|
timetableDriver: string;
|
||||||
|
timetableRouteDistance: number;
|
||||||
|
|
||||||
|
mostActiveDispatchers: {
|
||||||
|
name: string;
|
||||||
|
count: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITimetablesDailyStatsResponse {
|
||||||
|
totalTimetables: number;
|
||||||
|
distanceSum: number;
|
||||||
|
distanceAvg: number;
|
||||||
|
maxTimetable: TimetableHistory | null;
|
||||||
|
|
||||||
|
mostActiveDispatchers: {
|
||||||
|
name: string;
|
||||||
|
count: number;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
export interface TimetableHistory {
|
export interface TimetableHistory {
|
||||||
|
id: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
|
||||||
timetableId: number;
|
timetableId: number;
|
||||||
trainNo: number;
|
trainNo: number;
|
||||||
trainCategoryCode: string;
|
trainCategoryCode: string;
|
||||||
|
|
||||||
driverId: number;
|
driverId: number;
|
||||||
driverName: string;
|
driverName: string;
|
||||||
|
driverLevel: number | null;
|
||||||
|
driverIsSupporter: boolean;
|
||||||
|
|
||||||
route: string;
|
route: string;
|
||||||
twr: number;
|
twr: number;
|
||||||
skr: number;
|
skr: number;
|
||||||
@@ -27,7 +35,11 @@ export interface TimetableHistory {
|
|||||||
authorName?: string;
|
authorName?: string;
|
||||||
authorId?: number;
|
authorId?: number;
|
||||||
|
|
||||||
|
stopsString?: string;
|
||||||
|
|
||||||
stockString?: string;
|
stockString?: string;
|
||||||
|
stockHistory: string[];
|
||||||
|
|
||||||
stockMass?: number;
|
stockMass?: number;
|
||||||
stockLength?: number;
|
stockLength?: number;
|
||||||
maxSpeed?: number;
|
maxSpeed?: number;
|
||||||
@@ -35,10 +47,12 @@ export interface TimetableHistory {
|
|||||||
hashesString?: string;
|
hashesString?: string;
|
||||||
currentSceneryName?: string;
|
currentSceneryName?: string;
|
||||||
currentSceneryHash?: string;
|
currentSceneryHash?: string;
|
||||||
|
|
||||||
|
routeSceneries?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SceneryTimetableHistory {
|
export interface SceneryTimetableHistory {
|
||||||
sceneryTimetables: TimetableHistory[];
|
timetables: TimetableHistory[];
|
||||||
totalCount: number;
|
// totalCount: number;
|
||||||
sceneryName: string;
|
// sceneryName: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { JournalTimetableSorter } from '../../types/JournalTimetablesTypes';
|
||||||
|
|
||||||
|
export interface TimetablesQueryParams {
|
||||||
|
driverName?: string;
|
||||||
|
trainNo?: string;
|
||||||
|
authorName?: string;
|
||||||
|
timestampFrom?: number;
|
||||||
|
timestampTo?: number;
|
||||||
|
issuedFrom?: string;
|
||||||
|
|
||||||
|
countFrom?: number;
|
||||||
|
countLimit?: number;
|
||||||
|
|
||||||
|
fulfilled?: number;
|
||||||
|
terminated?: number;
|
||||||
|
|
||||||
|
twr?: number;
|
||||||
|
skr?: number;
|
||||||
|
|
||||||
|
sortBy?: JournalTimetableSorter['id'];
|
||||||
|
}
|
||||||
@@ -1,4 +1,44 @@
|
|||||||
export default interface TrainAPIData {
|
export interface TimetableStop {
|
||||||
|
stopName: string;
|
||||||
|
stopNameRAW: string;
|
||||||
|
stopType: string;
|
||||||
|
stopDistance: number;
|
||||||
|
pointId: number;
|
||||||
|
|
||||||
|
mainStop: boolean;
|
||||||
|
|
||||||
|
arrivalLine: string;
|
||||||
|
arrivalTimestamp: number;
|
||||||
|
arrivalRealTimestamp: number;
|
||||||
|
arrivalDelay: number;
|
||||||
|
|
||||||
|
departureLine: string;
|
||||||
|
departureTimestamp: number;
|
||||||
|
departureRealTimestamp: number;
|
||||||
|
departureDelay: number;
|
||||||
|
|
||||||
|
comments?: any;
|
||||||
|
|
||||||
|
beginsHere: boolean;
|
||||||
|
terminatesHere: boolean;
|
||||||
|
confirmed: boolean;
|
||||||
|
stopped: boolean;
|
||||||
|
stopTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrainTimetable {
|
||||||
|
timetableId: number;
|
||||||
|
category: string;
|
||||||
|
route: string;
|
||||||
|
|
||||||
|
stopList: TimetableStop[];
|
||||||
|
|
||||||
|
TWR: boolean;
|
||||||
|
SKR: boolean;
|
||||||
|
sceneries: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrainAPIData {
|
||||||
trainNo: number;
|
trainNo: number;
|
||||||
|
|
||||||
mass: number;
|
mass: number;
|
||||||
@@ -13,6 +53,7 @@ export default interface TrainAPIData {
|
|||||||
driverName: string;
|
driverName: string;
|
||||||
driverId: number;
|
driverId: number;
|
||||||
driverIsSupporter: boolean;
|
driverIsSupporter: boolean;
|
||||||
|
driverLevel?: number;
|
||||||
|
|
||||||
currentStationName: string;
|
currentStationName: string;
|
||||||
currentStationHash: string;
|
currentStationHash: string;
|
||||||
@@ -23,41 +64,5 @@ export default interface TrainAPIData {
|
|||||||
region: string;
|
region: string;
|
||||||
isTimeout: boolean;
|
isTimeout: boolean;
|
||||||
|
|
||||||
timetable?: {
|
timetable?: TrainTimetable;
|
||||||
timetableId: number;
|
|
||||||
category: string;
|
|
||||||
route: string;
|
|
||||||
|
|
||||||
stopList: {
|
|
||||||
stopName: string;
|
|
||||||
stopNameRAW: string;
|
|
||||||
stopType: string;
|
|
||||||
stopDistance: number;
|
|
||||||
pointId: number;
|
|
||||||
|
|
||||||
mainStop: boolean;
|
|
||||||
|
|
||||||
arrivalLine: string;
|
|
||||||
arrivalTimestamp: number;
|
|
||||||
arrivalRealTimestamp: number;
|
|
||||||
arrivalDelay: number;
|
|
||||||
|
|
||||||
departureLine: string;
|
|
||||||
departureTimestamp: number;
|
|
||||||
departureRealTimestamp: number;
|
|
||||||
departureDelay: number;
|
|
||||||
|
|
||||||
comments?: any;
|
|
||||||
|
|
||||||
beginsHere: boolean;
|
|
||||||
terminatesHere: boolean;
|
|
||||||
confirmed: boolean;
|
|
||||||
stopped: boolean;
|
|
||||||
stopTime: number;
|
|
||||||
}[];
|
|
||||||
|
|
||||||
TWR: boolean;
|
|
||||||
SKR: boolean;
|
|
||||||
sceneries: string[];
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { DataStatus } from '../scripts/enums/DataStatus';
|
import { DataStatus } from '../../enums/DataStatus';
|
||||||
import StationAPIData from '../scripts/interfaces/api/StationAPIData';
|
import StationAPIData from '../api/StationAPIData';
|
||||||
import TrainAPIData from '../scripts/interfaces/api/TrainAPIData';
|
import { TrainAPIData } from '../api/TrainAPIData';
|
||||||
import Station from '../scripts/interfaces/Station';
|
import Station from '../Station';
|
||||||
import Train from '../scripts/interfaces/Train';
|
import Train from '../Train';
|
||||||
import { DispatcherStatsAPIData } from '../scripts/interfaces/api/DispatcherStatsAPIData';
|
import { DispatcherStatsAPIData } from '../api/DispatcherStatsAPIData';
|
||||||
import { DriverStatsAPIData } from '../scripts/interfaces/api/DriverStatsAPIData';
|
import { DriverStatsAPIData } from '../api/DriverStatsAPIData';
|
||||||
|
|
||||||
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
|
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
|
||||||
|
|
||||||
@@ -23,15 +23,19 @@ export interface StoreState {
|
|||||||
stationCount: number;
|
stationCount: number;
|
||||||
|
|
||||||
webSocket?: Socket;
|
webSocket?: Socket;
|
||||||
|
isOffline: boolean;
|
||||||
|
|
||||||
dispatcherStatsName: string;
|
dispatcherStatsName: string;
|
||||||
dispatcherStatsData?: DispatcherStatsAPIData;
|
dispatcherStatsData?: DispatcherStatsAPIData;
|
||||||
|
|
||||||
driverStatsName: string;
|
driverStatsName: string;
|
||||||
driverStatsData?: DriverStatsAPIData;
|
driverStatsData?: DriverStatsAPIData;
|
||||||
|
driverStatsStatus: DataStatus;
|
||||||
|
|
||||||
chosenModalTrainId?: string;
|
chosenModalTrainId?: string;
|
||||||
|
|
||||||
|
currentStatsTab: 'daily' | 'driver' | null;
|
||||||
|
|
||||||
dataStatuses: {
|
dataStatuses: {
|
||||||
connection: DataStatus;
|
connection: DataStatus;
|
||||||
sceneries: DataStatus;
|
sceneries: DataStatus;
|
||||||
@@ -48,13 +52,26 @@ export interface APIData {
|
|||||||
stations?: StationAPIData[];
|
stations?: StationAPIData[];
|
||||||
dispatchers?: string[][];
|
dispatchers?: string[][];
|
||||||
trains?: TrainAPIData[];
|
trains?: TrainAPIData[];
|
||||||
|
connectedSocketCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StationRoutesInfo {
|
||||||
|
routeName: string;
|
||||||
|
isElectric: boolean;
|
||||||
|
isInternal: boolean;
|
||||||
|
isRouteSBL: boolean;
|
||||||
|
routeLength: number;
|
||||||
|
routeSpeed: number;
|
||||||
|
routeTracks: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StationJSONData {
|
export interface StationJSONData {
|
||||||
name: string;
|
name: string;
|
||||||
|
abbr: string;
|
||||||
url: string;
|
url: string;
|
||||||
lines: string;
|
lines: string;
|
||||||
project: string;
|
project: string;
|
||||||
|
projectUrl: string;
|
||||||
|
|
||||||
reqLevel: number;
|
reqLevel: number;
|
||||||
|
|
||||||
@@ -63,7 +80,9 @@ export interface StationJSONData {
|
|||||||
|
|
||||||
SUP: boolean;
|
SUP: boolean;
|
||||||
|
|
||||||
routes: string;
|
// routes: string;
|
||||||
|
routesInfo: StationRoutesInfo[];
|
||||||
|
|
||||||
checkpoints: string | null;
|
checkpoints: string | null;
|
||||||
authors?: string;
|
authors?: string;
|
||||||
|
|
||||||
@@ -1,115 +1,130 @@
|
|||||||
import { TrainFilter } from "../../types/Trains/TrainOptionsTypes";
|
import { TrainFilter } from '../interfaces/Trains/TrainFilter';
|
||||||
import { TrainFilterType } from "../enums/TrainFilterType";
|
import { TrainFilterType } from '../enums/TrainFilterType';
|
||||||
import Train from "../interfaces/Train";
|
import Train from '../interfaces/Train';
|
||||||
import TrainStop from "../interfaces/TrainStop";
|
import TrainStop from '../interfaces/TrainStop';
|
||||||
|
|
||||||
function confirmedPercentage(stops: TrainStop[] | undefined) {
|
function confirmedPercentage(stops: TrainStop[] | undefined) {
|
||||||
if (!stops) return -1;
|
if (!stops) return -1;
|
||||||
|
|
||||||
return Number(((stops.filter((stop) => stop.confirmed).length / stops.length) * 100).toFixed(0));
|
return Number(((stops.filter((stop) => stop.confirmed).length / stops.length) * 100).toFixed(0));
|
||||||
};
|
}
|
||||||
|
|
||||||
function currentDelay(stops: TrainStop[] | undefined) {
|
function currentDelay(stops: TrainStop[] | undefined) {
|
||||||
if (!stops) return -Infinity;
|
if (!stops) return -Infinity;
|
||||||
|
|
||||||
const delay =
|
const delay =
|
||||||
stops.find((stop, i) => (i == 0 && !stop.confirmed) || (i > 0 && stops[i - 1].confirmed && !stop.confirmed))
|
stops.find((stop, i) => (i == 0 && !stop.confirmed) || (i > 0 && stops[i - 1].confirmed && !stop.confirmed))
|
||||||
?.departureDelay || 0;
|
?.departureDelay || 0;
|
||||||
|
|
||||||
return delay;
|
return delay;
|
||||||
};
|
}
|
||||||
|
|
||||||
function filterTrainList(trainList: Train[], searchedTrain: string, searchedDriver: string, filters: TrainFilter[]) {
|
function filterTrainList(trainList: Train[], searchedTrain: string, searchedDriver: string, filters: TrainFilter[]) {
|
||||||
return trainList.filter(
|
return trainList.filter((train) => {
|
||||||
(train) => {
|
const isFiltered = filters.every((f) => {
|
||||||
const isFiltered = filters.every(f => {
|
if (f.isActive) return true;
|
||||||
if (f.isActive) return true;
|
|
||||||
|
|
||||||
if (!train.timetableData) return filters.find(filter => filter.id == TrainFilterType.noTimetable)!.isActive;
|
switch (f.id) {
|
||||||
|
case TrainFilterType.noTimetable:
|
||||||
|
return train.timetableData;
|
||||||
|
|
||||||
switch (f.id) {
|
case TrainFilterType.withTimetable:
|
||||||
case TrainFilterType.comments:
|
return !train.timetableData;
|
||||||
return !train.timetableData.followingStops.some(stop => stop.comments);
|
|
||||||
|
|
||||||
case TrainFilterType.twr:
|
case TrainFilterType.withComments:
|
||||||
return !train.timetableData.TWR;
|
return !train.timetableData?.followingStops.some((stop) => stop.comments);
|
||||||
|
|
||||||
case TrainFilterType.skr:
|
case TrainFilterType.noComments:
|
||||||
return !train.timetableData.SKR;
|
return train.timetableData?.followingStops.some((stop) => stop.comments);
|
||||||
|
|
||||||
case TrainFilterType.passenger:
|
case TrainFilterType.twr:
|
||||||
return !/^[AMRE]\D{2}$/.test(train.timetableData.category);
|
return !train.timetableData?.TWR;
|
||||||
|
|
||||||
case TrainFilterType.freight:
|
case TrainFilterType.skr:
|
||||||
return !train.timetableData.category.startsWith('T');
|
return !train.timetableData?.SKR;
|
||||||
|
|
||||||
case TrainFilterType.other:
|
case TrainFilterType.common:
|
||||||
return !/^[PXZL]\D{2}$/.test(train.timetableData.category);
|
return train.timetableData?.SKR || train.timetableData?.TWR;
|
||||||
|
|
||||||
default:
|
case TrainFilterType.passenger:
|
||||||
return true;
|
return !/^[AMRE]\D{2}$/.test(train.timetableData?.category || '');
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (searchedTrain.length > 0 ? train.trainNo.toString().startsWith(searchedTrain) : true) &&
|
case TrainFilterType.freight:
|
||||||
(searchedDriver.length > 0 ? train.driverName.toLowerCase().startsWith(searchedDriver.toLowerCase()) : true) && isFiltered
|
return !train.timetableData?.category.startsWith('T');
|
||||||
}
|
|
||||||
|
|
||||||
|
case TrainFilterType.other:
|
||||||
|
return !/^[PXZL]\D{2}$/.test(train.timetableData?.category || '');
|
||||||
|
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
(searchedTrain.length > 0 ? train.trainNo.toString().startsWith(searchedTrain) : true) &&
|
||||||
|
(searchedDriver.length > 0 ? train.driverName.toLowerCase().startsWith(searchedDriver.toLowerCase()) : true) &&
|
||||||
|
(!train.timetableData ? train.online : train.timetableData) &&
|
||||||
|
isFiltered
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortTrainList(trainList: Train[], sorterActive: { id: string; dir: number }) {
|
function sortTrainList(trainList: Train[], sorterActive: { id: string; dir: number }) {
|
||||||
return trainList.sort((a: Train, b: Train) => {
|
return trainList.sort((a: Train, b: Train) => {
|
||||||
switch (sorterActive.id) {
|
switch (sorterActive.id) {
|
||||||
case 'mass':
|
case 'id':
|
||||||
if (a.mass > b.mass) return sorterActive.dir;
|
if ((a.timetableData?.timetableId || -1) > (b.timetableData?.timetableId || -1)) return sorterActive.dir;
|
||||||
return -sorterActive.dir;
|
|
||||||
|
|
||||||
case 'distance':
|
return -sorterActive.dir;
|
||||||
if ((a.timetableData?.routeDistance || -1) > (b.timetableData?.routeDistance || -1)) return sorterActive.dir;
|
|
||||||
|
|
||||||
return -sorterActive.dir;
|
case 'mass':
|
||||||
|
if (a.mass > b.mass) return sorterActive.dir;
|
||||||
|
return -sorterActive.dir;
|
||||||
|
|
||||||
case 'progress':
|
case 'routeDistance':
|
||||||
if (confirmedPercentage(a.timetableData?.followingStops) > confirmedPercentage(b.timetableData?.followingStops))
|
if ((a.timetableData?.routeDistance || -1) > (b.timetableData?.routeDistance || -1)) return sorterActive.dir;
|
||||||
return sorterActive.dir;
|
|
||||||
|
|
||||||
return -sorterActive.dir;
|
return -sorterActive.dir;
|
||||||
|
|
||||||
case 'delay':
|
case 'progress':
|
||||||
if (currentDelay(a.timetableData?.followingStops) > currentDelay(b.timetableData?.followingStops))
|
if (confirmedPercentage(a.timetableData?.followingStops) > confirmedPercentage(b.timetableData?.followingStops))
|
||||||
return sorterActive.dir;
|
return sorterActive.dir;
|
||||||
|
|
||||||
return -sorterActive.dir;
|
return -sorterActive.dir;
|
||||||
|
|
||||||
case 'speed':
|
case 'delay':
|
||||||
if (a.speed > b.speed) return sorterActive.dir;
|
if (currentDelay(a.timetableData?.followingStops) > currentDelay(b.timetableData?.followingStops))
|
||||||
return -sorterActive.dir;
|
return sorterActive.dir;
|
||||||
|
|
||||||
case 'timetable':
|
return -sorterActive.dir;
|
||||||
if (a.trainNo > b.trainNo) return sorterActive.dir;
|
|
||||||
return -sorterActive.dir;
|
|
||||||
|
|
||||||
case 'length':
|
case 'speed':
|
||||||
if (a.length > b.length) return sorterActive.dir;
|
if (a.speed > b.speed) return sorterActive.dir;
|
||||||
return -sorterActive.dir;
|
return -sorterActive.dir;
|
||||||
|
|
||||||
default:
|
case 'timetable':
|
||||||
break;
|
if (a.trainNo > b.trainNo) return sorterActive.dir;
|
||||||
}
|
return -sorterActive.dir;
|
||||||
|
|
||||||
return 0;
|
case 'length':
|
||||||
});
|
if (a.length > b.length) return sorterActive.dir;
|
||||||
|
return -sorterActive.dir;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filteredTrainList(
|
export function filteredTrainList(
|
||||||
trainList: Train[],
|
trainList: Train[],
|
||||||
searchedTrain: string,
|
searchedTrain: string,
|
||||||
searchedDriver: string,
|
searchedDriver: string,
|
||||||
sorterActive: { id: string; dir: number },
|
sorterActive: { id: string; dir: number },
|
||||||
filters: TrainFilter[]
|
filters: TrainFilter[]
|
||||||
) {
|
) {
|
||||||
|
const filtered = filterTrainList(trainList, searchedTrain, searchedDriver, filters);
|
||||||
const filtered = filterTrainList(trainList, searchedTrain, searchedDriver, filters);
|
return [...sortTrainList(filtered, sorterActive)];
|
||||||
return [...sortTrainList(filtered, sorterActive)];
|
}
|
||||||
};
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { JournalFilterType } from '../../scripts/enums/JournalFilterType';
|
||||||
|
|
||||||
|
export type JournalTimetableSearchKey =
|
||||||
|
| 'search-driver'
|
||||||
|
| 'search-train'
|
||||||
|
| 'search-date'
|
||||||
|
| 'search-dispatcher'
|
||||||
|
| 'search-issuedFrom';
|
||||||
|
|
||||||
|
export type JournalTimetableSorterKey = 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
|
||||||
|
|
||||||
|
export type JournalTimetableSearchType = {
|
||||||
|
[key in JournalTimetableSearchKey]: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface JournalFilter {
|
||||||
|
id: JournalFilterType;
|
||||||
|
filterSection: string;
|
||||||
|
isActive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JournalTimetableSorter {
|
||||||
|
id: JournalTimetableSorterKey;
|
||||||
|
dir: 'asc' | 'desc';
|
||||||
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
export const URLs = {
|
export const URLs = {
|
||||||
stacjownikAPI:
|
stacjownikAPI:
|
||||||
import.meta.env.VITE_APP_API_DEV == 1 && !import.meta.env.PROD
|
import.meta.env.VITE_APP_API_DEV == 1 && !import.meta.env.PROD
|
||||||
? 'http://localhost:3000'
|
? 'http://localhost:3001'
|
||||||
: 'https://stacjownik.eu-4.evennode.com',
|
: 'https://spythere.pl',
|
||||||
stacjownikAPIDev: 'localhost:3000',
|
stacjownikAPIDev: 'localhost:3000',
|
||||||
// trains: "https://api.td2.info.pl:9640/?method=getTrainsOnline",
|
|
||||||
// getTimetableURL: (trainNo: string | number, region = "eu") => `https://api.td2.info.pl:9640/?method=readFromSWDR&value=getTimetable%3B${trainNo}%3B${region}`
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import { HeadIdsTypes } from '../data/stationHeaderNames';
|
||||||
|
import Filter from '../interfaces/Filter';
|
||||||
|
import Station from '../interfaces/Station';
|
||||||
|
|
||||||
|
export const sortStations = (a: Station, b: Station, sorter: { headerName: HeadIdsTypes; dir: number }) => {
|
||||||
|
let diff = 0;
|
||||||
|
|
||||||
|
switch (sorter.headerName) {
|
||||||
|
case 'station':
|
||||||
|
return sorter.dir == 1 ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
|
||||||
|
|
||||||
|
case 'min-lvl':
|
||||||
|
diff = (a.generalInfo?.reqLevel || 0) - (b.generalInfo?.reqLevel || 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'status':
|
||||||
|
diff = (a.onlineInfo?.statusTimestamp || 0) - (b.onlineInfo?.statusTimestamp || 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'dispatcher':
|
||||||
|
if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') > (b.onlineInfo?.dispatcherName.toLowerCase() || ''))
|
||||||
|
return sorter.dir;
|
||||||
|
if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') < (b.onlineInfo?.dispatcherName.toLowerCase() || ''))
|
||||||
|
return -sorter.dir;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'dispatcher-lvl':
|
||||||
|
diff = (a.onlineInfo?.dispatcherExp || 0) - (b.onlineInfo?.dispatcherExp || 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'user':
|
||||||
|
diff = (b.onlineInfo ? b.onlineInfo.currentUsers : -1) - (a.onlineInfo ? a.onlineInfo.currentUsers : -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'spawn':
|
||||||
|
diff = (a.onlineInfo ? a.onlineInfo.spawns.length : -1) - (b.onlineInfo ? b.onlineInfo.spawns.length : -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'timetableConfirmed':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo?.scheduledTrains
|
||||||
|
? a.onlineInfo.scheduledTrains.filter((train) => train.stopInfo.confirmed).length
|
||||||
|
: -1) -
|
||||||
|
(b.onlineInfo?.scheduledTrains
|
||||||
|
? b.onlineInfo.scheduledTrains.filter((train) => train.stopInfo.confirmed).length
|
||||||
|
: -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'timetableUnconfirmed':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo?.scheduledTrains
|
||||||
|
? a.onlineInfo.scheduledTrains.filter((train) => !train.stopInfo.confirmed).length
|
||||||
|
: -1) -
|
||||||
|
(b.onlineInfo?.scheduledTrains
|
||||||
|
? b.onlineInfo.scheduledTrains.filter((train) => !train.stopInfo.confirmed).length
|
||||||
|
: -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'timetableAll':
|
||||||
|
diff =
|
||||||
|
(a.onlineInfo?.scheduledTrains ? a.onlineInfo.scheduledTrains.length : -1) -
|
||||||
|
(b.onlineInfo?.scheduledTrains ? b.onlineInfo.scheduledTrains.length : -1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff != 0) return Math.sign(diff) * sorter.dir;
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const filterStations = (station: Station, filters: Filter) => {
|
||||||
|
if (!station.onlineInfo && filters['free']) return false;
|
||||||
|
|
||||||
|
if (station.onlineInfo) {
|
||||||
|
const { statusID, statusTimestamp } = station.onlineInfo;
|
||||||
|
|
||||||
|
const isEnding = statusID == 'ending' && filters['endingStatus'];
|
||||||
|
const isNotSigned = (statusID == 'not-signed' || statusID == 'unavailable') && filters['unavailableStatus'];
|
||||||
|
const isAFK = statusID == 'brb' && filters['afkStatus'];
|
||||||
|
const isNoSpace = statusID == 'no-space' && filters['noSpaceStatus'];
|
||||||
|
const isOccupied = station.onlineInfo && filters['occupied'];
|
||||||
|
|
||||||
|
const isOnlineInBounds =
|
||||||
|
(filters['onlineFromHours'] < 8 &&
|
||||||
|
statusTimestamp > 0 &&
|
||||||
|
statusTimestamp <= Date.now() + filters['onlineFromHours'] * 3600000) ||
|
||||||
|
(filters['onlineFromHours'] > 0 && statusTimestamp <= 0) ||
|
||||||
|
(filters['onlineFromHours'] == 8 && statusID != 'no-limit');
|
||||||
|
|
||||||
|
if (isEnding || isOnlineInBounds || isNotSigned || isAFK || isNoSpace || isOccupied) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((station.generalInfo?.availability == 'nonPublic' || !station.generalInfo) && filters['nonPublic']) return false;
|
||||||
|
|
||||||
|
if (station.generalInfo) {
|
||||||
|
const { routes, availability, controlType, lines, reqLevel, signalType, SUP, authors } = station.generalInfo;
|
||||||
|
|
||||||
|
if (availability == 'unavailable' && filters['unavailable'] && !station.onlineInfo) return false;
|
||||||
|
if (availability == 'abandoned' && filters['abandoned'] && !station.onlineInfo) return false;
|
||||||
|
if (availability == 'default' && filters['default']) return false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
availability != 'default' &&
|
||||||
|
filters['notDefault'] &&
|
||||||
|
!(availability == 'abandoned' || availability == 'unavailable')
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (filters['real'] && lines) return false;
|
||||||
|
if (filters['fictional'] && !lines) return false;
|
||||||
|
|
||||||
|
const otherAvailability =
|
||||||
|
availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned';
|
||||||
|
|
||||||
|
if (reqLevel + (otherAvailability ? 1 : 0) < filters['minLevel']) return false;
|
||||||
|
|
||||||
|
if (reqLevel + (otherAvailability ? 1 : 0) > filters['maxLevel']) return false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
filters['no-1track'] &&
|
||||||
|
(routes.oneWayCatenaryRouteNames.length != 0 || routes.oneWayNoCatenaryRouteNames.length != 0)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
filters['no-2track'] &&
|
||||||
|
(routes.twoWayCatenaryRouteNames.length != 0 || routes.twoWayNoCatenaryRouteNames.length != 0)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (routes.oneWayCatenaryRouteNames.length < filters['minOneWayCatenary']) return false;
|
||||||
|
if (routes.oneWayNoCatenaryRouteNames.length < filters['minOneWay']) return false;
|
||||||
|
|
||||||
|
if (routes.twoWayCatenaryRouteNames.length < filters['minTwoWayCatenary']) return false;
|
||||||
|
if (routes.twoWayNoCatenaryRouteNames.length < filters['minTwoWay']) return false;
|
||||||
|
|
||||||
|
if (filters[controlType]) return false;
|
||||||
|
if (filters[signalType]) return false;
|
||||||
|
|
||||||
|
if (filters['SUP'] && SUP) return false;
|
||||||
|
if (filters['noSUP'] && !SUP) return false;
|
||||||
|
|
||||||
|
if (filters['SBL'] && routes.sblRouteNames.length > 0) return false;
|
||||||
|
if (filters['PBL'] && routes.sblRouteNames.length == 0) return false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
filters['authors'].length > 3 &&
|
||||||
|
!authors?.map((a) => a.toLocaleLowerCase()).includes(filters['authors'].toLocaleLowerCase())
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import ScheduledTrain from '../interfaces/ScheduledTrain';
|
import { ScheduledTrain, StopStatus } from '../interfaces/ScheduledTrain';
|
||||||
import Train from '../interfaces/Train';
|
import Train from '../interfaces/Train';
|
||||||
import TrainStop from '../interfaces/TrainStop';
|
import TrainStop from '../interfaces/TrainStop';
|
||||||
|
|
||||||
@@ -74,32 +74,32 @@ export const parseSpawns = (spawnString: string) => {
|
|||||||
export const getTimestamp = (date: string | null): number => (date ? new Date(date).getTime() : 0);
|
export const getTimestamp = (date: string | null): number => (date ? new Date(date).getTime() : 0);
|
||||||
|
|
||||||
export const getTrainStopStatus = (stopInfo: TrainStop, currentStationName: string, stationName: string) => {
|
export const getTrainStopStatus = (stopInfo: TrainStop, currentStationName: string, stationName: string) => {
|
||||||
let stopStatus = '',
|
let stopStatus = StopStatus['arriving'],
|
||||||
stopLabel = '',
|
stopLabel = '',
|
||||||
stopStatusID = -1;
|
stopStatusID = -1;
|
||||||
|
|
||||||
if (stopInfo.terminatesHere && stopInfo.confirmed) {
|
if (stopInfo.terminatesHere && stopInfo.confirmed) {
|
||||||
stopStatus = 'terminated';
|
stopStatus = StopStatus['terminated'];
|
||||||
stopLabel = 'Skończył bieg';
|
stopLabel = 'Skończył bieg';
|
||||||
stopStatusID = 5;
|
stopStatusID = 5;
|
||||||
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName == stationName) {
|
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName == stationName) {
|
||||||
stopStatus = 'departed';
|
stopStatus = StopStatus['departed'];
|
||||||
stopLabel = 'Odprawiony';
|
stopLabel = 'Odprawiony';
|
||||||
stopStatusID = 2;
|
stopStatusID = 2;
|
||||||
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName != stationName) {
|
} else if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName != stationName) {
|
||||||
stopStatus = 'departed-away';
|
stopStatus = StopStatus['departed-away'];
|
||||||
stopLabel = 'Odjechał';
|
stopLabel = 'Odjechał';
|
||||||
stopStatusID = 4;
|
stopStatusID = 4;
|
||||||
} else if (currentStationName == stationName && !stopInfo.stopped) {
|
} else if (currentStationName == stationName && !stopInfo.stopped) {
|
||||||
stopStatus = 'online';
|
stopStatus = StopStatus['online'];
|
||||||
stopLabel = 'Na stacji';
|
stopLabel = 'Na stacji';
|
||||||
stopStatusID = 0;
|
stopStatusID = 0;
|
||||||
} else if (currentStationName == stationName && stopInfo.stopped) {
|
} else if (currentStationName == stationName && stopInfo.stopped) {
|
||||||
stopStatus = 'stopped';
|
stopStatus = StopStatus['stopped'];
|
||||||
stopLabel = 'Postój';
|
stopLabel = 'Postój';
|
||||||
stopStatusID = 1;
|
stopStatusID = 1;
|
||||||
} else if (currentStationName != stationName) {
|
} else if (currentStationName != stationName) {
|
||||||
stopStatus = 'arriving';
|
stopStatus = StopStatus['arriving'];
|
||||||
stopLabel = 'W drodze';
|
stopLabel = 'W drodze';
|
||||||
stopStatusID = 3;
|
stopStatusID = 3;
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ export function getScheduledTrain(train: Train, trainStopIndex: number, stationN
|
|||||||
|
|
||||||
for (let i = trainStopIndex - 1; i >= 0; i--) {
|
for (let i = trainStopIndex - 1; i >= 0; i--) {
|
||||||
if (/strong|podg/g.test(followingStops[i].stopName)) {
|
if (/strong|podg/g.test(followingStops[i].stopName)) {
|
||||||
prevStationName = followingStops[i].stopNameRAW.replace(/,.*/g,"");
|
prevStationName = followingStops[i].stopNameRAW.replace(/,.*/g, '');
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -130,7 +130,7 @@ export function getScheduledTrain(train: Train, trainStopIndex: number, stationN
|
|||||||
|
|
||||||
for (let i = trainStopIndex + 1; i < followingStops.length; i++) {
|
for (let i = trainStopIndex + 1; i < followingStops.length; i++) {
|
||||||
if (/strong|podg/g.test(followingStops[i].stopName)) {
|
if (/strong|podg/g.test(followingStops[i].stopName)) {
|
||||||
nextStationName = followingStops[i].stopNameRAW.replace(/,.*/g,"");
|
nextStationName = followingStops[i].stopNameRAW.replace(/,.*/g, '');
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -172,7 +172,6 @@ export function getScheduledTrain(train: Train, trainStopIndex: number, stationN
|
|||||||
signal: train.signal,
|
signal: train.signal,
|
||||||
connectedTrack: train.connectedTrack,
|
connectedTrack: train.connectedTrack,
|
||||||
|
|
||||||
|
|
||||||
driverName: train.driverName,
|
driverName: train.driverName,
|
||||||
driverId: train.driverId,
|
driverId: train.driverId,
|
||||||
currentStationName: train.currentStationName,
|
currentStationName: train.currentStationName,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export const useJournalFiltersStore = defineStore('journalFiltersStore', {
|
||||||
|
state: () => ({
|
||||||
|
timetableFilters: {
|
||||||
|
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -1,244 +1,29 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import inputData from '../data/options.json';
|
import inputData from '../data/options.json';
|
||||||
import Filter from '../scripts/interfaces/Filter';
|
|
||||||
import Station from '../scripts/interfaces/Station';
|
import Station from '../scripts/interfaces/Station';
|
||||||
import StorageManager from '../scripts/managers/storageManager';
|
import StorageManager from '../scripts/managers/storageManager';
|
||||||
|
import { useStore } from './store';
|
||||||
const sortStations = (a: Station, b: Station, sorter: { index: number; dir: number }) => {
|
import { filterInitStates } from '../scripts/constants/stores/initFilterStates';
|
||||||
switch (sorter.index) {
|
import { filterStations, sortStations } from '../scripts/utils/filterUtils';
|
||||||
case 0:
|
import { HeadIdsTypes } from '../scripts/data/stationHeaderNames';
|
||||||
return sorter.dir == 1 ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
if ((a.generalInfo?.reqLevel || 0) > (b.generalInfo?.reqLevel || 0)) return sorter.dir;
|
|
||||||
if ((a.generalInfo?.reqLevel || 0) < (b.generalInfo?.reqLevel || 0)) return -sorter.dir;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
if ((a.onlineInfo?.statusTimestamp || 0) > (b.onlineInfo?.statusTimestamp || 0)) return sorter.dir;
|
|
||||||
if ((a.onlineInfo?.statusTimestamp || 0) < (b.onlineInfo?.statusTimestamp || 0)) return -sorter.dir;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') > (b.onlineInfo?.dispatcherName.toLowerCase() || ''))
|
|
||||||
return sorter.dir;
|
|
||||||
if ((a.onlineInfo?.dispatcherName.toLowerCase() || '') < (b.onlineInfo?.dispatcherName.toLowerCase() || ''))
|
|
||||||
return -sorter.dir;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
if ((a.onlineInfo?.dispatcherExp || 0) > (b.onlineInfo?.dispatcherExp || 0)) return sorter.dir;
|
|
||||||
if ((a.onlineInfo?.dispatcherExp || 0) < (b.onlineInfo?.dispatcherExp || 0)) return -sorter.dir;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 7:
|
|
||||||
if ((a.onlineInfo?.currentUsers || 0) > (b.onlineInfo?.currentUsers || 0)) return sorter.dir;
|
|
||||||
if ((a.onlineInfo?.currentUsers || 0) < (b.onlineInfo?.currentUsers || 0)) return -sorter.dir;
|
|
||||||
|
|
||||||
if ((a.onlineInfo?.maxUsers || 0) > (b.onlineInfo?.maxUsers || 0)) return sorter.dir;
|
|
||||||
if ((a.onlineInfo?.maxUsers || 0) < (b.onlineInfo?.maxUsers || 0)) return -sorter.dir;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 8:
|
|
||||||
if ((a.onlineInfo?.spawns.length || 0) > (b.onlineInfo?.spawns.length || 0)) return sorter.dir;
|
|
||||||
if ((a.onlineInfo?.spawns.length || 0) < (b.onlineInfo?.spawns.length || 0)) return -sorter.dir;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 9:
|
|
||||||
if ((a.onlineInfo?.scheduledTrains?.length || 0) > (b.onlineInfo?.scheduledTrains?.length || 0))
|
|
||||||
return sorter.dir;
|
|
||||||
if ((a.onlineInfo?.scheduledTrains?.length || 0) < (b.onlineInfo?.scheduledTrains?.length || 0))
|
|
||||||
return -sorter.dir;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterStations = (station: Station, filters: Filter) => {
|
|
||||||
const returnMode = false;
|
|
||||||
|
|
||||||
if ((station.generalInfo?.availability == 'nonPublic' || !station.generalInfo) && filters['nonPublic'])
|
|
||||||
return returnMode;
|
|
||||||
|
|
||||||
if (station.onlineInfo?.statusID == 'ending' && filters['ending']) return returnMode;
|
|
||||||
|
|
||||||
if (
|
|
||||||
station.onlineInfo &&
|
|
||||||
station.onlineInfo.statusTimestamp > 0 &&
|
|
||||||
filters['onlineFromHours'] < 8 &&
|
|
||||||
station.onlineInfo.statusTimestamp <= Date.now() + filters['onlineFromHours'] * 3600000
|
|
||||||
)
|
|
||||||
return returnMode;
|
|
||||||
|
|
||||||
if (filters['onlineFromHours'] > 0 && station.onlineInfo && station.onlineInfo.statusTimestamp <= 0)
|
|
||||||
return returnMode;
|
|
||||||
if (filters['onlineFromHours'] == 8 && station.onlineInfo?.statusID != 'no-limit') return returnMode;
|
|
||||||
|
|
||||||
if (station.onlineInfo?.statusID == 'ending' && filters['endingStatus']) return returnMode;
|
|
||||||
if (
|
|
||||||
(station.onlineInfo?.statusID == 'not-signed' || station.onlineInfo?.statusID == 'unavailable') &&
|
|
||||||
filters['unavailableStatus']
|
|
||||||
)
|
|
||||||
return returnMode;
|
|
||||||
if (station.onlineInfo?.statusID == 'brb' && filters['afkStatus']) return returnMode;
|
|
||||||
if (station.onlineInfo?.statusID == 'no-space' && filters['noSpaceStatus']) return returnMode;
|
|
||||||
|
|
||||||
if (station.onlineInfo && filters['occupied']) return returnMode;
|
|
||||||
if (!station.onlineInfo && filters['free']) return returnMode;
|
|
||||||
if (station.generalInfo?.availability == 'unavailable' && filters['unavailable'] && !station.onlineInfo)
|
|
||||||
return returnMode;
|
|
||||||
|
|
||||||
if (station.generalInfo) {
|
|
||||||
const routes = station.generalInfo.routes;
|
|
||||||
const availability = station.generalInfo.availability;
|
|
||||||
|
|
||||||
if (filters['abandoned'] && availability == 'abandoned' && !station.onlineInfo) return returnMode;
|
|
||||||
|
|
||||||
if (availability == 'default' && filters['default']) return returnMode;
|
|
||||||
if (
|
|
||||||
availability != 'default' &&
|
|
||||||
filters['notDefault'] &&
|
|
||||||
!(availability == 'abandoned' || availability == 'unavailable')
|
|
||||||
)
|
|
||||||
return returnMode;
|
|
||||||
|
|
||||||
if (filters['real'] && station.generalInfo.lines != '') return returnMode;
|
|
||||||
if (
|
|
||||||
filters['fictional'] &&
|
|
||||||
station.generalInfo.lines == '' &&
|
|
||||||
availability != 'abandoned' &&
|
|
||||||
availability != 'unavailable'
|
|
||||||
)
|
|
||||||
return returnMode;
|
|
||||||
|
|
||||||
if (
|
|
||||||
station.generalInfo.reqLevel +
|
|
||||||
(availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned' ? 1 : 0) <
|
|
||||||
filters['minLevel']
|
|
||||||
)
|
|
||||||
return returnMode;
|
|
||||||
if (
|
|
||||||
station.generalInfo.reqLevel +
|
|
||||||
(availability == 'nonPublic' || availability == 'unavailable' || availability == 'abandoned' ? 1 : 0) >
|
|
||||||
filters['maxLevel']
|
|
||||||
)
|
|
||||||
return returnMode;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['no-1track'] &&
|
|
||||||
(routes.oneWayCatenaryRouteNames.length != 0 || routes.oneWayNoCatenaryRouteNames.length != 0)
|
|
||||||
)
|
|
||||||
return returnMode;
|
|
||||||
if (
|
|
||||||
filters['no-2track'] &&
|
|
||||||
(routes.twoWayCatenaryRouteNames.length != 0 || routes.twoWayNoCatenaryRouteNames.length != 0)
|
|
||||||
)
|
|
||||||
return returnMode;
|
|
||||||
|
|
||||||
if (routes.oneWayCatenaryRouteNames.length < filters['minOneWayCatenary']) return returnMode;
|
|
||||||
if (routes.oneWayNoCatenaryRouteNames.length < filters['minOneWay']) return returnMode;
|
|
||||||
|
|
||||||
if (routes.twoWayCatenaryRouteNames.length < filters['minTwoWayCatenary']) return returnMode;
|
|
||||||
if (routes.twoWayNoCatenaryRouteNames.length < filters['minTwoWay']) return returnMode;
|
|
||||||
|
|
||||||
if (filters[station.generalInfo.controlType]) return returnMode;
|
|
||||||
if (filters[station.generalInfo.signalType]) return returnMode;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['SPK'] &&
|
|
||||||
(station.generalInfo.controlType === 'SPK' || station.generalInfo.controlType.includes('+SPK'))
|
|
||||||
)
|
|
||||||
return returnMode;
|
|
||||||
if (
|
|
||||||
filters['SCS'] &&
|
|
||||||
(station.generalInfo.controlType === 'SCS' || station.generalInfo.controlType.includes('+SCS'))
|
|
||||||
)
|
|
||||||
return returnMode;
|
|
||||||
if (
|
|
||||||
filters['SPE'] &&
|
|
||||||
(station.generalInfo.controlType === 'SPE' || station.generalInfo.controlType.includes('+SPE'))
|
|
||||||
)
|
|
||||||
return returnMode;
|
|
||||||
if (filters['SUP'] && station.generalInfo.SUP) return returnMode;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['SCS'] &&
|
|
||||||
filters['SPK'] &&
|
|
||||||
(station.generalInfo.controlType.includes('SPK') || station.generalInfo.controlType.includes('SCS'))
|
|
||||||
)
|
|
||||||
return returnMode;
|
|
||||||
|
|
||||||
if (filters['mechaniczne'] && station.generalInfo.controlType.includes('mechaniczne')) return returnMode;
|
|
||||||
|
|
||||||
if (filters['ręczne'] && station.generalInfo.controlType.includes('ręczne')) return returnMode;
|
|
||||||
|
|
||||||
if (filters['SBL'] && routes.sblRouteNames.length > 0) return returnMode;
|
|
||||||
|
|
||||||
if (
|
|
||||||
filters['authors'].length > 3 &&
|
|
||||||
!station.generalInfo.authors?.map((a) => a.toLocaleLowerCase()).includes(filters['authors'].toLocaleLowerCase())
|
|
||||||
)
|
|
||||||
return returnMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterInitStates: Filter = {
|
|
||||||
default: false,
|
|
||||||
notDefault: false,
|
|
||||||
real: false,
|
|
||||||
fictional: false,
|
|
||||||
SPK: false,
|
|
||||||
SCS: false,
|
|
||||||
SPE: false,
|
|
||||||
SUP: false,
|
|
||||||
ręczne: false,
|
|
||||||
mechaniczne: false,
|
|
||||||
współczesna: false,
|
|
||||||
kształtowa: false,
|
|
||||||
historyczna: false,
|
|
||||||
mieszana: false,
|
|
||||||
SBL: false,
|
|
||||||
minLevel: 0,
|
|
||||||
maxLevel: 20,
|
|
||||||
minOneWayCatenary: 0,
|
|
||||||
minOneWay: 0,
|
|
||||||
minTwoWayCatenary: 0,
|
|
||||||
minTwoWay: 0,
|
|
||||||
'include-selected': false,
|
|
||||||
'no-1track': false,
|
|
||||||
'no-2track': false,
|
|
||||||
free: true,
|
|
||||||
occupied: false,
|
|
||||||
ending: false,
|
|
||||||
nonPublic: false,
|
|
||||||
unavailable: true,
|
|
||||||
abandoned: true,
|
|
||||||
afkStatus: false,
|
|
||||||
endingStatus: false,
|
|
||||||
noSpaceStatus: false,
|
|
||||||
unavailableStatus: false,
|
|
||||||
unsignedStatus: false,
|
|
||||||
|
|
||||||
authors: '',
|
|
||||||
|
|
||||||
onlineFromHours: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useStationFiltersStore = defineStore('stationFiltersStore', {
|
export const useStationFiltersStore = defineStore('stationFiltersStore', {
|
||||||
state() {
|
state() {
|
||||||
return {
|
return {
|
||||||
inputs: inputData,
|
inputs: inputData,
|
||||||
filters: { ...filterInitStates },
|
filters: { ...filterInitStates },
|
||||||
sorterActive: { index: 0, dir: 1 },
|
sorterActive: { headerName: 'station' as HeadIdsTypes, dir: 1 },
|
||||||
|
store: useStore(),
|
||||||
|
lastClickedFilterId: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
areFiltersAtDefault(state) {
|
||||||
|
return Object.keys(state.filters).every((f) => state.filters[f] === filterInitStates[f]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
getFilteredStationList(stationList: Station[], region: string): Station[] {
|
getFilteredStationList(stationList: Station[], region: string): Station[] {
|
||||||
return stationList
|
return stationList
|
||||||
@@ -257,10 +42,10 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
|
|||||||
if (!StorageManager.isRegistered('options_saved')) return;
|
if (!StorageManager.isRegistered('options_saved')) return;
|
||||||
|
|
||||||
this.inputs.options.forEach((option) => {
|
this.inputs.options.forEach((option) => {
|
||||||
if (!StorageManager.isRegistered(option.id)) return;
|
if (!StorageManager.isRegistered(option.name)) return;
|
||||||
const savedValue = StorageManager.getBooleanValue(option.id);
|
const savedValue = StorageManager.getBooleanValue(option.name);
|
||||||
|
|
||||||
this.filters[option.id] = savedValue;
|
this.filters[option.name] = savedValue;
|
||||||
option.value = !savedValue;
|
option.value = !savedValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -293,13 +78,22 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
changeSorter(index: number) {
|
resetSectionOptions(section: string) {
|
||||||
if (index > 4 && index < 7) return;
|
this.inputs.options.forEach((option) => {
|
||||||
|
if (option.section != section) return;
|
||||||
|
|
||||||
if (index == this.sorterActive.index) this.sorterActive.dir = -1 * this.sorterActive.dir;
|
option.value = option.defaultValue;
|
||||||
|
this.filters[option.id] = !option.defaultValue;
|
||||||
|
|
||||||
|
StorageManager.setBooleanValue(option.name, !option.defaultValue);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
changeSorter(headerName: HeadIdsTypes) {
|
||||||
|
if (headerName == this.sorterActive.headerName) this.sorterActive.dir = -1 * this.sorterActive.dir;
|
||||||
else this.sorterActive.dir = 1;
|
else this.sorterActive.dir = 1;
|
||||||
|
|
||||||
this.sorterActive.index = index;
|
this.sorterActive.headerName = headerName;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { defineStore } from 'pinia';
|
|||||||
import { io } from 'socket.io-client';
|
import { io } from 'socket.io-client';
|
||||||
import { DataStatus } from '../scripts/enums/DataStatus';
|
import { DataStatus } from '../scripts/enums/DataStatus';
|
||||||
import StationAPIData from '../scripts/interfaces/api/StationAPIData';
|
import StationAPIData from '../scripts/interfaces/api/StationAPIData';
|
||||||
import ScheduledTrain from '../scripts/interfaces/ScheduledTrain';
|
import { ScheduledTrain } from '../scripts/interfaces/ScheduledTrain';
|
||||||
import Station from '../scripts/interfaces/Station';
|
import Station from '../scripts/interfaces/Station';
|
||||||
import StationRoutes from '../scripts/interfaces/StationRoutes';
|
import StationRoutes from '../scripts/interfaces/StationRoutes';
|
||||||
import Train from '../scripts/interfaces/Train';
|
import Train from '../scripts/interfaces/Train';
|
||||||
@@ -15,8 +15,7 @@ import {
|
|||||||
getScheduledTrain,
|
getScheduledTrain,
|
||||||
parseSpawns,
|
parseSpawns,
|
||||||
} from '../scripts/utils/storeUtils';
|
} from '../scripts/utils/storeUtils';
|
||||||
import { APIData, StationJSONData, StoreState } from './storeTypes';
|
import { APIData, StationJSONData, StoreState } from '../scripts/interfaces/store/storeTypes';
|
||||||
|
|
||||||
|
|
||||||
export const useStore = defineStore('store', {
|
export const useStore = defineStore('store', {
|
||||||
state: () =>
|
state: () =>
|
||||||
@@ -25,6 +24,7 @@ export const useStore = defineStore('store', {
|
|||||||
|
|
||||||
stationList: [],
|
stationList: [],
|
||||||
trainList: [],
|
trainList: [],
|
||||||
|
routesList: [],
|
||||||
|
|
||||||
sceneryData: [],
|
sceneryData: [],
|
||||||
lastDispatcherStatuses: [],
|
lastDispatcherStatuses: [],
|
||||||
@@ -35,12 +35,14 @@ export const useStore = defineStore('store', {
|
|||||||
stationCount: 0,
|
stationCount: 0,
|
||||||
|
|
||||||
webSocket: undefined,
|
webSocket: undefined,
|
||||||
|
isOffline: false,
|
||||||
|
|
||||||
dispatcherStatsName: '',
|
dispatcherStatsName: '',
|
||||||
dispatcherStatsData: undefined,
|
dispatcherStatsData: undefined,
|
||||||
|
|
||||||
driverStatsName: '',
|
driverStatsName: '',
|
||||||
driverStatsData: undefined,
|
driverStatsData: undefined,
|
||||||
|
driverStatsStatus: DataStatus.Initialized,
|
||||||
|
|
||||||
chosenModalTrainId: undefined,
|
chosenModalTrainId: undefined,
|
||||||
|
|
||||||
@@ -52,9 +54,10 @@ export const useStore = defineStore('store', {
|
|||||||
trains: DataStatus.Loading,
|
trains: DataStatus.Loading,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
currentStatsTab: null,
|
||||||
|
|
||||||
blockScroll: false,
|
blockScroll: false,
|
||||||
listenerLaunched: false,
|
listenerLaunched: false,
|
||||||
|
|
||||||
} as StoreState),
|
} as StoreState),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
@@ -98,6 +101,9 @@ export const useStore = defineStore('store', {
|
|||||||
lastSeen: train.lastSeen,
|
lastSeen: train.lastSeen,
|
||||||
isTimeout: train.isTimeout,
|
isTimeout: train.isTimeout,
|
||||||
|
|
||||||
|
isSupporter: train.driverIsSupporter,
|
||||||
|
driverLevel: train.driverLevel,
|
||||||
|
|
||||||
timetableData: timetable
|
timetableData: timetable
|
||||||
? {
|
? {
|
||||||
timetableId: timetable.timetableId,
|
timetableId: timetable.timetableId,
|
||||||
@@ -110,8 +116,8 @@ export const useStore = defineStore('store', {
|
|||||||
sceneries: timetable.sceneries,
|
sceneries: timetable.sceneries,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
} as Train;
|
||||||
}) as Train[];
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getDispatcherStatus(onlineStationData: StationAPIData) {
|
getDispatcherStatus(onlineStationData: StationAPIData) {
|
||||||
@@ -220,6 +226,14 @@ export const useStore = defineStore('store', {
|
|||||||
const onlineStationNames: string[] = [];
|
const onlineStationNames: string[] = [];
|
||||||
const prevDispatcherStatuses: StoreState['lastDispatcherStatuses'] = [];
|
const prevDispatcherStatuses: StoreState['lastDispatcherStatuses'] = [];
|
||||||
|
|
||||||
|
if (this.isOffline) {
|
||||||
|
this.stationList.forEach((station) => {
|
||||||
|
station.onlineInfo = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.apiData.stations?.forEach((stationAPIData) => {
|
this.apiData.stations?.forEach((stationAPIData) => {
|
||||||
if (stationAPIData.region !== this.region.id || !stationAPIData.isOnline) return;
|
if (stationAPIData.region !== this.region.id || !stationAPIData.isOnline) return;
|
||||||
const station = this.stationList.find((s) => s.name === stationAPIData.stationName);
|
const station = this.stationList.find((s) => s.name === stationAPIData.stationName);
|
||||||
@@ -281,47 +295,34 @@ export const useStore = defineStore('store', {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stationList = sceneryData.map((scenery) => ({
|
this.stationList = sceneryData.map((scenery) => {
|
||||||
name: scenery.name,
|
return {
|
||||||
|
name: scenery.name,
|
||||||
|
|
||||||
generalInfo: {
|
generalInfo: {
|
||||||
...scenery,
|
...scenery,
|
||||||
authors: scenery.authors?.split(',').map((a) => a.trim()),
|
authors: scenery.authors?.split(',').map((a) => a.trim()),
|
||||||
routes:
|
routes:
|
||||||
scenery.routes
|
scenery.routesInfo.reduce(
|
||||||
?.split(';')
|
(acc, route) => {
|
||||||
.filter((routeString) => routeString)
|
const propName: keyof StationRoutes = `${route.routeTracks == 2 ? 'twoWay' : 'oneWay'}${
|
||||||
.reduce(
|
route.isElectric ? '' : 'No'
|
||||||
(acc, routeString) => {
|
}CatenaryRouteNames`;
|
||||||
const specs1 = routeString.split('_')[0];
|
|
||||||
const isInternal = specs1.startsWith('!');
|
|
||||||
const name = isInternal ? specs1.replace('!', '') : specs1;
|
|
||||||
|
|
||||||
const specs2 = routeString.split('_')[1].split('');
|
acc[route.routeTracks == 2 ? 'twoWay' : 'oneWay'].push({
|
||||||
const twoWay = specs2[0] == '2';
|
name: route.routeName,
|
||||||
const catenary = specs2[1] == 'E';
|
SBL: route.isRouteSBL,
|
||||||
const SBL = specs2[2] == 'S';
|
TWB: false,
|
||||||
const TWB = specs2[3] ? true : false;
|
catenary: route.isElectric,
|
||||||
|
isInternal: route.isInternal,
|
||||||
const propName = twoWay
|
tracks: route.routeTracks,
|
||||||
? catenary
|
length: route.routeLength,
|
||||||
? 'twoWayCatenaryRouteNames'
|
speed: route.routeSpeed,
|
||||||
: 'twoWayNoCatenaryRouteNames'
|
|
||||||
: catenary
|
|
||||||
? 'oneWayCatenaryRouteNames'
|
|
||||||
: 'oneWayNoCatenaryRouteNames';
|
|
||||||
|
|
||||||
acc[twoWay ? 'twoWay' : 'oneWay'].push({
|
|
||||||
name,
|
|
||||||
SBL,
|
|
||||||
TWB,
|
|
||||||
catenary,
|
|
||||||
isInternal,
|
|
||||||
tracks: twoWay ? 2 : 1,
|
|
||||||
});
|
});
|
||||||
if (!isInternal) acc[propName].push(name);
|
|
||||||
|
|
||||||
if (SBL) acc['sblRouteNames'].push(name);
|
if (!route.isInternal) acc[propName].push(route.routeName);
|
||||||
|
|
||||||
|
if (route.isRouteSBL) acc['sblRouteNames'].push(route.routeName);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
@@ -335,24 +336,23 @@ export const useStore = defineStore('store', {
|
|||||||
twoWayNoCatenaryRouteNames: [],
|
twoWayNoCatenaryRouteNames: [],
|
||||||
} as StationRoutes
|
} as StationRoutes
|
||||||
) || {},
|
) || {},
|
||||||
checkpoints: scenery.checkpoints
|
checkpoints: scenery.checkpoints
|
||||||
? scenery.checkpoints.split(';').map((sub) => ({ checkpointName: sub, scheduledTrains: [] }))
|
? scenery.checkpoints.split(';').map((sub) => ({ checkpointName: sub, scheduledTrains: [] }))
|
||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
connectToWebsocket() {
|
connectToWebsocket() {
|
||||||
const socket = io(URLs.stacjownikAPI, {
|
const socket = io(URLs.stacjownikAPI, {
|
||||||
transports: ['websocket', 'polling'],
|
// transports: ['websocket', 'polling'],
|
||||||
rememberUpgrade: true,
|
rememberUpgrade: true,
|
||||||
reconnection: true,
|
reconnection: true,
|
||||||
timeout: 10000,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('connect_error', (err) => {
|
socket.on('connect_error', (err) => {
|
||||||
this.dataStatuses.connection = DataStatus.Error;
|
this.dataStatuses.connection = DataStatus.Error;
|
||||||
this.webSocket = undefined;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('UPDATE', (data: APIData) => {
|
socket.on('UPDATE', (data: APIData) => {
|
||||||
@@ -362,6 +362,8 @@ export const useStore = defineStore('store', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.emit('FETCH_DATA', {}, (data: APIData) => {
|
socket.emit('FETCH_DATA', {}, (data: APIData) => {
|
||||||
|
this.dataStatuses.connection = DataStatus.Loaded;
|
||||||
|
|
||||||
this.apiData = data;
|
this.apiData = data;
|
||||||
this.setOnlineData();
|
this.setOnlineData();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,21 +1,5 @@
|
|||||||
@import 'responsive.scss';
|
@import 'responsive.scss';
|
||||||
|
@import 'animations.scss';
|
||||||
// Animations
|
|
||||||
.warning {
|
|
||||||
&-enter-from,
|
|
||||||
&-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-active {
|
|
||||||
transition: all 150ms 100ms ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-leave-active {
|
|
||||||
transition: all 150ms 100ms ease-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Styles
|
//Styles
|
||||||
|
|
||||||
.list_wrapper {
|
.list_wrapper {
|
||||||
@@ -26,10 +10,16 @@
|
|||||||
padding-right: 0.2em;
|
padding-right: 0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.journal-list {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.journal_wrapper {
|
.journal_wrapper {
|
||||||
max-width: 1350px;
|
max-width: 1350px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
padding: 1em 0;
|
padding: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +47,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn--load-data {
|
.btn--load-data {
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
@import 'variables.scss';
|
@import 'variables.scss';
|
||||||
@import 'responsive.scss';
|
@import 'responsive.scss';
|
||||||
|
|
||||||
.journal-stats {
|
.stats-tab {
|
||||||
background-color: #1a1a1a;
|
background-color: #1a1a1a;
|
||||||
|
box-shadow: 0 0 5px 1px $accentCol;
|
||||||
|
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin-bottom: 1em;
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-stats {
|
.info-stats {
|
||||||
@@ -40,3 +47,4 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
.list-anim-move,
|
||||||
|
.list-anim-enter-active,
|
||||||
|
.list-anim-leave-active {
|
||||||
|
transition: all 250ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-anim-enter-from,
|
||||||
|
.list-anim-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-anim-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-anim {
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-active {
|
||||||
|
transition: all 100ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-leave-active {
|
||||||
|
transition: all 100ms ease-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,11 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
background: #585858;
|
|
||||||
|
|
||||||
margin: 0.25em;
|
margin: 0.25em;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
background: #585858;
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,3 +25,56 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.level-badge {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.9em;
|
||||||
|
|
||||||
|
&.driver {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 1.7em;
|
||||||
|
height: 1.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dispatcher {
|
||||||
|
border-radius: 0.25em;
|
||||||
|
|
||||||
|
width: 1.6em;
|
||||||
|
height: 1.6em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-badge {
|
||||||
|
padding: 0 0.5em;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&.eu {
|
||||||
|
background-color: forestgreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.train-badge {
|
||||||
|
padding: 0.1em 0.2em;
|
||||||
|
border-radius: 0.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
font-size: 0.9em;
|
||||||
|
|
||||||
|
&.twr {
|
||||||
|
background-color: var(--clr-twr);
|
||||||
|
box-shadow: 0 0 5px 1px var(--clr-twr);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.skr {
|
||||||
|
background-color: var(--clr-skr);
|
||||||
|
box-shadow: 0 0 5px 1px var(--clr-skr);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.offline {
|
||||||
|
background-color: #be3728;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,17 +21,16 @@
|
|||||||
|
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
overflow-x: hidden;
|
overflow: hidden;
|
||||||
background: #202020da;
|
background: #202020e8;
|
||||||
|
|
||||||
box-shadow: 0 0 15px 5px #303030;
|
box-shadow: 0 0 15px 0 black;
|
||||||
|
border: 1px solid #202020e8;
|
||||||
|
|
||||||
width: 600px;
|
width: 95%;
|
||||||
|
max-width: 700px;
|
||||||
|
|
||||||
@include smallScreen {
|
max-height: 95vh;
|
||||||
width: 100%;
|
|
||||||
height: 80vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-exit {
|
&-exit {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -46,3 +45,9 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.card {
|
||||||
|
max-height: 85vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,24 @@
|
|||||||
@import 'variables.scss';
|
@import 'variables.scss';
|
||||||
@import 'search_box.scss';
|
@import 'search_box.scss';
|
||||||
|
|
||||||
|
.filters-options {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-bar {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-button .active-indicator {
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
background-color: lightgreen;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
h1.option-title {
|
h1.option-title {
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
@@ -42,22 +60,16 @@ h1.option-title {
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filters-options {
|
|
||||||
position: relative;
|
|
||||||
margin-bottom: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options_wrapper {
|
.options_wrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
background-color: $bgCol;
|
background-color: $bgCol;
|
||||||
box-shadow: 0 5px 10px 2px #0f0f0f;
|
box-shadow: 0 5px 10px 2px #0f0f0f;
|
||||||
|
|
||||||
width: 100%;
|
width: 97%;
|
||||||
max-width: 500px;
|
max-width: 550px;
|
||||||
|
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,24 +77,30 @@ h1.option-title {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
padding: 0.25em 0.25em 0 0;
|
padding: 0.25em 0.25em 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.options_filter-sections section {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
.options_filters {
|
.options_filters {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin: 0.5em 0 0 0;
|
|
||||||
|
gap: 0.5em;
|
||||||
|
margin: 0.25em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sort-option,
|
.sort-option,
|
||||||
.filter-option {
|
.filter-option {
|
||||||
margin: 0.25em 0.25em 0.25em 0;
|
padding: 0.25em 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sort-option[data-selected='true'] {
|
.sort-option[data-selected='true'] {
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-option {
|
.filter-option {
|
||||||
@@ -104,17 +122,6 @@ h1.option-title {
|
|||||||
margin: 0.5em auto;
|
margin: 0.5em auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search_actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5em;
|
|
||||||
margin: 1em 0;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-box {
|
.search-box {
|
||||||
.search-exit {
|
.search-exit {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -125,6 +132,17 @@ h1.option-title {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.options_actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@include smallScreen() {
|
@include smallScreen() {
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -141,13 +159,12 @@ h1.option-title {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-option,
|
|
||||||
.sort-option {
|
|
||||||
margin: 0.25em 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options_filters,
|
.options_filters,
|
||||||
.options_sorters {
|
.options_sorters {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-section {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,19 +18,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 1rem;
|
width: 15px;
|
||||||
height: 1rem;
|
height: 15px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
&-track {
|
&-track {
|
||||||
border-radius: 0.5em;
|
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-thumb {
|
&-thumb {
|
||||||
border-radius: 0.5em;
|
|
||||||
background-color: #666;
|
background-color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-corner {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
@@ -43,11 +45,12 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: 'Quicksand', sans-serif;
|
font-family: 'Quicksand', sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|
||||||
&.no-scroll {
|
&.no-scroll {
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
padding-right: 1rem;
|
padding-right: 15px;
|
||||||
|
|
||||||
@include smallScreen() {
|
@include smallScreen() {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -79,22 +82,9 @@ body {
|
|||||||
|
|
||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
|
|
||||||
// @include smallScreen() {
|
|
||||||
// right: 0;
|
|
||||||
// left: 0;
|
|
||||||
|
|
||||||
// &::after {
|
|
||||||
// left: 75%;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover > .content {
|
&:hover > .content {
|
||||||
// @include smallScreen() {
|
|
||||||
// display: none;
|
|
||||||
// }
|
|
||||||
|
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@@ -103,7 +93,6 @@ body {
|
|||||||
button,
|
button,
|
||||||
input,
|
input,
|
||||||
select {
|
select {
|
||||||
// font-family: "Open Sans", sans-serif;
|
|
||||||
border: none;
|
border: none;
|
||||||
font-family: 'Quicksand', sans-serif;
|
font-family: 'Quicksand', sans-serif;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
@@ -207,10 +196,16 @@ button {
|
|||||||
padding: 0.25em 0.5em;
|
padding: 0.25em 0.5em;
|
||||||
|
|
||||||
transition: all 100ms ease;
|
transition: all 100ms ease;
|
||||||
|
|
||||||
|
&[data-disabled='true'] {
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button.btn--filled {
|
button.btn--filled {
|
||||||
background-color: #333;
|
background-color: #1a1a1a;
|
||||||
border-radius: 0.25em;
|
border-radius: 0.25em;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -1,18 +1,23 @@
|
|||||||
@mixin smallScreen() {
|
@mixin smallScreen() {
|
||||||
@media only screen and (max-width: 700px) {
|
@media only screen and (max-width: 700px) {
|
||||||
@content;
|
@content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@mixin midScreen() {
|
@mixin midScreen() {
|
||||||
@media only screen and (max-width: 1150px) {
|
@media only screen and (max-width: 1150px) {
|
||||||
@content;
|
@content;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin screenLandscape() {
|
||||||
|
@media only screen and (orientation: landscape) and (max-device-height: 450px) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin bigScreen() {
|
@mixin bigScreen() {
|
||||||
@media only screen and (min-width: 2000px) {
|
@media only screen and (min-width: 2000px) {
|
||||||
@content;
|
@content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { JournalFilterType } from '../../scripts/enums/JournalFilterType';
|
|
||||||
|
|
||||||
export type JorunalTimetableSearchType = {
|
|
||||||
[key in 'search-driver' | 'search-train' | 'search-date' | 'search-author']: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface JournalTimetableFilter {
|
|
||||||
id: JournalFilterType;
|
|
||||||
filterSection: string;
|
|
||||||
isActive: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface JournalTimetableSorter {
|
|
||||||
id: 'timetableId' | 'beginDate' | 'distance' | 'total-stops';
|
|
||||||
dir: -1 | 1;
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { TrainFilterType } from "../../scripts/enums/TrainFilterType";
|
|
||||||
|
|
||||||
export interface TrainFilter {
|
|
||||||
id: TrainFilterType;
|
|
||||||
isActive: boolean;
|
|
||||||
}
|
|
||||||
@@ -1,39 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="journal-timetables">
|
<section class="journal-timetables">
|
||||||
|
<JournalHeader />
|
||||||
|
|
||||||
<div class="journal_wrapper">
|
<div class="journal_wrapper">
|
||||||
<JournalOptions
|
<JournalOptions
|
||||||
@on-search-confirm="searchHistory"
|
@on-search-confirm="fetchHistoryData"
|
||||||
@on-options-reset="resetOptions"
|
@on-options-reset="resetOptions"
|
||||||
|
@on-refresh-data="fetchHistoryData"
|
||||||
:sorter-option-ids="['timestampFrom', 'duration']"
|
:sorter-option-ids="['timestampFrom', 'duration']"
|
||||||
:data-status="dataStatus"
|
:data-status="dataStatus"
|
||||||
|
:current-options-active="currentOptionsActive"
|
||||||
|
optionsType="dispatchers"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="list_wrapper" @scroll="handleScroll">
|
<div class="list_wrapper" @scroll="handleScroll">
|
||||||
<!-- <transition name="warning" mode="out-in"> -->
|
<transition name="status-anim" mode="out-in">
|
||||||
<!-- <div :key="dataStatus"> -->
|
<div :key="dataStatus">
|
||||||
<Loading v-if="dataStatus == DataStatus.Initialized || dataStatus == DataStatus.Loading" />
|
<div class="journal_warning" v-if="store.isOffline">
|
||||||
|
{{ $t('app.offline') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
|
<Loading v-else-if="dataStatus == DataStatus.Loading" />
|
||||||
{{ $t('app.error') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal_warning" v-else-if="historyList.length == 0">
|
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
|
||||||
{{ $t('app.no-result') }}
|
{{ $t('app.error') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else>
|
<div class="journal_warning" v-else-if="historyList.length == 0">
|
||||||
<JournalDispatchersList :dispatcherHistory="computedHistoryList" />
|
{{ $t('app.no-result') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<div v-else>
|
||||||
class="btn btn--option btn--load-data"
|
<JournalDispatchersList :dispatcherHistory="computedHistoryList" />
|
||||||
v-if="!scrollNoMoreData && scrollDataLoaded && computedHistoryList.length > 15"
|
|
||||||
@click="addHistoryData"
|
<button
|
||||||
>
|
class="btn btn--option btn--load-data"
|
||||||
{{ $t('journal.load-data') }}
|
v-if="!scrollNoMoreData && scrollDataLoaded && computedHistoryList.length > 15"
|
||||||
</button>
|
@click="addHistoryData"
|
||||||
</div>
|
>
|
||||||
<!-- </div>
|
{{ $t('journal.load-data') }}
|
||||||
</transition> -->
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
<div class="journal_warning" v-if="scrollNoMoreData">
|
<div class="journal_warning" v-if="scrollNoMoreData">
|
||||||
{{ $t('journal.no-further-data') }}
|
{{ $t('journal.no-further-data') }}
|
||||||
@@ -51,24 +60,33 @@
|
|||||||
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
|
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import ActionButton from '../../components/Global/ActionButton.vue';
|
import ActionButton from '../components/Global/ActionButton.vue';
|
||||||
import JournalOptions from '../../components/JournalView/JournalOptions.vue';
|
import JournalOptions from '../components/JournalView/JournalOptions.vue';
|
||||||
import DispatcherStats from '../../components/JournalView/DispatcherStats.vue';
|
import DispatcherStats from '../components/JournalView/DispatcherStats.vue';
|
||||||
import SearchBox from '../Global/SearchBox.vue';
|
import SearchBox from '../components/Global/SearchBox.vue';
|
||||||
|
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../components/Global/Loading.vue';
|
||||||
import { URLs } from '../../scripts/utils/apiURLs';
|
import { URLs } from '../scripts/utils/apiURLs';
|
||||||
import { DataStatus } from '../../scripts/enums/DataStatus';
|
import { DataStatus } from '../scripts/enums/DataStatus';
|
||||||
import { useStore } from '../../store/store';
|
import { useStore } from '../store/store';
|
||||||
import JournalDispatchersList from './JournalDispatchersList.vue';
|
import JournalDispatchersList from '../components/JournalView/JournalDispatchersList.vue';
|
||||||
import { JournalDispatcherSearcher, JournalDispatcherSorter } from '../../types/Journal/JournalDispatcherTypes';
|
import { JournalDispatcherSearcher, JournalDispatcherSorter } from '../scripts/types/JournalDispatcherTypes';
|
||||||
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
|
import { DispatcherHistory } from '../scripts/interfaces/api/DispatchersAPIData';
|
||||||
import { JournalTimetableFilter } from '../../types/Journal/JournalTimetablesTypes';
|
import JournalHeader from '../components/JournalView/JournalHeader.vue';
|
||||||
|
import { LocationQuery } from 'vue-router';
|
||||||
|
|
||||||
const DISPATCHERS_API_URL = `${URLs.stacjownikAPI}/api/getDispatchers`;
|
const DISPATCHERS_API_URL = `${URLs.stacjownikAPI}/api/getDispatchers`;
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { SearchBox, ActionButton, JournalOptions, DispatcherStats, Loading, JournalDispatchersList },
|
components: {
|
||||||
|
SearchBox,
|
||||||
|
ActionButton,
|
||||||
|
JournalOptions,
|
||||||
|
DispatcherStats,
|
||||||
|
Loading,
|
||||||
|
JournalDispatchersList,
|
||||||
|
JournalHeader,
|
||||||
|
},
|
||||||
name: 'JournalDispatchers',
|
name: 'JournalDispatchers',
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
@@ -85,13 +103,16 @@ export default defineComponent({
|
|||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
currentQuery: '',
|
currentQuery: '',
|
||||||
|
currentQueryArray: [] as string[],
|
||||||
|
|
||||||
scrollDataLoaded: true,
|
scrollDataLoaded: true,
|
||||||
scrollNoMoreData: false,
|
scrollNoMoreData: false,
|
||||||
|
|
||||||
showReturnButton: false,
|
showReturnButton: false,
|
||||||
statsCardOpen: false,
|
statsCardOpen: false,
|
||||||
|
currentOptionsActive: false,
|
||||||
|
|
||||||
dataStatus: DataStatus.Initialized,
|
dataStatus: DataStatus.Loading,
|
||||||
DataStatus,
|
DataStatus,
|
||||||
|
|
||||||
historyList: [] as DispatcherHistory[],
|
historyList: [] as DispatcherHistory[],
|
||||||
@@ -113,6 +134,7 @@ export default defineComponent({
|
|||||||
provide('sorterActive', sorterActive);
|
provide('sorterActive', sorterActive);
|
||||||
provide('journalFilterActive', journalFilterActive);
|
provide('journalFilterActive', journalFilterActive);
|
||||||
provide('searchersValues', searchersValues);
|
provide('searchersValues', searchersValues);
|
||||||
|
provide('filterList', reactive([]));
|
||||||
|
|
||||||
const scrollElement: Ref<HTMLElement | null> = ref(null);
|
const scrollElement: Ref<HTMLElement | null> = ref(null);
|
||||||
|
|
||||||
@@ -130,6 +152,13 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
currentQueryArray(q: string[]) {
|
||||||
|
this.currentOptionsActive =
|
||||||
|
q.length > 2 || q.some((qv) => qv.startsWith('sortBy=') && qv.split('=')[1] != 'timestampFrom');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
computedHistoryList() {
|
computedHistoryList() {
|
||||||
return this.historyList.filter(
|
return this.historyList.filter(
|
||||||
@@ -138,18 +167,14 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
activated() {
|
beforeRouteUpdate(to, _) {
|
||||||
if (this.sceneryName || this.dispatcherName) {
|
this.handleQueries(to.query);
|
||||||
this.searchersValues['search-station'] = this.sceneryName?.toString() || '';
|
this.fetchHistoryData();
|
||||||
this.searchersValues['search-dispatcher'] = this.dispatcherName?.toString() || '';
|
|
||||||
this.searchHistory();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
activated() {
|
||||||
if (!this.sceneryName && !this.dispatcherName) {
|
this.handleQueries(this.$route.query);
|
||||||
this.searchHistory();
|
this.fetchHistoryData();
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -163,21 +188,22 @@ export default defineComponent({
|
|||||||
if (scrollTop > elementHeight * 0.85) this.addHistoryData();
|
if (scrollTop > elementHeight * 0.85) this.addHistoryData();
|
||||||
},
|
},
|
||||||
|
|
||||||
resetOptions() {
|
handleQueries(query: LocationQuery) {
|
||||||
this.searchersValues['search-station'] = '';
|
if ('sceneryName' in query) this.searchersValues['search-station'] = `${query.sceneryName}`;
|
||||||
this.searchersValues['search-dispatcher'] = '';
|
if ('dispatcherName' in query) this.searchersValues['search-dispatcher'] = `${query.dispatcherName}`;
|
||||||
this.sorterActive.id = 'timestampFrom';
|
|
||||||
|
|
||||||
this.searchHistory();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
searchHistory() {
|
setSearchers(date: string, station: string, dispatcher: string) {
|
||||||
this.fetchHistoryData({
|
this.searchersValues['search-date'] = date;
|
||||||
searchers: this.searchersValues,
|
this.searchersValues['search-station'] = station;
|
||||||
});
|
this.searchersValues['search-dispatcher'] = dispatcher;
|
||||||
|
},
|
||||||
|
|
||||||
this.scrollNoMoreData = false;
|
resetOptions() {
|
||||||
this.scrollDataLoaded = true;
|
this.setSearchers('', '', '');
|
||||||
|
this.sorterActive.id = 'timestampFrom';
|
||||||
|
|
||||||
|
this.fetchHistoryData();
|
||||||
},
|
},
|
||||||
|
|
||||||
async addHistoryData() {
|
async addHistoryData() {
|
||||||
@@ -200,19 +226,13 @@ export default defineComponent({
|
|||||||
this.scrollDataLoaded = true;
|
this.scrollDataLoaded = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchHistoryData(
|
async fetchHistoryData() {
|
||||||
props: {
|
|
||||||
searchers?: JournalDispatcherSearcher;
|
|
||||||
filter?: JournalTimetableFilter;
|
|
||||||
} = {}
|
|
||||||
) {
|
|
||||||
this.dataStatus = DataStatus.Loading;
|
|
||||||
|
|
||||||
const queries: string[] = [];
|
const queries: string[] = [];
|
||||||
|
|
||||||
const dispatcher = props.searchers?.['search-dispatcher'].trim();
|
const dispatcher = this.searchersValues['search-dispatcher'].trim();
|
||||||
const station = props.searchers?.['search-station'].trim();
|
const station = this.searchersValues['search-station'].trim();
|
||||||
const dateString = props.searchers?.['search-date'].trim();
|
const dateString = this.searchersValues['search-date'].trim();
|
||||||
|
|
||||||
const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
|
const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
|
||||||
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
|
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
|
||||||
|
|
||||||
@@ -227,7 +247,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
queries.push('countLimit=30');
|
queries.push('countLimit=30');
|
||||||
|
|
||||||
|
if (this.currentQuery != queries.join('&')) this.dataStatus = DataStatus.Loading;
|
||||||
|
|
||||||
this.currentQuery = queries.join('&');
|
this.currentQuery = queries.join('&');
|
||||||
|
this.currentQueryArray = queries;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const responseData: DispatcherHistory[] = await (
|
const responseData: DispatcherHistory[] = await (
|
||||||
@@ -254,11 +277,14 @@ export default defineComponent({
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.dataStatus = DataStatus.Error;
|
this.dataStatus = DataStatus.Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.scrollNoMoreData = false;
|
||||||
|
this.scrollDataLoaded = true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/JournalSection.scss';
|
@import '../styles/JournalSection.scss';
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,344 @@
|
|||||||
|
<template>
|
||||||
|
<section class="journal-timetables">
|
||||||
|
<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"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<JournalStats />
|
||||||
|
|
||||||
|
<div class="list_wrapper" @scroll="handleScroll">
|
||||||
|
<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 == DataStatus.Loading" />
|
||||||
|
|
||||||
|
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
|
||||||
|
{{ $t('app.error') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="timetableHistory.length == 0" class="journal_warning">
|
||||||
|
{{ $t('app.no-result') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<JournalTimetablesList :timetableHistory="timetableHistory" />
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn btn--option btn--load-data"
|
||||||
|
v-if="!scrollNoMoreData && scrollDataLoaded && timetableHistory.length >= 15"
|
||||||
|
@click="addHistoryData"
|
||||||
|
>
|
||||||
|
{{ $t('journal.load-data') }}
|
||||||
|
</button>
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, provide, reactive, Ref, ref } from 'vue';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import imageMixin from '../mixins/imageMixin';
|
||||||
|
import dateMixin from '../mixins/dateMixin';
|
||||||
|
import routerMixin from '../mixins/routerMixin';
|
||||||
|
import modalTrainMixin from '../mixins/modalTrainMixin';
|
||||||
|
|
||||||
|
import DriverStats from '../components/JournalView/JournalDriverStats.vue';
|
||||||
|
import JournalOptions from '../components/JournalView/JournalOptions.vue';
|
||||||
|
import JournalStats from '../components/JournalView/JournalStats.vue';
|
||||||
|
import JournalHeader from '../components/JournalView/JournalHeader.vue';
|
||||||
|
import JournalTimetablesList from '../components/JournalView/JournalTimetablesList.vue';
|
||||||
|
import Loading from '../components/Global/Loading.vue';
|
||||||
|
|
||||||
|
import { DataStatus } from '../scripts/enums/DataStatus';
|
||||||
|
import { TimetableHistory } from '../scripts/interfaces/api/TimetablesAPIData';
|
||||||
|
import { URLs } from '../scripts/utils/apiURLs';
|
||||||
|
import { useStore } from '../store/store';
|
||||||
|
|
||||||
|
import { LocationQuery } from 'vue-router';
|
||||||
|
import { TimetablesQueryParams } from '../scripts/interfaces/api/TimetablesQueryParams';
|
||||||
|
import { JournalFilterType } from '../scripts/enums/JournalFilterType';
|
||||||
|
import {
|
||||||
|
JournalFilter,
|
||||||
|
JournalTimetableSearchType,
|
||||||
|
JournalTimetableSorter,
|
||||||
|
} from '../scripts/types/JournalTimetablesTypes';
|
||||||
|
import { journalTimetableFilters } from '../constants/Journal/JournalTimetablesConsts';
|
||||||
|
|
||||||
|
const TIMETABLES_API_URL = `${URLs.stacjownikAPI}/api/getTimetables`;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { DriverStats, Loading, JournalOptions, JournalTimetablesList, JournalStats, JournalHeader },
|
||||||
|
mixins: [dateMixin, routerMixin, modalTrainMixin, imageMixin],
|
||||||
|
|
||||||
|
name: 'JournalTimetables',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
timetableId: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data: () => ({
|
||||||
|
currentQueryParams: {} as TimetablesQueryParams,
|
||||||
|
|
||||||
|
scrollDataLoaded: true,
|
||||||
|
scrollNoMoreData: false,
|
||||||
|
|
||||||
|
showReturnButton: false,
|
||||||
|
statsCardOpen: false,
|
||||||
|
currentOptionsActive: false,
|
||||||
|
|
||||||
|
timetableHistory: [] as TimetableHistory[],
|
||||||
|
journalTimetableFilters,
|
||||||
|
|
||||||
|
dataStatus: DataStatus.Loading,
|
||||||
|
dataErrorMessage: '',
|
||||||
|
|
||||||
|
DataStatus,
|
||||||
|
}),
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const sorterActive: JournalTimetableSorter = reactive({ id: 'timetableId', dir: 'desc' });
|
||||||
|
// const journalFilterActive = ref(journalTimetableFilters[0]);
|
||||||
|
const initFilters: readonly JournalFilter[] = JSON.parse(JSON.stringify(journalTimetableFilters));
|
||||||
|
const filterList: JournalFilter[] = reactive(JSON.parse(JSON.stringify(initFilters)));
|
||||||
|
|
||||||
|
const searchersValues = reactive({
|
||||||
|
'search-train': '',
|
||||||
|
'search-driver': '',
|
||||||
|
'search-dispatcher': '',
|
||||||
|
'search-issuedFrom': '',
|
||||||
|
'search-date': '',
|
||||||
|
} as JournalTimetableSearchType);
|
||||||
|
|
||||||
|
const countFromIndex = ref(0);
|
||||||
|
const countLimit = 15;
|
||||||
|
|
||||||
|
provide('searchersValues', searchersValues);
|
||||||
|
provide('sorterActive', sorterActive);
|
||||||
|
provide('filterList', filterList);
|
||||||
|
|
||||||
|
const scrollElement: Ref<HTMLElement | null> = ref(null);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sorterActive,
|
||||||
|
searchersValues,
|
||||||
|
filterList,
|
||||||
|
initFilters,
|
||||||
|
|
||||||
|
countFromIndex,
|
||||||
|
countLimit,
|
||||||
|
|
||||||
|
scrollElement,
|
||||||
|
|
||||||
|
store: useStore(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
currentQueryParams(q: TimetablesQueryParams) {
|
||||||
|
this.currentOptionsActive = Object.values(q).some((v) => v !== undefined);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Handle route updates for route-links
|
||||||
|
beforeRouteUpdate(to, _) {
|
||||||
|
this.handleQueries(to.query);
|
||||||
|
this.fetchHistoryData();
|
||||||
|
},
|
||||||
|
|
||||||
|
activated() {
|
||||||
|
this.handleQueries(this.$route.query);
|
||||||
|
this.fetchHistoryData();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
handleScroll(e: Event) {
|
||||||
|
const listElement = e.target as HTMLElement;
|
||||||
|
const scrollTop = listElement.scrollTop;
|
||||||
|
const elementHeight = listElement.scrollHeight - listElement.offsetHeight;
|
||||||
|
|
||||||
|
if (!this.scrollDataLoaded || this.scrollNoMoreData || this.dataStatus != DataStatus.Loaded) return;
|
||||||
|
|
||||||
|
if (scrollTop > elementHeight * 0.85) this.addHistoryData();
|
||||||
|
},
|
||||||
|
|
||||||
|
handleQueries(query: LocationQuery) {
|
||||||
|
if ('timetableId' in query) this.searchersValues['search-train'] = `#${query.timetableId}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
setSearchers(date: string, driver: string, train: string, dispatcher: string, issuedFrom: string) {
|
||||||
|
this.searchersValues['search-date'] = date;
|
||||||
|
this.searchersValues['search-driver'] = driver;
|
||||||
|
this.searchersValues['search-train'] = train;
|
||||||
|
this.searchersValues['search-dispatcher'] = dispatcher;
|
||||||
|
this.searchersValues['search-issuedFrom'] = issuedFrom;
|
||||||
|
},
|
||||||
|
|
||||||
|
resetOptions() {
|
||||||
|
this.setSearchers('', '', '', '', '');
|
||||||
|
|
||||||
|
this.sorterActive.id = 'timetableId';
|
||||||
|
|
||||||
|
this.filterList.forEach(
|
||||||
|
(f) => (f.isActive = this.initFilters.find((initFilter) => initFilter.id == f.id)?.isActive || false)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.fetchHistoryData();
|
||||||
|
},
|
||||||
|
|
||||||
|
async addHistoryData() {
|
||||||
|
this.scrollDataLoaded = false;
|
||||||
|
|
||||||
|
this.currentQueryParams['countFrom'] = this.timetableHistory.length;
|
||||||
|
|
||||||
|
const responseData: TimetableHistory[] = await (
|
||||||
|
await axios.get(`${TIMETABLES_API_URL}`, {
|
||||||
|
params: { ...this.currentQueryParams },
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
|
if (!responseData) return;
|
||||||
|
|
||||||
|
if (responseData.length == 0) {
|
||||||
|
this.scrollNoMoreData = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timetableHistory.push(...responseData);
|
||||||
|
this.scrollDataLoaded = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchHistoryData() {
|
||||||
|
const driverName = this.searchersValues['search-driver'].trim() || undefined;
|
||||||
|
const trainNo = this.searchersValues['search-train'].trim() || undefined;
|
||||||
|
const authorName = this.searchersValues['search-dispatcher'].trim() || undefined;
|
||||||
|
const dateString = this.searchersValues['search-date'].trim() || undefined;
|
||||||
|
const issuedFrom = this.searchersValues['search-issuedFrom'].trim() || undefined;
|
||||||
|
|
||||||
|
const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) - 120 * 60 * 1000 : undefined;
|
||||||
|
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
|
||||||
|
|
||||||
|
const queryParams: TimetablesQueryParams = {};
|
||||||
|
|
||||||
|
this.filterList
|
||||||
|
.filter((f) => f.isActive)
|
||||||
|
.forEach((f) => {
|
||||||
|
switch (f.id) {
|
||||||
|
case JournalFilterType.ABANDONED:
|
||||||
|
queryParams['fulfilled'] = 0;
|
||||||
|
queryParams['terminated'] = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JournalFilterType.ACTIVE:
|
||||||
|
queryParams['fulfilled'] = undefined;
|
||||||
|
queryParams['terminated'] = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JournalFilterType.FULFILLED:
|
||||||
|
queryParams['terminated'] = undefined;
|
||||||
|
queryParams['fulfilled'] = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JournalFilterType.ALL:
|
||||||
|
queryParams['terminated'] = undefined;
|
||||||
|
queryParams['fulfilled'] = undefined;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JournalFilterType.TWR_SKR:
|
||||||
|
queryParams['twr'] = undefined;
|
||||||
|
queryParams['skr'] = undefined;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JournalFilterType.TWR:
|
||||||
|
queryParams['twr'] = 1;
|
||||||
|
queryParams['skr'] = undefined;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JournalFilterType.SKR:
|
||||||
|
queryParams['twr'] = undefined;
|
||||||
|
queryParams['skr'] = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
queryParams['driverName'] = driverName;
|
||||||
|
queryParams['trainNo'] = trainNo;
|
||||||
|
|
||||||
|
queryParams['countFrom'] = undefined;
|
||||||
|
queryParams['countLimit'] = undefined;
|
||||||
|
|
||||||
|
queryParams['authorName'] = authorName;
|
||||||
|
queryParams['timestampFrom'] = timestampFrom;
|
||||||
|
queryParams['timestampTo'] = timestampTo;
|
||||||
|
queryParams['issuedFrom'] = issuedFrom;
|
||||||
|
queryParams['sortBy'] = this.sorterActive.id != 'timetableId' ? this.sorterActive.id : undefined;
|
||||||
|
|
||||||
|
if (JSON.stringify(this.currentQueryParams) != JSON.stringify(queryParams)) this.dataStatus = DataStatus.Loading;
|
||||||
|
|
||||||
|
this.currentQueryParams = queryParams;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const responseData: TimetableHistory[] = await (
|
||||||
|
await axios.get(`${TIMETABLES_API_URL}`, {
|
||||||
|
params: this.currentQueryParams,
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
|
if (!responseData) {
|
||||||
|
this.dataStatus = DataStatus.Error;
|
||||||
|
this.dataErrorMessage = 'Brak danych!';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!responseData) return;
|
||||||
|
|
||||||
|
// Response data exists
|
||||||
|
this.timetableHistory = responseData;
|
||||||
|
|
||||||
|
// Stats display
|
||||||
|
this.store.driverStatsName =
|
||||||
|
this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim()
|
||||||
|
? this.timetableHistory[0].driverName
|
||||||
|
: '';
|
||||||
|
|
||||||
|
this.dataStatus = DataStatus.Loaded;
|
||||||
|
} catch (error) {
|
||||||
|
this.dataStatus = DataStatus.Error;
|
||||||
|
this.dataErrorMessage = 'Ups! Coś poszło nie tak!';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollNoMoreData = false;
|
||||||
|
this.scrollDataLoaded = true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../styles/JournalSection.scss';
|
||||||
|
</style>
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section class="journal-view">
|
|
||||||
<div class="journal-type-options">
|
|
||||||
<router-link class="router-link" active-class="route-active" to="/journal/timetables" exact>
|
|
||||||
{{ $t('journal.section-timetables') }}
|
|
||||||
</router-link>
|
|
||||||
•
|
|
||||||
<router-link class="router-link" active-class="route-active" to="/journal/dispatchers">
|
|
||||||
{{ $t('journal.section-dispatchers') }}
|
|
||||||
</router-link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal-section">
|
|
||||||
<router-view v-slot="{ Component }">
|
|
||||||
<keep-alive>
|
|
||||||
<component :is="Component" :key="$route.path" />
|
|
||||||
</keep-alive>
|
|
||||||
</router-view>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref } from 'vue';
|
|
||||||
import JournalDispatchers from '../components/JournalView/JournalDispatchers.vue';
|
|
||||||
import JournalTimetables from '../components/JournalView/JournalTimetables.vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { JournalDispatchers, JournalTimetables },
|
|
||||||
setup() {
|
|
||||||
const journalTypeChosen = ref('journalTimetables');
|
|
||||||
|
|
||||||
return {
|
|
||||||
activeJournalComponent: journalTypeChosen,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
changeJournalType(type: string) {
|
|
||||||
this.activeJournalComponent = type;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
activated() {
|
|
||||||
const query = this.$route.query;
|
|
||||||
|
|
||||||
if (query.view == 'dispatchers') this.activeJournalComponent = 'journalDispatchers';
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.journal-type-options {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
background-color: #2c2c2c;
|
|
||||||
max-width: 18em;
|
|
||||||
|
|
||||||
font-size: 1.2em;
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
border-radius: 0 0 0.5em 0.5em;
|
|
||||||
padding: 0.1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.journal-section > section {
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.router-link.active {
|
|
||||||
color: gold;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="scenery-view">
|
<div class="scenery-view">
|
||||||
<div class="scenery-offline" v-if="!stationInfo && isComponentVisible && store.dataStatuses.sceneries == 2">
|
<div class="scenery-offline" v-if="!stationInfo && isComponentVisible && store.dataStatuses.sceneries == 2">
|
||||||
<div>{{ $t('scenery.no-scenery') }}</div>
|
<div>{{ $t('scenery.no-scenery') }}</div>
|
||||||
|
|||||||
@@ -52,17 +52,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.filterStore.setupFilters();
|
this.filterStore.setupFilters();
|
||||||
// this.filterStore.inputs.options.forEach((option) => {
|
|
||||||
// const value = StorageManager.getBooleanValue(option.name);
|
|
||||||
// option.value = value;
|
|
||||||
// this.filterStore.changeFilterValue({ name: option.name, value: value });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// this.filterStore.inputs.sliders.forEach((slider) => {
|
|
||||||
// const value = StorageManager.getNumericValue(slider.name);
|
|
||||||
// slider.value = value;
|
|
||||||
// this.filterStore.changeFilterValue({ name: slider.name, value: value });
|
|
||||||
// });
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="trains-view">
|
<section class="trains-view">
|
||||||
<div class="trains_wrapper">
|
<div class="trains_wrapper">
|
||||||
<TrainOptions :sorter-option-ids="['distance', 'progress', 'delay', 'mass', 'speed', 'length']" />
|
<TrainOptions
|
||||||
|
:sorter-option-ids="['routeDistance', 'id', 'progress', 'delay', 'mass', 'speed', 'length']"
|
||||||
|
:current-options-active="currentOptionsActive"
|
||||||
|
/>
|
||||||
|
|
||||||
<TrainTable :trains="computedTrains" />
|
<TrainTable :trains="computedTrains" />
|
||||||
</div>
|
</div>
|
||||||
@@ -9,7 +12,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, ComputedRef, defineComponent, provide, reactive, ref } from 'vue';
|
import { computed, ComputedRef, defineComponent, provide, reactive, ref, watch } from 'vue';
|
||||||
import TrainOptions from '../components/TrainsView/TrainOptions.vue';
|
import TrainOptions from '../components/TrainsView/TrainOptions.vue';
|
||||||
import TrainStats from '../components/TrainsView/TrainStats.vue';
|
import TrainStats from '../components/TrainsView/TrainStats.vue';
|
||||||
import TrainTable from '../components/TrainsView/TrainTable.vue';
|
import TrainTable from '../components/TrainsView/TrainTable.vue';
|
||||||
@@ -18,7 +21,7 @@ import modalTrainMixin from '../mixins/modalTrainMixin';
|
|||||||
import Train from '../scripts/interfaces/Train';
|
import Train from '../scripts/interfaces/Train';
|
||||||
import { filteredTrainList } from '../scripts/managers/trainFilterManager';
|
import { filteredTrainList } from '../scripts/managers/trainFilterManager';
|
||||||
import { useStore } from '../store/store';
|
import { useStore } from '../store/store';
|
||||||
import { TrainFilter } from '../types/Trains/TrainOptionsTypes';
|
import { TrainFilter } from '../scripts/interfaces/Trains/TrainFilter';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@@ -52,10 +55,13 @@ export default defineComponent({
|
|||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
const initTrainFilters = [...trainFilters.map((f) => ({ ...f }))];
|
||||||
|
|
||||||
const sorterActive = ref({ id: 'distance', dir: -1 });
|
const sorterActive = reactive({ id: 'routeDistance', dir: -1 });
|
||||||
const filterList = reactive([...trainFilters]) as TrainFilter[];
|
const filterList = reactive([...trainFilters]) as TrainFilter[];
|
||||||
|
|
||||||
|
const currentOptionsActive = ref(false);
|
||||||
|
|
||||||
const searchedDriver = ref('');
|
const searchedDriver = ref('');
|
||||||
const searchedTrain = ref('');
|
const searchedTrain = ref('');
|
||||||
|
|
||||||
@@ -65,13 +71,13 @@ export default defineComponent({
|
|||||||
provide('filterList', filterList);
|
provide('filterList', filterList);
|
||||||
|
|
||||||
const computedTrains: ComputedRef<Train[]> = computed(() => {
|
const computedTrains: ComputedRef<Train[]> = computed(() => {
|
||||||
return filteredTrainList(
|
return filteredTrainList(store.trainList, searchedTrain.value, searchedDriver.value, sorterActive, filterList);
|
||||||
store.trainList,
|
});
|
||||||
searchedTrain.value,
|
|
||||||
searchedDriver.value,
|
watch([searchedTrain, searchedDriver, sorterActive, filterList], ([sT, sD, sA, fL]) => {
|
||||||
sorterActive.value,
|
const areFiltersActive = fL.some((f, i) => f.isActive !== initTrainFilters[i].isActive);
|
||||||
filterList
|
|
||||||
);
|
currentOptionsActive.value = sT.length > 0 || sD.length > 0 || sA.id != 'distance' || areFiltersActive;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -80,6 +86,7 @@ export default defineComponent({
|
|||||||
searchedDriver,
|
searchedDriver,
|
||||||
sorterActive,
|
sorterActive,
|
||||||
store,
|
store,
|
||||||
|
currentOptionsActive,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"ESNext",
|
"ESNext",
|
||||||
"DOM"
|
"DOM"
|
||||||
],
|
],
|
||||||
"types": ["vite/client"],
|
"types": ["vite/client", "vite-plugin-pwa/client"],
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
|||||||
@@ -1,34 +1,55 @@
|
|||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
server: {
|
||||||
|
port: 5001,
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
VitePWA({
|
||||||
|
registerType: 'autoUpdate',
|
||||||
|
|
||||||
|
workbox: {
|
||||||
|
globPatterns: ['**/*.{js,css,html,png,svg,jpg}'],
|
||||||
|
runtimeCaching: [
|
||||||
|
{
|
||||||
|
urlPattern: new RegExp('^https://spythere.pl/api/getSceneries', 'i'),
|
||||||
|
handler: 'NetworkFirst',
|
||||||
|
options: {
|
||||||
|
cacheName: 'sceneries-cache',
|
||||||
|
expiration: {
|
||||||
|
maxEntries: 1,
|
||||||
|
maxAgeSeconds: 60 * 60 * 24 * 7, // <== 7 days
|
||||||
|
},
|
||||||
|
cacheableResponse: {
|
||||||
|
statuses: [0, 200],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlPattern: /^https:\/\/rj.td2.info.pl\/dist\/img\/thumbnails\/.*/i,
|
||||||
|
handler: 'CacheFirst',
|
||||||
|
options: {
|
||||||
|
cacheName: 'images-cache',
|
||||||
|
expiration: {
|
||||||
|
maxEntries: 100,
|
||||||
|
maxAgeSeconds: 60 * 60 * 24 * 60,
|
||||||
|
},
|
||||||
|
cacheableResponse: {
|
||||||
|
statuses: [0, 200, 404],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
devOptions: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
// PWA
|
|
||||||
|
|
||||||
// VitePWA({
|
|
||||||
// registerType: 'autoUpdate',
|
|
||||||
// workbox: {
|
|
||||||
// globPatterns: ['**/*.{js,css,html,png,svg,img}'],
|
|
||||||
// runtimeCaching: [
|
|
||||||
// {
|
|
||||||
// urlPattern: new RegExp('^https://stacjownik.eu-4.evennode.com/api/getSceneries'),
|
|
||||||
// handler: 'NetworkFirst',
|
|
||||||
// options: {
|
|
||||||
// cacheName: 'sceneries-cache',
|
|
||||||
// expiration: {
|
|
||||||
// maxEntries: 200,
|
|
||||||
// maxAgeSeconds: 60 * 60 * 24 * 60, // <== 60 days
|
|
||||||
// },
|
|
||||||
// cacheableResponse: {
|
|
||||||
// statuses: [0, 200],
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// devOptions: {
|
|
||||||
// enabled: true,
|
|
||||||
// },
|
|
||||||
// }),
|
|
||||||
|
|||||||