Compare commits
250 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 41e60bc69e | |||
| 933bdecb3c | |||
| 10e183d96b | |||
| 5429d39f5e | |||
| ff31e7f903 | |||
| 91f4c6bc57 | |||
| c133eb060b | |||
| 7ffc169d8a | |||
| 1b85cc5f58 | |||
| 72ff857fff | |||
| 96d64e77fc | |||
| 6ceae3f161 | |||
| 8e8e27658c | |||
| 9b6ace394a | |||
| 6cfeaa91bf | |||
| 08b208aeaa | |||
| a089b5275b | |||
| 8425cd4371 | |||
| dbdc517b87 | |||
| e271358a27 | |||
| 66262e3fcd | |||
| 5b2b6bdea2 | |||
| c8587de6d9 | |||
| 1f376085f2 | |||
| 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 | |||
| fcb8357489 | |||
| ceffd8e675 | |||
| 5aa53521f7 | |||
| d8b559694b | |||
| c82ac04a91 | |||
| 284bdcbf2a | |||
| 7f4df98349 | |||
| aecbcf62df | |||
| 2a817365a6 | |||
| ecf3a00cab | |||
| beb2f3c0d4 | |||
| a65b09981b | |||
| 4ec544e8a9 | |||
| 7e108c5183 | |||
| 72361b157e | |||
| 1cc4d76e4d | |||
| 846d4d0547 | |||
| 751cadd218 | |||
| 3b44adff44 | |||
| 29a02dd98f | |||
| c5e68c4d03 | |||
| 95f7c2a4d9 | |||
| 84412822ff | |||
| 42bb056e66 | |||
| 053e9d2b6a | |||
| c729d75541 | |||
| a9b72d0b7a | |||
| 95a027f284 | |||
| dbba83b28b | |||
| 65abe550f5 | |||
| 531108c25a | |||
| bcf750d451 | |||
| 0a8bfe4c52 | |||
| 0f19bc767a | |||
| 8eb0266874 | |||
| ae5b5ff965 | |||
| 3a0c4bc151 | |||
| 4f5fcb3189 | |||
| 3a2978bbe3 | |||
| a81cc4559b | |||
| 065143c359 | |||
| 1661881127 | |||
| 93aa889414 | |||
| 2a131ab1fb | |||
| 387f42985a | |||
| 6c83ce90bf | |||
| 3d519e874f | |||
| 99cdb3442a | |||
| a6c0fe86c8 | |||
| 828421efe0 | |||
| 21bacb1c95 | |||
| 0d9a3f4b4f | |||
| 76b8534d63 | |||
| 0821fd708e | |||
| b0a9939446 | |||
| 2a64b8f10d | |||
| dc1c457ea4 | |||
| 1f95bc5230 | |||
| 5a06920e5b | |||
| ee0d9e7ed4 | |||
| 30ad3ad4f2 | |||
| c2bd5a8a1b | |||
| 7101d0972d | |||
| 82bbfcdf70 | |||
| b90ac6c09e | |||
| 76d0ff88f1 | |||
| 951afcedeb | |||
| 96de3f0dcc | |||
| 03950eef66 | |||
| 6dd8cb2dad | |||
| aae51d4139 | |||
| 9994a541b1 | |||
| bc3a603ba2 | |||
| 7857377cab | |||
| 0034f43be4 |
@@ -1,3 +1,6 @@
|
|||||||
|
# This file was auto-generated by the Firebase CLI
|
||||||
|
# https://github.com/firebase/firebase-tools
|
||||||
|
|
||||||
name: Deploy to Firebase Hosting on PR
|
name: Deploy to Firebase Hosting on PR
|
||||||
'on': pull_request
|
'on': pull_request
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -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,15 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "stacjownik",
|
"name": "stacjownik",
|
||||||
"version": "1.10.0",
|
"version": "1.16.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview"
|
"deploy": "yarn build && firebase deploy --only hosting",
|
||||||
|
"preview": "yarn build && vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.12.1",
|
"core-js": "^3.12.1",
|
||||||
"dotenv": "^8.6.0",
|
"dotenv": "^16.0.3",
|
||||||
"firebase": "^9.8.1",
|
"firebase": "^9.8.1",
|
||||||
"howler": "^2.2.1",
|
"howler": "^2.2.1",
|
||||||
"pinia": "^2.0.14",
|
"pinia": "^2.0.14",
|
||||||
@@ -20,12 +21,13 @@
|
|||||||
"vue-router": "^4.0.0-0"
|
"vue-router": "^4.0.0-0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^17.0.35",
|
"@types/node": "^18.11.17",
|
||||||
"@vitejs/plugin-vue": "^3.0.0",
|
"@vitejs/plugin-vue": "^4.0.0",
|
||||||
"axios": "^0.21.1",
|
"axios": "^1.2.1",
|
||||||
"typescript": "^4.6.4",
|
"typescript": "^4.9.4",
|
||||||
"vite": "^3.0.0",
|
"vite": "^4.0.3",
|
||||||
"vue-tsc": "^0.38.4"
|
"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.4rem + 1.4vw);
|
font-size: calc(0.55rem + 1.1vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
@include screenLandscape() {
|
||||||
|
font-size: calc(0.45rem + 0.8vw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,167 +86,16 @@
|
|||||||
border-radius: 0 0 1em 1em;
|
border-radius: 0 0 1em 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error icon
|
|
||||||
.wip-alert {
|
|
||||||
padding: 0 0.5em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-error {
|
|
||||||
width: 13em;
|
|
||||||
margin: 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// HEADER
|
|
||||||
.app_header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
background-color: $primaryCol;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
&_body {
|
|
||||||
max-width: 21em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
width: 1350px;
|
|
||||||
padding: 0.5em 0.3em 0 0.3em;
|
|
||||||
border-radius: 0 0 1em 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_brand {
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&_info {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_links {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
border-radius: 0.7em;
|
|
||||||
|
|
||||||
font-size: 1.25em;
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_icons {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: flex-end;
|
|
||||||
padding: 0.5em 0.5em;
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
right: auto;
|
|
||||||
left: 0.75em;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ICONS
|
|
||||||
.icons {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&-top {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// COUNTER
|
|
||||||
.info_counter {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
span {
|
|
||||||
margin: 0 0.15em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 1.35em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// REGION SELECTION
|
|
||||||
.info_region {
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
.select-box_content button {
|
|
||||||
background-color: transparent;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 0.1em 0.5em;
|
|
||||||
color: paleturquoise;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options {
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FOOTER
|
// FOOTER
|
||||||
footer.app_footer {
|
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;
|
||||||
|
|||||||
@@ -1,72 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app_container">
|
<div class="app_container">
|
||||||
<UpdateModal />
|
|
||||||
|
|
||||||
<transition name="modal-anim">
|
<transition name="modal-anim">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<TrainModal v-if="store.chosenModalTrainId" />
|
<TrainModal v-if="store.chosenModalTrainId" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<header class="app_header">
|
<UpdatePrompt />
|
||||||
<div class="header_container">
|
|
||||||
<div class="header_icons">
|
|
||||||
<span class="icons-top">
|
|
||||||
<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 />
|
|
||||||
</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">
|
<AppHeader :current-lang="currentLang" @change-lang="changeLang" />
|
||||||
<img :src="getIcon('discord', 'png')" alt="icon discord" />
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="header_body">
|
|
||||||
<status-indicator />
|
|
||||||
<span class="header_brand">
|
|
||||||
<img :src="getImage('stacjownik-header-logo.svg')" alt="Stacjownik" />
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="header_info">
|
|
||||||
<Clock />
|
|
||||||
|
|
||||||
<div class="info_counter">
|
|
||||||
<img :src="getIcon('dispatcher')" alt="icon dispatcher" />
|
|
||||||
<span class="text--primary">{{ onlineDispatchers.length }}</span>
|
|
||||||
<span class="text--grayed"> / </span>
|
|
||||||
<span class="text--primary">{{ trainList.length }}</span>
|
|
||||||
<img :src="getIcon('train')" alt="icon train" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span class="info_region">
|
|
||||||
<SelectBox :itemList="computedRegions" :defaultItemIndex="0" @selected="changeRegion" />
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="header_links">
|
|
||||||
<router-link class="route" active-class="route-active" to="/" exact>
|
|
||||||
{{ $t('app.sceneries') }}
|
|
||||||
</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">
|
|
||||||
{{ $t('app.journal') }}
|
|
||||||
</router-link>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<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.path" />
|
<component :is="Component" :key="$route.name" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</router-view>
|
</router-view>
|
||||||
</main>
|
</main>
|
||||||
@@ -74,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>
|
||||||
@@ -82,28 +32,33 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, provide, ref } 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';
|
||||||
|
|
||||||
import packageInfo from '.././package.json';
|
import packageInfo from '.././package.json';
|
||||||
import options from './data/options.json';
|
|
||||||
|
|
||||||
import StatusIndicator from './components/App/StatusIndicator.vue';
|
import StatusIndicator from './components/App/StatusIndicator.vue';
|
||||||
import SelectBox from './components/Global/SelectBox.vue';
|
import SelectBox from './components/Global/SelectBox.vue';
|
||||||
import { useStore } from './store/store';
|
import { useStore } from './store/store';
|
||||||
import UpdateModal from './components/App/UpdateModal.vue';
|
|
||||||
import TrainModal from './components/Global/TrainModal.vue';
|
import TrainModal from './components/Global/TrainModal.vue';
|
||||||
import StorageManager from './scripts/managers/storageManager';
|
import StorageManager from './scripts/managers/storageManager';
|
||||||
import imageMixin from './mixins/imageMixin';
|
import imageMixin from './mixins/imageMixin';
|
||||||
|
import AppHeader from './components/App/AppHeader.vue';
|
||||||
|
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: {
|
||||||
Clock,
|
Clock,
|
||||||
StatusIndicator,
|
StatusIndicator,
|
||||||
SelectBox,
|
SelectBox,
|
||||||
UpdateModal,
|
|
||||||
TrainModal,
|
TrainModal,
|
||||||
|
AppHeader,
|
||||||
|
UpdatePrompt,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [imageMixin],
|
mixins: [imageMixin],
|
||||||
@@ -112,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);
|
||||||
@@ -127,49 +84,54 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
trainList() {
|
|
||||||
return this.store.trainList.filter((train) => train.online);
|
|
||||||
},
|
|
||||||
|
|
||||||
computedRegions() {
|
|
||||||
return this.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,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
VERSION: packageInfo.version,
|
VERSION: packageInfo.version,
|
||||||
options,
|
|
||||||
|
|
||||||
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() {
|
||||||
this.updateStorage();
|
|
||||||
this.setReleaseURL();
|
this.setReleaseURL();
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => this.store.blockScroll,
|
||||||
|
(value) => {
|
||||||
|
if (value) {
|
||||||
|
document.body.classList.add('no-scroll');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.classList.remove('no-scroll');
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
changeRegion(region: { id: string; value: string }) {
|
|
||||||
this.store.changeRegion(region);
|
|
||||||
},
|
|
||||||
|
|
||||||
changeLang(lang: string) {
|
changeLang(lang: string) {
|
||||||
this.$i18n.locale = lang;
|
this.$i18n.locale = lang;
|
||||||
this.currentLang = lang;
|
this.currentLang = lang;
|
||||||
@@ -177,18 +139,18 @@ export default defineComponent({
|
|||||||
StorageManager.setStringValue('lang', lang);
|
StorageManager.setStringValue('lang', lang);
|
||||||
},
|
},
|
||||||
|
|
||||||
setReleaseURL() {
|
async setReleaseURL() {
|
||||||
const releaseURL = StorageManager.getStringValue('releaseURL');
|
try {
|
||||||
|
const releaseData = await (
|
||||||
|
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
|
||||||
|
).data;
|
||||||
|
|
||||||
this.releaseURL = releaseURL || '';
|
if (!releaseData) return;
|
||||||
},
|
|
||||||
|
|
||||||
updateStorage() {
|
this.releaseURL = releaseData.html_url;
|
||||||
if (!StorageManager.isRegistered('unavailable-status')) {
|
} catch (error) {
|
||||||
StorageManager.setBooleanValue('unavailable-status', true);
|
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
||||||
StorageManager.setBooleanValue('ending-status', true);
|
return;
|
||||||
StorageManager.setBooleanValue('no-space-status', true);
|
|
||||||
StorageManager.setBooleanValue('afk-status', true);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -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 |
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" ?><svg enable-background="new 0 0 32 32" id="Glyph" version="1.1" viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M27.414,24.586l-5.077-5.077C23.386,17.928,24,16.035,24,14c0-5.514-4.486-10-10-10S4,8.486,4,14 s4.486,10,10,10c2.035,0,3.928-0.614,5.509-1.663l5.077,5.077c0.78,0.781,2.048,0.781,2.828,0 C28.195,26.633,28.195,25.367,27.414,24.586z M7,14c0-3.86,3.14-7,7-7s7,3.14,7,7s-3.14,7-7,7S7,17.86,7,14z" id="XMLID_223_" fill="white" /></svg>
|
||||||
|
After Width: | Height: | Size: 546 B |
|
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 |
@@ -0,0 +1,237 @@
|
|||||||
|
<template>
|
||||||
|
<header class="app_header">
|
||||||
|
<div class="header_container">
|
||||||
|
<div class="header_icons">
|
||||||
|
<span class="icons-top">
|
||||||
|
<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 />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header_body">
|
||||||
|
<StatusIndicator />
|
||||||
|
|
||||||
|
<span class="header_brand">
|
||||||
|
<router-link to="/">
|
||||||
|
<img :src="getImage('stacjownik-header-logo.svg')" alt="Stacjownik" />
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="header_info">
|
||||||
|
<Clock />
|
||||||
|
|
||||||
|
<div class="info_counter">
|
||||||
|
<img :src="getIcon('dispatcher')" alt="icon dispatcher" />
|
||||||
|
<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--primary">{{ onlineTrainsCount }}</span>
|
||||||
|
<img :src="getIcon('train')" alt="icon train" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="info_region">
|
||||||
|
<SelectBox :itemList="computedRegions" :defaultItemIndex="0" @selected="changeRegion" />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="header_links">
|
||||||
|
<router-link class="route" active-class="route-active" to="/" exact>
|
||||||
|
{{ $t('app.sceneries') }}
|
||||||
|
</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"
|
||||||
|
:data-active="$route.path.startsWith('/journal')"
|
||||||
|
to="/journal"
|
||||||
|
>
|
||||||
|
{{ $t('app.journal') }}
|
||||||
|
</router-link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { useStore } from '../../store/store';
|
||||||
|
import options from '../../data/options.json';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
import SelectBox from '../Global/SelectBox.vue';
|
||||||
|
import StatusIndicator from './StatusIndicator.vue';
|
||||||
|
import Clock from './Clock.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
emits: ['changeLang'],
|
||||||
|
mixins: [imageMixin],
|
||||||
|
props: {
|
||||||
|
currentLang: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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 {
|
||||||
|
id: region.id,
|
||||||
|
value: `${region.value} <div class='text--grayed'>${regionStationCount} / ${regionTrainCount}</div>`,
|
||||||
|
selectedValue: region.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: { SelectBox, StatusIndicator, Clock },
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/variables.scss';
|
||||||
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
|
// HEADER
|
||||||
|
.app_header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
background-color: $primaryCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
&_body {
|
||||||
|
position: relative;
|
||||||
|
max-width: 20em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
border-radius: 0 0 1em 1em;
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&_brand {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&_info {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
font-size: 1.15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_links {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
border-radius: 0.7em;
|
||||||
|
|
||||||
|
font-size: 1.25em;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_icons {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
transform: translateX(85%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ICONS
|
||||||
|
.icons-top {
|
||||||
|
img {
|
||||||
|
width: 2.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// COUNTER
|
||||||
|
.info_counter {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin: 0 0.15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 1.35em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// REGION SELECTION
|
||||||
|
.info_region {
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.select-box_content button {
|
||||||
|
background-color: transparent;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.1em 0.5em;
|
||||||
|
color: paleturquoise;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -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>
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="action-btn">
|
<button class="action-btn btn--filled">
|
||||||
<div class="button_content">
|
<div class="button_content">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
@@ -16,47 +16,6 @@ export default defineComponent({});
|
|||||||
@import "../../styles/variables";
|
@import "../../styles/variables";
|
||||||
@import "../../styles/responsive";
|
@import "../../styles/responsive";
|
||||||
|
|
||||||
.action-btn {
|
|
||||||
background: #333;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
color: #bdbdbd;
|
|
||||||
|
|
||||||
font-size: 1em;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
padding: 0.35em 0.65em;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
transition: all 0.3s;
|
|
||||||
|
|
||||||
&.outlined {
|
|
||||||
border: 1px solid white;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 1.25em;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-right: 0.35em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 1em;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.open {
|
|
||||||
color: $accentCol;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
color: $accentCol;
|
|
||||||
background: #5c5c5c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.button_content {
|
.button_content {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
<div class="select-box">
|
<div class="select-box">
|
||||||
<div class="select-box_content">
|
<div class="select-box_content">
|
||||||
<button class="selected" @click="toggleBox">
|
<button class="selected" @click="toggleBox">
|
||||||
<span class="text--primary">{{ prefix }}</span>
|
|
||||||
<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)">
|
||||||
@@ -22,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>
|
||||||
|
|
||||||
@@ -130,44 +129,25 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.select-box {
|
.select-box {
|
||||||
position: relative;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
.arrow {
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: 0;
|
|
||||||
padding: 0.5em;
|
|
||||||
|
|
||||||
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: #333;
|
color: paleturquoise;
|
||||||
color: white;
|
|
||||||
|
|
||||||
font-size: 1em;
|
font-weight: bold;
|
||||||
|
padding: 0.1em 0.5em;
|
||||||
padding: 0.35em 0.5em;
|
|
||||||
margin-right: 1.4em;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
text-align: left;
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
background: #555;
|
background-color: #262626;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,8 +168,9 @@ ul.options {
|
|||||||
height: auto;
|
height: auto;
|
||||||
|
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
li.option {
|
li.option {
|
||||||
@@ -203,6 +184,7 @@ li.option {
|
|||||||
appearance: none;
|
appearance: none;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
background: none;
|
||||||
|
|
||||||
&:focus + span {
|
&:focus + span {
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
@@ -218,11 +200,11 @@ li.option {
|
|||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: hsla(0, 0%, 15%, 0.95);
|
background-color: #262626f2;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: hsla(0, 0%, 20%, 0.95);
|
background-color: #333333f2;
|
||||||
}
|
}
|
||||||
|
|
||||||
padding: 0.5em 0;
|
padding: 0.5em 0;
|
||||||
|
|||||||
@@ -144,9 +144,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
.train-modal {
|
|
||||||
font-size: 1.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal_content {
|
.modal_content {
|
||||||
max-height: 85vh;
|
max-height: 85vh;
|
||||||
|
|||||||
@@ -0,0 +1,250 @@
|
|||||||
|
<template>
|
||||||
|
<section class="daily-stats">
|
||||||
|
<span :data-active="statsStatus">
|
||||||
|
<b v-if="statsStatus == DataStatus.Loading">
|
||||||
|
{{ $t('app.loading') }}
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<b v-else-if="stats.distanceSum == null">
|
||||||
|
{{ $t('journal.daily-stats-info') }}
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<span class="stats-list" v-else>
|
||||||
|
<h3>
|
||||||
|
{{ $t('journal.daily-stats-title') }}
|
||||||
|
<b class="text--primary">{{ new Date().toLocaleDateString($i18n.locale) }}</b>
|
||||||
|
</h3>
|
||||||
|
<hr style="margin-bottom: 0.5em" />
|
||||||
|
|
||||||
|
<div v-if="stats.totalTimetables">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.timetable-stats-total">
|
||||||
|
<template #count>
|
||||||
|
<b class="text--primary">
|
||||||
|
{{ stats.totalTimetables }}
|
||||||
|
{{ $t('journal.timetable-count', stats.totalTimetables) }}
|
||||||
|
</b>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #distance>
|
||||||
|
<b class="text--primary"> {{ stats.distanceSum?.toFixed(2) }} km</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="stats.timetableId">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.timetable-stats-longest">
|
||||||
|
<template #id>
|
||||||
|
<router-link :to="`/journal/timetables?timetableId=${stats.timetableId}`">
|
||||||
|
<b>{{ stats.timetableId }}</b>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
<template #author>
|
||||||
|
<router-link :to="`/journal/dispatchers?dispatcherName=${stats.timetableAuthor}`">
|
||||||
|
<b>{{ stats.timetableAuthor }}</b>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
<template #driver>
|
||||||
|
<b class="text--primary">{{ stats.timetableDriver }}</b>
|
||||||
|
</template>
|
||||||
|
<template #distance>
|
||||||
|
<b class="text--primary">{{ stats.timetableRouteDistance }} km</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="firstPlaceDispatchers.length == 1">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.timetable-stats-most-active-dr">
|
||||||
|
<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-dr-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>
|
||||||
|
|
||||||
|
<div v-if="stats.longestDuties.length > 0">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.timetable-stats-longest-duties">
|
||||||
|
<template #dispatcher>
|
||||||
|
<router-link :to="`/journal/dispatchers?dispatcherName=${stats.longestDuties[0].name}`">
|
||||||
|
<b>{{ stats.longestDuties[0].name }}</b>
|
||||||
|
</router-link>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #station>{{ stats.longestDuties[0].station }}</template>
|
||||||
|
|
||||||
|
<template #duration>
|
||||||
|
{{ calculateDuration(stats.longestDuties[0].duration) }}
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="stats.mostActiveDrivers.length > 0">
|
||||||
|
•
|
||||||
|
<i18n-t keypath="journal.timetable-stats-most-active-driver">
|
||||||
|
<template #driver>
|
||||||
|
<b class="text--primary">{{ stats.mostActiveDrivers[0].name }}</b>
|
||||||
|
</template>
|
||||||
|
<template #distance>
|
||||||
|
<b class="text--primary">{{ stats.mostActiveDrivers[0].distance }} km</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import axios from 'axios';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
|
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
|
import { ITimetablesDailyStats, ITimetablesDailyStatsResponse } from '../../scripts/interfaces/api/StatsAPIData';
|
||||||
|
import { URLs } from '../../scripts/utils/apiURLs';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
mixins: [dateMixin],
|
||||||
|
emits: ['toggleStatsOpen'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
DataStatus,
|
||||||
|
statsStatus: DataStatus.Loading,
|
||||||
|
intervalId: -1,
|
||||||
|
|
||||||
|
stats: {
|
||||||
|
totalTimetables: 0,
|
||||||
|
distanceSum: 0,
|
||||||
|
distanceAvg: 0,
|
||||||
|
timetableAuthor: '',
|
||||||
|
timetableDriver: '',
|
||||||
|
timetableId: 0,
|
||||||
|
timetableRouteDistance: 0,
|
||||||
|
longestDuties: [],
|
||||||
|
mostActiveDrivers: [],
|
||||||
|
mostActiveDispatchers: [],
|
||||||
|
} as ITimetablesDailyStats,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
activated() {
|
||||||
|
this.startFetchingDailyStats();
|
||||||
|
this.$emit('toggleStatsOpen', true);
|
||||||
|
},
|
||||||
|
|
||||||
|
deactivated() {
|
||||||
|
this.stopFetchingDailyStats();
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
firstPlaceDispatchers() {
|
||||||
|
if (this.stats.mostActiveDispatchers.length == 0) return [];
|
||||||
|
const maxCount = this.stats.mostActiveDispatchers[0].count;
|
||||||
|
|
||||||
|
return this.stats.mostActiveDispatchers.filter((disp) => disp.count === maxCount);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async fetchDailyTimetableStats() {
|
||||||
|
try {
|
||||||
|
const res: ITimetablesDailyStatsResponse = await (
|
||||||
|
await axios.get(`${URLs.stacjownikAPI}/api/getDailyTimetableStats`)
|
||||||
|
).data;
|
||||||
|
|
||||||
|
this.stats = {
|
||||||
|
totalTimetables: res.totalTimetables,
|
||||||
|
distanceSum: res.distanceSum,
|
||||||
|
distanceAvg: res.distanceAvg,
|
||||||
|
timetableAuthor: res.maxTimetable?.authorName || '',
|
||||||
|
timetableDriver: res.maxTimetable?.driverName || '',
|
||||||
|
timetableId: res.maxTimetable?.id || 0,
|
||||||
|
timetableRouteDistance: res.maxTimetable?.routeDistance || 0,
|
||||||
|
|
||||||
|
mostActiveDispatchers: res.mostActiveDispatchers,
|
||||||
|
mostActiveDrivers: res.mostActiveDrivers,
|
||||||
|
longestDuties: res.longestDuties,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.statsStatus = DataStatus.Loaded;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ups! Wystąpił błąd podczas pobierania statystyk rozkładów jazdy...');
|
||||||
|
this.statsStatus = DataStatus.Error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startFetchingDailyStats() {
|
||||||
|
this.fetchDailyTimetableStats();
|
||||||
|
|
||||||
|
if (this.intervalId != -1) return;
|
||||||
|
|
||||||
|
this.intervalId = setInterval(this.fetchDailyTimetableStats, 60000);
|
||||||
|
},
|
||||||
|
|
||||||
|
stopFetchingDailyStats() {
|
||||||
|
clearInterval(this.intervalId);
|
||||||
|
this.intervalId = -1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
|
.daily-stats {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.daily-stats > span[data-active='0'] {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-list a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.daily-stats {
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="stats_container" v-click-outside="() => (cardVisible = false)">
|
<div class="stats_container" v-click-outside="() => (cardVisible = false)">
|
||||||
<button class="stats_button btn btn--option" @click="toggleCard">
|
<button class="stats_button" @click="toggleCard">
|
||||||
Statystyki dyżurnego {{ store.dispatcherStatsName }}
|
Statystyki dyżurnego {{ store.dispatcherStatsName }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<h3>STATYSTYKI WYSTAWIONYCH ROZKŁADÓW</h3>
|
<h3>STATYSTYKI WYSTAWIONYCH ROZKŁADÓW</h3>
|
||||||
|
|
||||||
<div class="info-stats" v-if="store.dispatcherStatsData._count._all">
|
<div class="info-stats" v-if="store.dispatcherStatsData._count._all">
|
||||||
<span class="stat-badge">
|
<span class="stat-badge">
|
||||||
<span>LICZBA</span>
|
<span>LICZBA</span>
|
||||||
@@ -162,42 +163,11 @@ h3 {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-stats {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-timetables {
|
.last-timetables {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-badge {
|
|
||||||
margin-right: 0.5em;
|
|
||||||
padding-bottom: 1em;
|
|
||||||
|
|
||||||
span {
|
|
||||||
padding: 0.25em 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
span:first-child {
|
|
||||||
background-color: #4d4d4d;
|
|
||||||
}
|
|
||||||
|
|
||||||
span:last-child {
|
|
||||||
background-color: $accentCol;
|
|
||||||
color: black;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
.stats_card {
|
|
||||||
text-align: center;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
border-radius: 0 0 1em 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="card-dimmer" @click="closeCard"></div>
|
|
||||||
|
|
||||||
<div class="stats-card card">
|
|
||||||
<div>
|
|
||||||
<h2 class="card-title">
|
|
||||||
STATYSTYKI MASZYNISTY <span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="loading" v-if="!store.driverStatsData">Ładowanie...</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<div class="info-stats" v-if="store.driverStatsData._sum.routeDistance != null">
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>PRZEBYTO</span>
|
|
||||||
<span>{{ store.driverStatsData._sum.routeDistance.toFixed(2) }}km</span>
|
|
||||||
</span>
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>PORZUCONO</span>
|
|
||||||
<span>
|
|
||||||
{{ (store.driverStatsData._sum.routeDistance - store.driverStatsData._sum.currentDistance).toFixed(2) }}km
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>WYPEŁNIONO</span>
|
|
||||||
<span>{{ store.driverStatsData._count.fulfilled }} RJ</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>PORZUCONO</span>
|
|
||||||
<span>{{ store.driverStatsData._count._all - store.driverStatsData._count.fulfilled }} RJ</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>ZATWIERDZONO</span>
|
|
||||||
<span>{{ store.driverStatsData._sum.confirmedStopsCount }} stacji</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="stat-badge">
|
|
||||||
<span>PORZUCONO</span>
|
|
||||||
<span>
|
|
||||||
{{ store.driverStatsData._sum.allStopsCount - store.driverStatsData._sum.confirmedStopsCount }}
|
|
||||||
stacji
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
|
|
||||||
import axios from 'axios';
|
|
||||||
import { defineComponent } 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,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
test: Math.random(),
|
|
||||||
lastDispatcherName: '',
|
|
||||||
|
|
||||||
lastTimetables: [] as TimetableHistory[],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
activated() {
|
|
||||||
this.fetchDispatcherStats();
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
async fetchDispatcherStats() {
|
|
||||||
this.store.driverStatsData = undefined;
|
|
||||||
|
|
||||||
const statsData: DriverStatsAPIData = await (
|
|
||||||
await axios.get(`${URLs.stacjownikAPI}/api/getDriverInfo?name=${this.store.driverStatsName}`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
const recentTimetablesData: TimetableHistory[] = await (
|
|
||||||
await axios.get(`${URLs.stacjownikAPI}/api/getTimetables?driverName=${this.store.driverStatsName}`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
this.store.driverStatsData = statsData;
|
|
||||||
this.lastTimetables = recentTimetablesData || [];
|
|
||||||
},
|
|
||||||
|
|
||||||
closeCard() {
|
|
||||||
this.$emit('closeCard');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/responsive.scss';
|
|
||||||
|
|
||||||
.timetable-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 4fr 1fr 1fr 2fr 2fr;
|
|
||||||
gap: 0.2em;
|
|
||||||
margin: 0.5em 0;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
span {
|
|
||||||
min-width: 100px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
background-color: #4d4d4d;
|
|
||||||
padding: 0.5em 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
span {
|
|
||||||
padding: 0.2em 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
background-color: #4d4d4d;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,425 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section class="journal-timetables">
|
|
||||||
<div class="journal-wrapper">
|
|
||||||
<div class="journal_top-bar">
|
|
||||||
<JournalOptions
|
|
||||||
@on-filter-change="search"
|
|
||||||
@on-input-change="search"
|
|
||||||
@on-sorter-change="search"
|
|
||||||
:sorter-option-ids="['timestampFrom', 'duration']"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- <DispatcherStats /> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal-list">
|
|
||||||
<div class="list-wrapper" ref="scrollElement">
|
|
||||||
<transition name="warning" mode="out-in">
|
|
||||||
<div :key="historyDataStatus.status">
|
|
||||||
<Loading v-if="isDataLoading || isDataInit" />
|
|
||||||
|
|
||||||
<div v-else-if="isDataError" class="journal_warning error">
|
|
||||||
{{ $t('app.error') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal_warning" v-else-if="historyList.length == 0">
|
|
||||||
{{ $t('app.no-result') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul v-else>
|
|
||||||
<transition-group name="journal-list-anim">
|
|
||||||
<li v-for="(doc, i) in computedHistoryList" :key="doc.id">
|
|
||||||
<div class="journal_day" v-if="isAnotherDay(i - 1, i)">
|
|
||||||
<span>{{ new Date(doc.timestampFrom).toLocaleDateString('pl-PL') }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="journal_item"
|
|
||||||
:class="{ online: doc.isOnline }"
|
|
||||||
@click="navigateToScenery(doc.stationName, doc.isOnline)"
|
|
||||||
@keydown.enter="navigateToScenery(doc.stationName, doc.isOnline)"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<b class="text--primary">{{ doc.dispatcherName }}</b> • <b>{{ doc.stationName }}</b>
|
|
||||||
<span class="text--grayed"> #{{ doc.stationHash }} </span>
|
|
||||||
<span class="region-badge" :class="doc.region">PL1</span>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<span :data-status="doc.isOnline">
|
|
||||||
{{ doc.isOnline ? $t('journal.online-since') : 'OFFLINE' }}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{{ new Date(doc.timestampFrom).toLocaleTimeString('pl-PL', { timeStyle: 'short' }) }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-if="doc.currentDuration && doc.isOnline">
|
|
||||||
({{ calculateDuration(doc.currentDuration) }})
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-if="doc.timestampTo">
|
|
||||||
>
|
|
||||||
{{ new Date(doc.timestampTo).toLocaleTimeString('pl-PL', { timeStyle: 'short' }) }}
|
|
||||||
({{ $t('journal.duty-lasted') }} {{ calculateDuration(doc.currentDuration!) }})
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</transition-group>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
|
|
||||||
<div class="journal_warning" v-else-if="!scrollDataLoaded">{{ $t('journal.loading-further-data') }}</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, JournalFilter, provide, reactive, Ref, ref } from 'vue';
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
import ActionButton from '../../components/Global/ActionButton.vue';
|
|
||||||
import JournalOptions from '../../components/JournalView/JournalOptions.vue';
|
|
||||||
import DispatcherStats from '../../components/JournalView/DispatcherStats.vue';
|
|
||||||
import SearchBox from '../Global/SearchBox.vue';
|
|
||||||
|
|
||||||
import Loading from '../Global/Loading.vue';
|
|
||||||
import { URLs } from '../../scripts/utils/apiURLs';
|
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
|
||||||
import { DataStatus } from '../../scripts/enums/DataStatus';
|
|
||||||
import { useStore } from '../../store/store';
|
|
||||||
|
|
||||||
const DISPATCHERS_API_URL = `${URLs.stacjownikAPI}/api/getDispatchers`;
|
|
||||||
|
|
||||||
interface DispatcherHistoryItem {
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
stationName: string;
|
|
||||||
stationHash: string;
|
|
||||||
region: string;
|
|
||||||
|
|
||||||
dispatcherName: string;
|
|
||||||
dispatcherId: number;
|
|
||||||
|
|
||||||
timestampFrom: number;
|
|
||||||
timestampTo?: number;
|
|
||||||
currentDuration?: number;
|
|
||||||
|
|
||||||
lastOnlineTimestamp: number;
|
|
||||||
|
|
||||||
isOnline: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
type JournalDispatcherSearcher = {
|
|
||||||
[key in 'search-dispatcher' | 'search-station']: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { SearchBox, ActionButton, JournalOptions, DispatcherStats, Loading },
|
|
||||||
mixins: [dateMixin],
|
|
||||||
name: 'JournalDispatchers',
|
|
||||||
|
|
||||||
props: {
|
|
||||||
sceneryName: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
dispatcherName: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
currentQuery: '',
|
|
||||||
scrollDataLoaded: true,
|
|
||||||
scrollNoMoreData: false,
|
|
||||||
|
|
||||||
showReturnButton: false,
|
|
||||||
statsCardOpen: false,
|
|
||||||
}),
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const historyDataStatus: Ref<{ status: DataStatus; error: string | null }> = ref({
|
|
||||||
status: DataStatus.Loading,
|
|
||||||
error: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const sorterActive = ref({ id: 'timestampFrom', dir: -1 });
|
|
||||||
const journalFilterActive = ref({});
|
|
||||||
const searchersValues = reactive({
|
|
||||||
'search-dispatcher': '',
|
|
||||||
'search-station': '',
|
|
||||||
} as JournalDispatcherSearcher);
|
|
||||||
|
|
||||||
const countFromIndex = ref(0);
|
|
||||||
const countLimit = 15;
|
|
||||||
|
|
||||||
provide('sorterActive', sorterActive);
|
|
||||||
provide('journalFilterActive', journalFilterActive);
|
|
||||||
provide('searchersValues', searchersValues);
|
|
||||||
|
|
||||||
const scrollElement: Ref<HTMLElement | null> = ref(null);
|
|
||||||
|
|
||||||
return {
|
|
||||||
store: useStore(),
|
|
||||||
|
|
||||||
historyList: ref([]) as Ref<DispatcherHistoryItem[]>,
|
|
||||||
historyDataStatus,
|
|
||||||
|
|
||||||
isDataLoading: computed(() => historyDataStatus.value.status === DataStatus.Loading),
|
|
||||||
isDataError: computed(() => historyDataStatus.value.status === DataStatus.Error),
|
|
||||||
isDataInit: computed(() => historyDataStatus.value.status === DataStatus.Initialized),
|
|
||||||
|
|
||||||
sorterActive,
|
|
||||||
searchersValues,
|
|
||||||
|
|
||||||
countFromIndex,
|
|
||||||
countLimit,
|
|
||||||
|
|
||||||
scrollElement,
|
|
||||||
maxCount: ref(15),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
computedHistoryList() {
|
|
||||||
return this.historyList.filter(
|
|
||||||
(doc) => doc.isOnline || (doc.currentDuration && doc.currentDuration > 10 * 60000)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
activated() {
|
|
||||||
if (this.sceneryName || this.dispatcherName) {
|
|
||||||
this.searchersValues['search-station'] = this.sceneryName?.toString() || '';
|
|
||||||
this.searchersValues['search-dispatcher'] = this.dispatcherName?.toString() || '';
|
|
||||||
this.search();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('scroll', this.handleScroll);
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
if (!this.sceneryName && !this.dispatcherName) {
|
|
||||||
this.search();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
deactivated() {
|
|
||||||
window.removeEventListener('scroll', this.handleScroll);
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
closeDispatcherStatsCard() {
|
|
||||||
this.statsCardOpen = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
navigateToScenery(name: string, isOnline: boolean) {
|
|
||||||
if (!isOnline) return;
|
|
||||||
|
|
||||||
this.$router.push(`/scenery?station=${name.trim().replace(/ /g, '_')}`);
|
|
||||||
},
|
|
||||||
|
|
||||||
isAnotherDay(prevIndex: number, currIndex: number) {
|
|
||||||
if (currIndex == 0) return true;
|
|
||||||
|
|
||||||
return (
|
|
||||||
new Date(this.computedHistoryList[prevIndex].timestampFrom).getDate() !=
|
|
||||||
new Date(this.computedHistoryList[currIndex].timestampFrom).getDate()
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleScroll() {
|
|
||||||
this.showReturnButton = window.scrollY > window.innerHeight;
|
|
||||||
|
|
||||||
const element = this.$refs.scrollElement as HTMLElement;
|
|
||||||
|
|
||||||
if (
|
|
||||||
element.getBoundingClientRect().bottom * 0.85 < window.innerHeight &&
|
|
||||||
this.scrollDataLoaded &&
|
|
||||||
!this.scrollNoMoreData &&
|
|
||||||
this.historyDataStatus.status == DataStatus.Loaded
|
|
||||||
)
|
|
||||||
this.addHistoryData();
|
|
||||||
},
|
|
||||||
|
|
||||||
scrollToTop() {
|
|
||||||
window.scrollTo({ top: 0 });
|
|
||||||
},
|
|
||||||
|
|
||||||
search() {
|
|
||||||
this.fetchHistoryData({
|
|
||||||
searchers: this.searchersValues,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.scrollNoMoreData = false;
|
|
||||||
this.scrollDataLoaded = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
async addHistoryData() {
|
|
||||||
this.scrollDataLoaded = false;
|
|
||||||
|
|
||||||
const countFrom = this.historyList.length;
|
|
||||||
|
|
||||||
const responseData: DispatcherHistoryItem[] = await (
|
|
||||||
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}&countFrom=${countFrom}`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
if (!responseData) return;
|
|
||||||
|
|
||||||
if (responseData.length == 0) {
|
|
||||||
this.scrollNoMoreData = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.historyList.push(...responseData);
|
|
||||||
this.scrollDataLoaded = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchHistoryData(
|
|
||||||
props: {
|
|
||||||
searchers?: JournalDispatcherSearcher;
|
|
||||||
filter?: JournalFilter;
|
|
||||||
} = {}
|
|
||||||
) {
|
|
||||||
this.historyDataStatus.status = DataStatus.Loading;
|
|
||||||
|
|
||||||
const queries: string[] = [];
|
|
||||||
|
|
||||||
// const dispatcher = props.searchers?.find((s) => s.id == 'search-dispatcher')?.value.trim();
|
|
||||||
// const station = props.searchers?.find((s) => s.id == 'search-station')?.value.trim();
|
|
||||||
|
|
||||||
const dispatcher = props.searchers?.['search-dispatcher'].trim();
|
|
||||||
const station = props.searchers?.['search-station'].trim();
|
|
||||||
|
|
||||||
if (dispatcher) queries.push(`dispatcherName=${dispatcher}`);
|
|
||||||
if (station) queries.push(`stationName=${station}`);
|
|
||||||
|
|
||||||
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
|
|
||||||
if (this.sorterActive.id == 'timestampFrom') queries.push('sortBy=timestampFrom');
|
|
||||||
else if (this.sorterActive.id == 'duration') queries.push('sortBy=currentDuration');
|
|
||||||
else queries.push('sortBy=timestampFrom');
|
|
||||||
|
|
||||||
queries.push('countLimit=15');
|
|
||||||
|
|
||||||
this.currentQuery = queries.join('&');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const responseData: DispatcherHistoryItem[] = await (
|
|
||||||
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
if (!responseData) {
|
|
||||||
this.historyDataStatus.status = DataStatus.Error;
|
|
||||||
this.historyDataStatus.error = 'Brak danych!';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!responseData) return;
|
|
||||||
|
|
||||||
// Response data exists
|
|
||||||
this.historyList = responseData;
|
|
||||||
|
|
||||||
// Stats display
|
|
||||||
this.store.dispatcherStatsName =
|
|
||||||
this.historyList.length > 0 && this.searchersValues['search-dispatcher'].trim()
|
|
||||||
? this.historyList[0].dispatcherName
|
|
||||||
: '';
|
|
||||||
|
|
||||||
this.historyDataStatus.status = DataStatus.Loaded;
|
|
||||||
} catch (error) {
|
|
||||||
this.historyDataStatus.status = DataStatus.Error;
|
|
||||||
this.historyDataStatus.error = 'Ups! Coś poszło nie tak!';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/JournalSection.scss';
|
|
||||||
@import '../../styles/responsive.scss';
|
|
||||||
|
|
||||||
.region-badge {
|
|
||||||
padding: 0.1em 0.5em;
|
|
||||||
border-radius: 0.5em;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
&.eu {
|
|
||||||
background-color: forestgreen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-wrapper {
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.journal_item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
&.online {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
span[data-status='true'] {
|
|
||||||
color: springgreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
span[data-status='false'] {
|
|
||||||
color: salmon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.journal_day {
|
|
||||||
position: relative;
|
|
||||||
text-align: center;
|
|
||||||
background-color: #4d4d4d;
|
|
||||||
|
|
||||||
span {
|
|
||||||
position: relative;
|
|
||||||
background-color: #4d4d4d;
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
padding: 0 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
|
|
||||||
z-index: 0;
|
|
||||||
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
|
|
||||||
height: 3px;
|
|
||||||
width: 60%;
|
|
||||||
min-width: 200px;
|
|
||||||
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
.journal_item {
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
span {
|
|
||||||
margin-top: 0.25em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
<template>
|
||||||
|
<transition-group class="journal-list" tag="ul" name="list-anim">
|
||||||
|
<li
|
||||||
|
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">
|
||||||
|
{{ item }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="journal_item"
|
||||||
|
:class="{ online: item.isOnline }"
|
||||||
|
@click="navigateToScenery(item.stationName, item.isOnline)"
|
||||||
|
@keydown.enter="navigateToScenery(item.stationName, item.isOnline)"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
<span class="text--grayed"> #{{ item.stationHash }} </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 class="item-time">
|
||||||
|
<span :data-status="item.isOnline"> {{ item.isOnline ? $t('journal.online-since') : 'OFFLINE' }} </span>
|
||||||
|
<span>
|
||||||
|
{{ new Date(item.timestampFrom).toLocaleTimeString('pl-PL', { timeStyle: 'short' }) }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-if="item.currentDuration && item.isOnline"> ({{ calculateDuration(item.currentDuration) }}) </span>
|
||||||
|
|
||||||
|
<span v-if="item.timestampTo">
|
||||||
|
>
|
||||||
|
{{ new Date(item.timestampTo).toLocaleTimeString('pl-PL', { timeStyle: 'short' }) }}
|
||||||
|
({{ $t('journal.duty-lasted') }} {{ calculateDuration(item.currentDuration!) }})
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</transition-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from 'vue';
|
||||||
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
|
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
|
||||||
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
dispatcherHistory: {
|
||||||
|
type: Array as PropType<DispatcherHistory[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mixins: [dateMixin, styleMixin, imageMixin],
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
computedDispatcherHistory() {
|
||||||
|
return this.dispatcherHistory.reduce((acc, historyItem, i) => {
|
||||||
|
if (this.isAnotherDay(i - 1, i)) acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL'));
|
||||||
|
acc.push(historyItem);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, [] as (DispatcherHistory | string)[]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
navigateToScenery(name: string, isOnline: boolean) {
|
||||||
|
if (!isOnline) return;
|
||||||
|
|
||||||
|
this.$router.push(`/scenery?station=${name.trim().replace(/ /g, '_')}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
isAnotherDay(prevIndex: number, currIndex: number) {
|
||||||
|
if (currIndex == 0) return true;
|
||||||
|
|
||||||
|
return (
|
||||||
|
new Date(this.dispatcherHistory[prevIndex].timestampFrom).getDate() !=
|
||||||
|
new Date(this.dispatcherHistory[currIndex].timestampFrom).getDate()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/animations.scss';
|
||||||
|
@import '../../styles/responsive.scss';
|
||||||
|
@import '../../styles/badge.scss';
|
||||||
|
@import '../../styles/JournalSection.scss';
|
||||||
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
|
li.sticky {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.journal_item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
gap: 0.5em 1em;
|
||||||
|
|
||||||
|
line-height: 1.7em;
|
||||||
|
padding: 0.75em;
|
||||||
|
|
||||||
|
&.online {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
span[data-status='true'] {
|
||||||
|
color: springgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
span[data-status='false'] {
|
||||||
|
color: salmon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-general {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.level-badge {
|
||||||
|
margin-right: 0.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.journal_day {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
padding: 0.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
background-color: #333;
|
||||||
|
|
||||||
|
span {
|
||||||
|
position: relative;
|
||||||
|
background-color: inherit;
|
||||||
|
z-index: 10;
|
||||||
|
padding-right: 1em;
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.like-count {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
font-size: 1.2em;
|
||||||
|
color: $accentCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.journal_item {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</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>
|
||||||
@@ -1,80 +1,121 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="journal-options">
|
<div class="filters-options" @keydown.esc="showOptions = false">
|
||||||
<div class="options_wrapper">
|
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
||||||
<div class="options_content">
|
|
||||||
<div class="content_select">
|
|
||||||
<select-box
|
|
||||||
:itemList="translatedSorterOptions"
|
|
||||||
:defaultItemIndex="0"
|
|
||||||
@selected="onSorterChange"
|
|
||||||
:prefix="$t('journal.sort-prefix')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content_search">
|
<div class="actions-bar">
|
||||||
<div class="search-box" v-for="(value, propName) in searchersValues" :key="propName">
|
<button class="filter-button btn--filled btn--image" @click="showOptions = !showOptions" ref="button">
|
||||||
<input
|
<img :src="getIcon('filter2')" alt="Open filters" />
|
||||||
class="search-input"
|
{{ $t('options.filters') }} [F]
|
||||||
:placeholder="$t(`journal.${propName}`)"
|
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
||||||
v-model="searchersValues[propName]"
|
</button>
|
||||||
@keydown.enter="onInputSearch"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img class="search-exit" :src="getIcon('exit')" alt="exit-icon" @click="onInputClear(propName)" />
|
<button class="filter-button btn--filled btn--image" @click="refreshData">
|
||||||
</div>
|
<img :src="getIcon('refresh')" alt="Refresh data" />
|
||||||
<!-- <div class="search-box">
|
{{ $t('general.refresh') }}
|
||||||
<input
|
</button>
|
||||||
class="search-input"
|
|
||||||
v-model="searchedTrain"
|
|
||||||
:placeholder="$t('journal.search-train')"
|
|
||||||
@keydown.enter="search"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img class="search-exit" :src="exitIcon" alt="exit-icon" @click="clearTrain" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="search-box">
|
|
||||||
<input
|
|
||||||
class="search-input"
|
|
||||||
v-model="searchedDriver"
|
|
||||||
:placeholder="$t('journal.search-driver')"
|
|
||||||
@keydown.enter="search"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img class="search-exit" :src="exitIcon" alt="exit-icon" @click="clearDriver" />
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<action-button class="search-button" @click="onInputSearch">
|
|
||||||
{{ $t('journal.search') }}
|
|
||||||
</action-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="options_filters">
|
|
||||||
<button
|
|
||||||
v-for="filter in filters"
|
|
||||||
class="journal-filter-option btn--option"
|
|
||||||
:class="{ checked: journalFilterActive.id === filter.id }"
|
|
||||||
:id="filter.id"
|
|
||||||
@click="onFilterChange(filter)"
|
|
||||||
>
|
|
||||||
{{ $t(`journal.filter-${filter.id}`) }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</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">
|
||||||
|
<div class="options_wrapper" v-if="showOptions">
|
||||||
|
<div class="options_content">
|
||||||
|
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
||||||
|
<div class="search_content">
|
||||||
|
<div class="search" v-for="(_, propName) in searchersValues" :key="propName">
|
||||||
|
<label v-if="propName == 'search-date'" for="date">{{ $t(`options.search-${optionsType}-date`) }}</label>
|
||||||
|
|
||||||
|
<div class="search-box">
|
||||||
|
<input
|
||||||
|
class="search-input"
|
||||||
|
v-model="searchersValues[propName]"
|
||||||
|
@keydown.enter="onSearchConfirm"
|
||||||
|
@focus="preventKeyDown = true"
|
||||||
|
@blur="preventKeyDown = false"
|
||||||
|
:placeholder="$t(`options.${propName}`)"
|
||||||
|
:type="propName == 'search-date' ? 'date' : 'text'"
|
||||||
|
:min="propName == 'search-date' ? '2022-02-01' : undefined"
|
||||||
|
:list="propName.toString()"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button class="search-exit" v-if="propName != 'search-date'">
|
||||||
|
<img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear(propName)" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="option-title">{{ $t('options.sort-title') }}</h1>
|
||||||
|
<div class="options_sorters">
|
||||||
|
<div v-for="opt in translatedSorterOptions">
|
||||||
|
<button
|
||||||
|
class="sort-option btn--option"
|
||||||
|
:data-selected="opt.id == sorterActive.id"
|
||||||
|
@click="onSorterChange(opt)"
|
||||||
|
>
|
||||||
|
{{ opt.value.toUpperCase() }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 class="option-title" v-if="filters.length != 0">{{ $t('options.filter-title') }}</h1>
|
||||||
|
|
||||||
|
<div class="options_filter-sections" v-if="filters.length != 0 && filterList">
|
||||||
|
<section class="filter-section" v-for="section in JournalFilterSection">
|
||||||
|
<p>{{ $t(`options.filter-section-${section}`) }}</p>
|
||||||
|
|
||||||
|
<div class="options_filters">
|
||||||
|
<button
|
||||||
|
v-for="filter in filterList.filter((f) => f.filterSection == section)"
|
||||||
|
class="filter-option btn--option"
|
||||||
|
:class="{ checked: filter.isActive }"
|
||||||
|
:id="filter.id"
|
||||||
|
@click="onFilterChange(filter)"
|
||||||
|
>
|
||||||
|
{{ $t(`options.filter-${filter.id}`) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options_actions">
|
||||||
|
<button class="btn--action" @click="onResetButtonClick">
|
||||||
|
{{ $t('options.reset-button') }}
|
||||||
|
</button>
|
||||||
|
<button class="btn--action" @click="onSearchButtonConfirm">
|
||||||
|
{{ $t('options.search-button') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, inject, JournalFilter, 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 { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
|
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: ['onSorterChange', 'onInputChange', 'onFilterChange'],
|
emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'],
|
||||||
mixins: [imageMixin],
|
mixins: [imageMixin, keyMixin],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
sorterOptionIds: {
|
sorterOptionIds: {
|
||||||
@@ -86,175 +127,174 @@ export default defineComponent({
|
|||||||
type: Array as PropType<JournalFilter[]>,
|
type: Array as PropType<JournalFilter[]>,
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dataStatus: {
|
||||||
|
type: Number as PropType<DataStatus>,
|
||||||
|
default: DataStatus.Initialized,
|
||||||
|
},
|
||||||
|
|
||||||
|
currentOptionsActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
optionsType: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showOptions: false,
|
||||||
|
JournalFilterSection,
|
||||||
|
|
||||||
|
driverSuggestions: [] as string[],
|
||||||
|
dispatcherSuggestions: [] as string[],
|
||||||
|
|
||||||
|
searchTimeout: 0,
|
||||||
|
store: useStore(),
|
||||||
|
|
||||||
|
DataStatus,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
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 JournalFilter,
|
// 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,
|
||||||
value: this.$t(`journal.option-${id}`),
|
value: this.$t(`options.sort-${id}`),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
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
|
||||||
|
onKeyDownFunction() {
|
||||||
|
this.showOptions = !this.showOptions;
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.showOptions) (this.$refs['button'] as HTMLButtonElement)?.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
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('onSorterChange');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onFilterChange(filter: JournalFilter) {
|
onFilterChange(filter: JournalFilter) {
|
||||||
this.journalFilterActive = filter;
|
// this.journalFilterActive = filter;
|
||||||
this.$emit('onFilterChange');
|
this.filterList?.filter((f) => f.filterSection === filter.filterSection).forEach((f) => (f.isActive = false));
|
||||||
},
|
filter.isActive = true;
|
||||||
|
|
||||||
onInputSearch() {
|
this.$emit('onSearchConfirm');
|
||||||
this.$emit('onInputChange');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onInputClear(id: any) {
|
onInputClear(id: any) {
|
||||||
this.searchersValues[id] = '';
|
this.searchersValues[id] = '';
|
||||||
this.onInputSearch();
|
this.$emit('onSearchConfirm');
|
||||||
|
},
|
||||||
|
|
||||||
|
onSearchConfirm() {
|
||||||
|
this.$emit('onSearchConfirm');
|
||||||
|
},
|
||||||
|
|
||||||
|
onSearchButtonConfirm() {
|
||||||
|
this.showOptions = false;
|
||||||
|
this.$emit('onSearchConfirm');
|
||||||
|
},
|
||||||
|
|
||||||
|
onResetButtonClick() {
|
||||||
|
this.$emit('onOptionsReset');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive';
|
@import '../../styles/filters_options.scss';
|
||||||
@import '../../styles/option.scss';
|
|
||||||
|
|
||||||
.options {
|
|
||||||
&_wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_content {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
.content_search,
|
|
||||||
.content_select {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
padding: 0.25em 0.25em 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&_filters {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin: 0.5em 0 0 0;
|
|
||||||
|
|
||||||
.journal-filter-option {
|
|
||||||
margin: 0 0.25em 0 0;
|
|
||||||
|
|
||||||
&#abandoned {
|
|
||||||
color: salmon;
|
|
||||||
}
|
|
||||||
|
|
||||||
&#fulfilled {
|
|
||||||
color: lightgreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
&#active {
|
|
||||||
color: lightblue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
|
||||||
&-box {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
background: #333;
|
|
||||||
border-radius: 0.5em;
|
|
||||||
min-width: 200px;
|
|
||||||
margin-right: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-input {
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
min-width: 100%;
|
|
||||||
padding: 0.35em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-exit {
|
|
||||||
position: absolute;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
top: 50%;
|
|
||||||
right: 10px;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
|
|
||||||
width: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
.journal-options {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.options {
|
|
||||||
&_wrapper {
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_content {
|
|
||||||
padding: 0 1em;
|
|
||||||
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.content_select {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content_search {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&_filters {
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.journal-filter-option {
|
|
||||||
margin: 0.25em 0.25em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
|
||||||
&-box,
|
|
||||||
&-button {
|
|
||||||
margin: 0.5em 0 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-box {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-button {
|
|
||||||
width: 80%;
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -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,439 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section class="journal-timetables">
|
|
||||||
<keep-alive>
|
|
||||||
<DriverStats v-if="statsCardOpen" @close-card="closeCard" />
|
|
||||||
</keep-alive>
|
|
||||||
|
|
||||||
<div class="journal-wrapper">
|
|
||||||
<div class="journal_top-bar">
|
|
||||||
<JournalOptions
|
|
||||||
@on-input-change="search"
|
|
||||||
@on-filter-change="search"
|
|
||||||
@on-sorter-change="search"
|
|
||||||
:sorter-option-ids="['timetableId', 'beginDate', 'distance', 'total-stops']"
|
|
||||||
:filters="journalTimetableFilters"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal-list">
|
|
||||||
<div class="list-wrapper" ref="scrollElement">
|
|
||||||
<transition name="warning" mode="out-in">
|
|
||||||
<div :key="historyDataStatus.status">
|
|
||||||
<Loading v-if="isDataLoading || isDataInit" />
|
|
||||||
|
|
||||||
<div v-else-if="isDataError" class="journal_warning error">
|
|
||||||
{{ $t('app.error') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal_warning" v-else-if="historyList.length == 0">
|
|
||||||
{{ $t('app.no-result') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul v-else>
|
|
||||||
<transition-group name="journal-list-anim">
|
|
||||||
<li v-for="(item, i) in historyList" class="journal_item" :key="item.timetableId">
|
|
||||||
<div class="journal_item-top">
|
|
||||||
<span>
|
|
||||||
<span
|
|
||||||
tabindex="0"
|
|
||||||
@click="navigateToTimetable(item)"
|
|
||||||
@keydown.enter="navigateToTimetable(item)"
|
|
||||||
style="cursor: pointer"
|
|
||||||
>
|
|
||||||
<b class="text--primary">{{ item.trainCategoryCode }} </b>
|
|
||||||
<b>{{ item.trainNo }}</b>
|
|
||||||
| <span>{{ item.driverName }}</span> |
|
|
||||||
<span class="text--grayed">#{{ item.timetableId }}</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<b>{{ item.route.replace('|', ' - ') }}</b>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr style="margin: 0.25em 0" />
|
|
||||||
|
|
||||||
<div class="scenery-list">
|
|
||||||
<span
|
|
||||||
v-for="(scenery, i) in getSceneryList(item)"
|
|
||||||
:key="scenery.name"
|
|
||||||
:class="{ confirmed: scenery.confirmed }"
|
|
||||||
>
|
|
||||||
{{ i > 0 ? ' > ' : '' }} {{ scenery.name }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="schedule-dates">
|
|
||||||
<!-- Data odjazdu ze stacji początkowej -->
|
|
||||||
<b>{{ item.route.split('|')[0] }}:</b>
|
|
||||||
<s v-if="item.beginDate != item.scheduledBeginDate" class="text--grayed">
|
|
||||||
{{ localeTime(item.beginDate, $i18n.locale) }}
|
|
||||||
</s>
|
|
||||||
<span>{{ localeTime(item.scheduledBeginDate, $i18n.locale) }} </span>•
|
|
||||||
|
|
||||||
<!-- Data przyjazdu na stację końcową / porzucenia -->
|
|
||||||
<b v-if="(item.fulfilled && item.terminated) || !item.terminated">
|
|
||||||
{{ item.route.split('|').slice(-1)[0] }}:
|
|
||||||
</b>
|
|
||||||
<i v-else>{{ $t('journal.timetable-abandoned') }} </i>
|
|
||||||
|
|
||||||
<s v-if="item.endDate != item.scheduledEndDate && item.terminated" class="text--grayed">
|
|
||||||
{{ localeTime(item.fulfilled ? item.endDate : item.scheduledEndDate, $i18n.locale) }}
|
|
||||||
</s>
|
|
||||||
<span
|
|
||||||
>{{ localeTime(item.fulfilled ? item.scheduledEndDate : item.endDate, $i18n.locale) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<b
|
|
||||||
class="journal_item-status"
|
|
||||||
:class="{
|
|
||||||
fulfilled: item.fulfilled || item.currentDistance >= item.routeDistance * 0.9,
|
|
||||||
terminated: item.terminated && !item.fulfilled,
|
|
||||||
active: !item.terminated,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
!item.terminated
|
|
||||||
? $t('journal.timetable-active')
|
|
||||||
: item.fulfilled || item.currentDistance >= item.routeDistance * 0.9
|
|
||||||
? $t('journal.timetable-fulfilled')
|
|
||||||
: $t('journal.timetable-abandoned')
|
|
||||||
}}
|
|
||||||
</b>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top: 1em">
|
|
||||||
<div>
|
|
||||||
{{ $t('journal.timetable-day') }} <b>{{ localeDay(item.beginDate, $i18n.locale) }}</b>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Nick dyżurnego -->
|
|
||||||
<div v-if="item.authorName">
|
|
||||||
<b class="text--grayed">{{ $t('journal.dispatcher-name') }} </b>
|
|
||||||
<router-link
|
|
||||||
class="dispatcher-link"
|
|
||||||
:to="`/journal/dispatchers?dispatcherName=${item.authorName}`"
|
|
||||||
>{{ item.authorName }}</router-link
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top: 1em">
|
|
||||||
<div>
|
|
||||||
<b>{{ $t('journal.route-length') }}</b>
|
|
||||||
{{ !item.fulfilled ? item.currentDistance + ' /' : '' }}
|
|
||||||
{{ item.routeDistance }} km
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<b>{{ $t('journal.station-count') }}</b>
|
|
||||||
{{ item.confirmedStopsCount }} /
|
|
||||||
{{ item.allStopsCount }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</transition-group>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
|
|
||||||
<div class="journal_warning" v-else-if="!scrollDataLoaded">{{ $t('journal.loading-further-data') }}</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, JournalFilter, provide, reactive, Ref, ref } from 'vue';
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
import DriverStats from './DriverStats.vue';
|
|
||||||
import Loading from '../Global/Loading.vue';
|
|
||||||
import { journalTimetableFilters } from '../../data/journalFilters';
|
|
||||||
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';
|
|
||||||
|
|
||||||
const TIMETABLES_API_URL = `${URLs.stacjownikAPI}/api/getTimetables`;
|
|
||||||
|
|
||||||
type JournalTimetableSearcher = {
|
|
||||||
[key in 'search-driver' | 'search-train']: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { DriverStats, Loading, JournalOptions },
|
|
||||||
mixins: [dateMixin, routerMixin],
|
|
||||||
|
|
||||||
name: 'JournalTimetables',
|
|
||||||
|
|
||||||
props: {
|
|
||||||
timetableId: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
currentQuery: '',
|
|
||||||
scrollDataLoaded: true,
|
|
||||||
scrollNoMoreData: false,
|
|
||||||
|
|
||||||
showReturnButton: false,
|
|
||||||
statsCardOpen: false,
|
|
||||||
|
|
||||||
journalTimetableFilters,
|
|
||||||
}),
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const historyDataStatus: Ref<{ status: DataStatus; error: string | null }> = ref({
|
|
||||||
status: DataStatus.Loading,
|
|
||||||
error: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const sorterActive = ref({ id: 'timetableId', dir: -1 });
|
|
||||||
const journalFilterActive = ref(journalTimetableFilters[0]);
|
|
||||||
|
|
||||||
const searchersValues = reactive({
|
|
||||||
'search-train': '',
|
|
||||||
'search-driver': '',
|
|
||||||
} as JournalTimetableSearcher);
|
|
||||||
|
|
||||||
const countFromIndex = ref(0);
|
|
||||||
const countLimit = 15;
|
|
||||||
|
|
||||||
provide('searchersValues', searchersValues);
|
|
||||||
provide('sorterActive', sorterActive);
|
|
||||||
provide('journalFilterActive', journalFilterActive);
|
|
||||||
|
|
||||||
const scrollElement: Ref<HTMLElement | null> = ref(null);
|
|
||||||
|
|
||||||
return {
|
|
||||||
historyList: ref([]) as Ref<TimetableHistory[]>,
|
|
||||||
historyDataStatus,
|
|
||||||
|
|
||||||
isDataLoading: computed(() => historyDataStatus.value.status === DataStatus.Loading),
|
|
||||||
isDataError: computed(() => historyDataStatus.value.status === DataStatus.Error),
|
|
||||||
isDataInit: computed(() => historyDataStatus.value.status === DataStatus.Initialized),
|
|
||||||
|
|
||||||
sorterActive,
|
|
||||||
journalFilterActive,
|
|
||||||
searchersValues,
|
|
||||||
|
|
||||||
countFromIndex,
|
|
||||||
countLimit,
|
|
||||||
|
|
||||||
scrollElement,
|
|
||||||
maxCount: ref(15),
|
|
||||||
store: useStore(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
activated() {
|
|
||||||
window.addEventListener('scroll', this.handleScroll);
|
|
||||||
|
|
||||||
if (this.timetableId) {
|
|
||||||
this.searchersValues['search-train'] = `#${this.timetableId}`;
|
|
||||||
this.search();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
if (!this.timetableId) this.search();
|
|
||||||
},
|
|
||||||
|
|
||||||
deactivated() {
|
|
||||||
window.removeEventListener('scroll', this.handleScroll);
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
navigateToTimetable(historyItem: TimetableHistory) {
|
|
||||||
if (historyItem.terminated) return;
|
|
||||||
|
|
||||||
this.navigateTo('/trains', {
|
|
||||||
trainNo: historyItem.trainNo,
|
|
||||||
driverName: historyItem.driverName,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
closeCard() {
|
|
||||||
this.statsCardOpen = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
getSceneryList(historyItem: TimetableHistory) {
|
|
||||||
return historyItem.sceneriesString
|
|
||||||
.split('%')
|
|
||||||
.map((name, i) => ({ name, confirmed: i < historyItem.confirmedStopsCount }));
|
|
||||||
},
|
|
||||||
|
|
||||||
handleScroll() {
|
|
||||||
this.showReturnButton = window.scrollY > window.innerHeight;
|
|
||||||
|
|
||||||
const element = this.$refs.scrollElement as HTMLElement;
|
|
||||||
|
|
||||||
if (
|
|
||||||
element.getBoundingClientRect().bottom * 0.85 < window.innerHeight &&
|
|
||||||
this.scrollDataLoaded &&
|
|
||||||
!this.scrollNoMoreData &&
|
|
||||||
this.historyDataStatus.status == DataStatus.Loaded
|
|
||||||
)
|
|
||||||
this.addHistoryData();
|
|
||||||
},
|
|
||||||
|
|
||||||
scrollToTop() {
|
|
||||||
window.scrollTo({ top: 0 });
|
|
||||||
},
|
|
||||||
|
|
||||||
search() {
|
|
||||||
this.fetchHistoryData({
|
|
||||||
searchers: this.searchersValues,
|
|
||||||
filter: this.journalFilterActive,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.scrollNoMoreData = false;
|
|
||||||
this.scrollDataLoaded = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
async addHistoryData() {
|
|
||||||
this.scrollDataLoaded = false;
|
|
||||||
|
|
||||||
const countFrom = this.historyList.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.historyList.push(...responseData);
|
|
||||||
this.scrollDataLoaded = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchHistoryData(
|
|
||||||
props: {
|
|
||||||
searchers?: JournalTimetableSearcher;
|
|
||||||
filter?: JournalFilter;
|
|
||||||
} = {}
|
|
||||||
) {
|
|
||||||
this.historyDataStatus.status = DataStatus.Loading;
|
|
||||||
|
|
||||||
const queries: string[] = [];
|
|
||||||
|
|
||||||
const driver = props.searchers?.['search-driver'].trim();
|
|
||||||
const train = props.searchers?.['search-train'].trim();
|
|
||||||
|
|
||||||
if (driver) queries.push(`driverName=${driver}`);
|
|
||||||
if (train) queries.push(train.startsWith('#') ? `timetableId=${train.replace('#', '')}` : `trainNo=${train}`);
|
|
||||||
|
|
||||||
// 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.historyDataStatus.status = DataStatus.Error;
|
|
||||||
this.historyDataStatus.error = 'Brak danych!';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!responseData) return;
|
|
||||||
|
|
||||||
// Response data exists
|
|
||||||
this.historyList = responseData;
|
|
||||||
|
|
||||||
// Stats display
|
|
||||||
this.store.driverStatsName =
|
|
||||||
this.historyList.length > 0 && this.searchersValues['search-driver'].trim()
|
|
||||||
? this.historyList[0].driverName
|
|
||||||
: '';
|
|
||||||
|
|
||||||
this.historyDataStatus.status = DataStatus.Loaded;
|
|
||||||
} catch (error) {
|
|
||||||
this.historyDataStatus.status = DataStatus.Error;
|
|
||||||
this.historyDataStatus.error = 'Ups! Coś poszło nie tak!';
|
|
||||||
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/JournalSection.scss';
|
|
||||||
|
|
||||||
.journal_item {
|
|
||||||
&-top {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
padding: 0.2em 0;
|
|
||||||
|
|
||||||
.scenery-list {
|
|
||||||
span {
|
|
||||||
color: #adadad;
|
|
||||||
|
|
||||||
&.confirmed {
|
|
||||||
color: #a3eba3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-status {
|
|
||||||
&.terminated {
|
|
||||||
color: salmon;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.fulfilled {
|
|
||||||
color: lightgreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: lightblue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dispatcher-link {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,490 @@
|
|||||||
|
<template>
|
||||||
|
<transition-group class="journal-list" tag="ul" name="list-anim">
|
||||||
|
<li
|
||||||
|
v-for="{ timetable, stockHistoryComp, stops, showExtraInfo, ...item } in computedTimetableHistory"
|
||||||
|
class="journal_item"
|
||||||
|
:key="timetable.id"
|
||||||
|
@click="showExtraInfo.value = !showExtraInfo.value"
|
||||||
|
>
|
||||||
|
<div class="journal_item-info">
|
||||||
|
<div class="info-general">
|
||||||
|
<span
|
||||||
|
class="general-train"
|
||||||
|
tabindex="0"
|
||||||
|
@click.stop="showTimetable(timetable)"
|
||||||
|
@keydown.enter="showTimetable(timetable)"
|
||||||
|
style="cursor: pointer"
|
||||||
|
>
|
||||||
|
<span class="text--grayed">#{{ timetable.id }}</span>
|
||||||
|
|
||||||
|
<span class="badges" v-if="timetable.skr || timetable.twr">
|
||||||
|
<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 class="general-time">
|
||||||
|
<b class="info-date">{{ localeDay(timetable.beginDate, $i18n.locale) }}</b>
|
||||||
|
<b
|
||||||
|
class="info-badge"
|
||||||
|
:class="{
|
||||||
|
fulfilled: timetable.fulfilled,
|
||||||
|
terminated: timetable.terminated && !timetable.fulfilled,
|
||||||
|
active: !timetable.terminated,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
!timetable.terminated
|
||||||
|
? $t('journal.timetable-active')
|
||||||
|
: timetable.fulfilled
|
||||||
|
? $t('journal.timetable-fulfilled')
|
||||||
|
: `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}`
|
||||||
|
}}
|
||||||
|
</b>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-route">
|
||||||
|
<b>{{ timetable.route.replace('|', ' - ') }}</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<!-- Spis postojów -->
|
||||||
|
<div class="stop-list">
|
||||||
|
<span
|
||||||
|
v-for="(stop, i) in stops.filter((_, i) => (!showExtraInfo.value ? i == 0 || i == stops.length - 1 : true))"
|
||||||
|
class="stop-list-item"
|
||||||
|
:key="stop.stopName"
|
||||||
|
:data-confirmed="stop.confirmed"
|
||||||
|
>
|
||||||
|
<span v-if="i > 0">
|
||||||
|
>
|
||||||
|
<span v-if="!showExtraInfo.value && i == 1 && stops.length > 2">
|
||||||
|
... (+{{ stops.length - 2 }}) >
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="stop-name">{{ stop.stopName }}</span>
|
||||||
|
<span v-html="stop.html"></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status RJ -->
|
||||||
|
<div class="info-status" style="margin: 0.5em 0">
|
||||||
|
<span>
|
||||||
|
<b>{{ $t('journal.route-length') }}</b>
|
||||||
|
{{ !timetable.fulfilled ? timetable.currentDistance + ' /' : '' }}
|
||||||
|
{{ timetable.routeDistance }} km
|
||||||
|
</span>
|
||||||
|
•
|
||||||
|
<span>
|
||||||
|
<b>{{ $t('journal.station-count') }}</b>
|
||||||
|
{{ timetable.confirmedStopsCount }} /
|
||||||
|
{{ timetable.allStopsCount }}
|
||||||
|
</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/, '') }}
|
||||||
|
|
||||||
|
<span v-if="timetable.currentLocation[0] || timetable.currentLocation[1]">(</span>
|
||||||
|
|
||||||
|
<span v-if="timetable.currentLocation[1]">
|
||||||
|
{{ $t('journal.timetable-location-route') }} {{ timetable.currentLocation[1] }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-else-if="timetable.currentLocation[0]">
|
||||||
|
{{ $t('journal.timetable-location-signal') }} {{ timetable.currentLocation[0] }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-if="timetable.currentLocation[0] || timetable.currentLocation[1]">)</span>
|
||||||
|
</b>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Info o autorze RJ -->
|
||||||
|
<div class="info-author" v-if="timetable.authorName">
|
||||||
|
<b class="text--grayed">{{ $t('journal.dispatcher-name') }} </b>
|
||||||
|
<router-link class="dispatcher-link" :to="`/journal/dispatchers?dispatcherName=${timetable.authorName}`">
|
||||||
|
<b>{{ timetable.authorName }}</b>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<button class="btn--option btn--show">
|
||||||
|
{{ $t('journal.stock-info') }}
|
||||||
|
<img :src="getIcon(`arrow-${showExtraInfo.value ? 'asc' : 'desc'}`)" alt="Arrow" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Dodatkowe informacje -->
|
||||||
|
<div class="info-extended" v-if="timetable.stockString && timetable.stockMass && showExtraInfo.value">
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="stock-specs">
|
||||||
|
<span class="badge specs-badge">
|
||||||
|
<span>{{ $t('journal.stock-max-speed') }}</span>
|
||||||
|
<span>{{ timetable.maxSpeed }}km/h</span>
|
||||||
|
</span>
|
||||||
|
<span class="badge specs-badge">
|
||||||
|
<span>{{ $t('journal.stock-length') }}</span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
item.currentHistoryIndex.value == 0
|
||||||
|
? timetable.stockLength
|
||||||
|
: stockHistoryComp[item.currentHistoryIndex.value].stockLength || timetable.stockLength
|
||||||
|
}}m
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="badge specs-badge">
|
||||||
|
<span>{{ $t('journal.stock-mass') }}</span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
Math.floor(
|
||||||
|
(item.currentHistoryIndex.value == 0
|
||||||
|
? timetable.stockMass!
|
||||||
|
: stockHistoryComp[item.currentHistoryIndex.value].stockMass || timetable.stockMass) / 1000
|
||||||
|
)
|
||||||
|
}}t
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Historia zmian w składzie -->
|
||||||
|
<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">
|
||||||
|
<li
|
||||||
|
v-for="(car, i) in (item.currentHistoryIndex.value == 0
|
||||||
|
? timetable.stockString
|
||||||
|
: stockHistoryComp[item.currentHistoryIndex.value].stockString
|
||||||
|
).split(';')"
|
||||||
|
:key="i"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
@error="onImageError"
|
||||||
|
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${car.split(':')[0]}.png`"
|
||||||
|
:alt="car"
|
||||||
|
/>
|
||||||
|
<div>{{ car.replace(/_/g, ' ').split(':')[0] }}</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</transition-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType, ref } from 'vue';
|
||||||
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
|
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
timetableHistory: {
|
||||||
|
type: Array as PropType<TimetableHistory[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mixins: [dateMixin, imageMixin, modalTrainMixin, styleMixin],
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
computedTimetableHistory() {
|
||||||
|
return this.timetableHistory.map((timetable) => ({
|
||||||
|
timetable,
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
|
||||||
|
showExtraInfo: ref(false),
|
||||||
|
stops: this.getTimetableStops(timetable),
|
||||||
|
currentHistoryIndex: ref(0),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
getTimetableStops(timetable: TimetableHistory) {
|
||||||
|
const stopNames = timetable.sceneriesString.split('%');
|
||||||
|
|
||||||
|
const beginDateHTML = ` (o. ${
|
||||||
|
timetable.beginDate != timetable.scheduledBeginDate
|
||||||
|
? `<s class="text--grayed">${this.localeTime(timetable.beginDate, this.$i18n.locale)}</s>`
|
||||||
|
: ''
|
||||||
|
} <span>${this.localeTime(timetable.scheduledBeginDate, this.$i18n.locale)}</span>)`;
|
||||||
|
|
||||||
|
const endDateHTML = ` (p. ${
|
||||||
|
timetable.endDate != timetable.scheduledEndDate && timetable.fulfilled
|
||||||
|
? `<s class="text--grayed">${this.localeTime(timetable.endDate, this.$i18n.locale)}</s>`
|
||||||
|
: ''
|
||||||
|
} <span>${this.localeTime(timetable.scheduledEndDate, this.$i18n.locale)}</span>)`;
|
||||||
|
|
||||||
|
return stopNames.map((stopName, i) => {
|
||||||
|
const confirmed = i < timetable.confirmedStopsCount;
|
||||||
|
if (i == 0) return { stopName, html: beginDateHTML, confirmed };
|
||||||
|
if (i == stopNames.length - 1) return { stopName, html: endDateHTML, confirmed };
|
||||||
|
|
||||||
|
const departureDateScheduled = this.stringToDate(timetable.checkpointDeparturesScheduled?.at(i));
|
||||||
|
const departureDateReal = this.stringToDate(timetable.checkpointDepartures?.at(i));
|
||||||
|
const arrivalDateScheduled = this.stringToDate(timetable.checkpointArrivalsScheduled?.at(i));
|
||||||
|
const arrivalDateReal = this.stringToDate(timetable.checkpointArrivals?.at(i));
|
||||||
|
|
||||||
|
// const arrivalDelay =
|
||||||
|
// arrivalDateReal && arrivalDateScheduled ? arrivalDateReal.getTime() - arrivalDateScheduled.getTime() : 0;
|
||||||
|
|
||||||
|
// const departureDelay =
|
||||||
|
// departureDateReal && departureDateScheduled
|
||||||
|
// ? departureDateReal.getTime() - departureDateScheduled.getTime()
|
||||||
|
// : 0;
|
||||||
|
|
||||||
|
const arrivalHTML =
|
||||||
|
(arrivalDateReal && arrivalDateScheduled && arrivalDateReal?.getTime() != arrivalDateScheduled?.getTime()
|
||||||
|
? `<s class="text--grayed">${this.parseDateToTimeString(arrivalDateScheduled)}</s> `
|
||||||
|
: '') + this.parseDateToTimeString(arrivalDateReal || arrivalDateScheduled);
|
||||||
|
|
||||||
|
const departureHTML =
|
||||||
|
(departureDateReal &&
|
||||||
|
departureDateScheduled &&
|
||||||
|
departureDateReal?.getTime() != departureDateScheduled?.getTime()
|
||||||
|
? `<s class="text--grayed">${this.parseDateToTimeString(departureDateScheduled)}</s> `
|
||||||
|
: '') + this.parseDateToTimeString(departureDateReal || departureDateScheduled);
|
||||||
|
|
||||||
|
let html = `${arrivalHTML}${departureHTML ? ` / ${departureHTML}` : ''}`;
|
||||||
|
if (html) html = ` (${html})`;
|
||||||
|
|
||||||
|
return { stopName, html, confirmed };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
showTimetable(timetable: TimetableHistory) {
|
||||||
|
if (!timetable) return;
|
||||||
|
if (timetable.terminated) return;
|
||||||
|
|
||||||
|
this.selectModalTrain(timetable.driverName + timetable.trainNo.toString());
|
||||||
|
},
|
||||||
|
|
||||||
|
onImageError(e: Event) {
|
||||||
|
const imageEl = e.target as HTMLImageElement;
|
||||||
|
imageEl.src = this.getImage('unknown.png');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../styles/animations.scss';
|
||||||
|
@import '../../styles/variables.scss';
|
||||||
|
@import '../../styles/responsive.scss';
|
||||||
|
@import '../../styles/badge.scss';
|
||||||
|
@import '../../styles/JournalSection.scss';
|
||||||
|
|
||||||
|
.journal_item {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
&-date {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-badge {
|
||||||
|
padding: 0.05em 0.35em;
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
&.terminated {
|
||||||
|
background-color: salmon;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.fulfilled {
|
||||||
|
background-color: lightgreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: lightblue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-general {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
gap: 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-route {
|
||||||
|
margin: 0.25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-extended {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.general-train {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.stock-list {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
overflow: auto;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
li > div {
|
||||||
|
text-align: center;
|
||||||
|
color: #aaa;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.25em;
|
||||||
|
|
||||||
|
color: #adadad;
|
||||||
|
|
||||||
|
&-item[data-confirmed='true'] {
|
||||||
|
color: #a3eba3;
|
||||||
|
|
||||||
|
.stop-name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn--show {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.2em 0.45em;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 1.3em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.journal_item-info {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-route {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn--show {
|
||||||
|
margin: 1em auto 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-general,
|
||||||
|
.general-train,
|
||||||
|
.stock-specs,
|
||||||
|
.stock-history {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,37 +1,64 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="scenery-dispatchers-history scenery-section">
|
<section class="scenery-table-section">
|
||||||
<Loading v-if="dataStatus != 2" />
|
<Loading v-if="dataStatus != DataStatus.Loaded && historyList.length == 0" />
|
||||||
|
<div class="no-history" v-else-if="historyList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
||||||
|
|
||||||
<div class="list-warning" v-else-if="dispatcherHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
<table class="scenery-history-table" v-else="historyList.length">
|
||||||
|
<thead>
|
||||||
|
<th>{{ $t('scenery.dispatchers-history-hash') }}</th>
|
||||||
|
<th>{{ $t('scenery.dispatchers-history-dispatcher') }}</th>
|
||||||
|
<th>{{ $t('scenery.dispatchers-history-level') }}</th>
|
||||||
|
<th>{{ $t('scenery.dispatchers-history-rate') }}</th>
|
||||||
|
<th>{{ $t('scenery.dispatchers-history-date') }}</th>
|
||||||
|
</thead>
|
||||||
|
|
||||||
<ul class="history-list" v-else>
|
<tbody>
|
||||||
<li class="list-item" v-for="historyItem in dispatcherHistoryList">
|
<tr v-for="historyItem in historyList">
|
||||||
<div>
|
<td>#{{ historyItem.stationHash }}</td>
|
||||||
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
|
<td>
|
||||||
<span class="text--grayed">#{{ historyItem.stationHash }} </span>
|
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
|
||||||
<b>{{ historyItem.dispatcherName }}</b>
|
<b>{{ historyItem.dispatcherName }}</b>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</td>
|
||||||
|
<td>
|
||||||
|
<b
|
||||||
|
v-if="historyItem.dispatcherLevel !== null"
|
||||||
|
class="level-badge dispatcher"
|
||||||
|
:style="calculateExpStyle(historyItem.dispatcherLevel, historyItem.dispatcherIsSupporter)"
|
||||||
|
>
|
||||||
|
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
||||||
|
</b>
|
||||||
|
</td>
|
||||||
|
<td class="text--primary">
|
||||||
|
<b>{{ historyItem.dispatcherRate }}</b>
|
||||||
|
</td>
|
||||||
|
<td style="min-width: 300px">
|
||||||
|
<div v-if="historyItem.timestampTo">
|
||||||
|
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
||||||
|
|
||||||
<div v-if="historyItem.timestampTo">
|
{{ timestampToString(historyItem.timestampFrom) }}
|
||||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
- {{ timestampToString(historyItem.timestampTo) }} ({{ calculateDuration(historyItem.currentDuration) }})
|
||||||
|
</div>
|
||||||
|
|
||||||
{{ timestampToString(historyItem.timestampFrom) }}
|
<div class="dispatcher-online" v-else>
|
||||||
- {{ timestampToString(historyItem.timestampTo) }} ({{ calculateDuration(historyItem.currentDuration) }})
|
{{ $t('journal.online-since') }}
|
||||||
</div>
|
<b>{{ timestampToString(historyItem.timestampFrom) }}</b>
|
||||||
|
({{ calculateDuration(historyItem.currentDuration) }})
|
||||||
<div class="dispatcher-online" v-else>
|
</div>
|
||||||
{{ $t('journal.online-since') }}
|
</td>
|
||||||
<b>{{ timestampToString(historyItem.timestampFrom) }}</b>
|
</tr>
|
||||||
({{ calculateDuration(historyItem.currentDuration) }})
|
</tbody>
|
||||||
</div>
|
</table>
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<div class="bottom-info">
|
||||||
|
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory">
|
||||||
|
{{ $t('scenery.bottom-info') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
@@ -40,39 +67,53 @@ 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';
|
||||||
|
import listObserverMixin from '../../mixins/listObserverMixin';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryDispatchersHistory',
|
name: 'SceneryDispatchersHistory',
|
||||||
mixins: [dateMixin],
|
mixins: [dateMixin, styleMixin, listObserverMixin],
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>,
|
type: Object as PropType<Station>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
dispatcherHistoryList: [] as DispatcherHistory[],
|
historyList: [] as DispatcherHistory[],
|
||||||
dataStatus: DataStatus.Loading,
|
dataStatus: DataStatus.Loading,
|
||||||
|
DataStatus,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
this.fetchAPIData();
|
async activated() {
|
||||||
|
// if (this.historyList.length == 0) {
|
||||||
|
const fetchedHistory = await this.fetchAPIData();
|
||||||
|
if (fetchedHistory) this.historyList = fetchedHistory;
|
||||||
|
// }
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async fetchAPIData(countFrom = 0, countLimit = 30) {
|
async fetchAPIData(countFrom = 0, countLimit = 30): Promise<DispatcherHistory[] | null> {
|
||||||
try {
|
try {
|
||||||
|
this.dataStatus = DataStatus.Loading;
|
||||||
|
|
||||||
const requestString = `${URLs.stacjownikAPI}/api/getDispatchers?stationName=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
const requestString = `${URLs.stacjownikAPI}/api/getDispatchers?stationName=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
|
||||||
const historyAPIData: DispatcherHistory[] = await (await axios.get(requestString)).data;
|
const historyAPIData: DispatcherHistory[] = await (await axios.get(requestString)).data;
|
||||||
|
|
||||||
this.dispatcherHistoryList = historyAPIData;
|
|
||||||
this.dataStatus = DataStatus.Loaded;
|
this.dataStatus = DataStatus.Loaded;
|
||||||
|
return historyAPIData;
|
||||||
console.log(this.dispatcherHistoryList);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
this.dataStatus = DataStatus.Error;
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
navigateToHistory() {
|
||||||
|
this.$router.push(`/journal/dispatchers?sceneryName=${this.station.name}`);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: { Loading },
|
components: { Loading },
|
||||||
});
|
});
|
||||||
@@ -80,23 +121,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
@import '../../styles/SceneryView/styles.scss';
|
@import '../../styles/sceneryViewTables.scss';
|
||||||
|
|
||||||
.history-list {
|
.level-badge {
|
||||||
padding: 0 0.5em;
|
margin: 0 auto;
|
||||||
}
|
|
||||||
|
|
||||||
.list-item {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
text-align: left;
|
|
||||||
background-color: #353535;
|
|
||||||
padding: 0.5em;
|
|
||||||
margin: 0.5em 0;
|
|
||||||
|
|
||||||
line-height: 1.5em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dispatcher-online {
|
.dispatcher-online {
|
||||||
@@ -105,7 +133,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
.history-list {
|
.history-list {
|
||||||
font-size: 1.2em;
|
font-size: 1.1em;
|
||||||
}
|
}
|
||||||
.list-item {
|
.list-item {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -113,4 +141,3 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-header">
|
<section class="info-header">
|
||||||
<div class="scenery-name">
|
<a class="scenery-name" :href="station.generalInfo?.url" target="_blank">
|
||||||
{{ station.name }}
|
{{ station.name }}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="scenery-abbrev">
|
||||||
|
{{ $t('scenery.abbrev') }} <b>{{ station.generalInfo?.abbr }}</b>
|
||||||
</div>
|
</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>
|
||||||
@@ -12,7 +16,6 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import Station from '../../scripts/interfaces/Station';
|
import Station from '../../scripts/interfaces/Station';
|
||||||
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
@@ -27,22 +30,24 @@ export default defineComponent({
|
|||||||
@import '../../styles/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
|
.info-header {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.scenery-name {
|
.scenery-name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
font-size: 3em;
|
||||||
position: relative;
|
|
||||||
|
|
||||||
font-size: 3.5em;
|
|
||||||
padding: 0 0.5em;
|
|
||||||
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
@include smallScreen() {
|
.scenery-abbrev {
|
||||||
font-size: 2.75em;
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -109,7 +115,7 @@ h3.section-header {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
font-size: 1.5em;
|
font-size: 1.2em;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 1.1em;
|
width: 1.1em;
|
||||||
@@ -125,12 +131,11 @@ h3.section-header {
|
|||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-general {
|
.scenery-info-general {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
font-size: 1.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>
|
||||||
|
|
||||||
@@ -64,6 +67,7 @@ export default defineComponent({
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
.dispatcher {
|
.dispatcher {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
@@ -82,17 +86,15 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
&_name {
|
&_name {
|
||||||
margin-right: 0.4em;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
margin-right: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_likes {
|
&_likes {
|
||||||
img {
|
img {
|
||||||
height: 0.7em;
|
height: 0.7em;
|
||||||
margin-right: 0.25em;
|
margin: 0 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
margin-right: 1.5em;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="!station.generalInfo"
|
v-if="!station.generalInfo"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="getImage('unknown.png')"
|
:src="getIcon('unknown')"
|
||||||
alt="icon-unknown"
|
alt="icon-unknown"
|
||||||
:title="$t('desc.unknown')"
|
:title="$t('desc.unknown')"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,9 @@
|
|||||||
<span v-if="station.onlineInfo">
|
<span v-if="station.onlineInfo">
|
||||||
<span
|
<span
|
||||||
class="badge spawn"
|
class="badge spawn"
|
||||||
v-for="(spawn, i) in station.onlineInfo.spawns"
|
v-for="(spawn, i) in sortedSpawns"
|
||||||
:key="spawn.spawnName + station.onlineInfo?.dispatcherName + i"
|
:key="spawn.spawnName + station.onlineInfo?.dispatcherName + i"
|
||||||
|
:data-electrified="spawn.isElectrified"
|
||||||
>
|
>
|
||||||
<span class="spawn_name">{{ spawn.spawnName }}</span>
|
<span class="spawn_name">{{ spawn.spawnName }}</span>
|
||||||
<span class="spawn_length">{{ spawn.spawnLength }}m</span>
|
<span class="spawn_length">{{ spawn.spawnLength }}m</span>
|
||||||
@@ -37,6 +38,12 @@ export default defineComponent({
|
|||||||
default: {},
|
default: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
sortedSpawns() {
|
||||||
|
return this.station.onlineInfo?.spawns.sort((s1, s2) => (s1.spawnLength < s2.spawnLength ? 1 : -1));
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -44,9 +51,15 @@ export default defineComponent({
|
|||||||
@import '../../../styles/variables.scss';
|
@import '../../../styles/variables.scss';
|
||||||
|
|
||||||
.spawn {
|
.spawn {
|
||||||
|
color: white;
|
||||||
|
|
||||||
&_length {
|
&_length {
|
||||||
background: $accentCol;
|
background-color: #404040;
|
||||||
color: black;
|
color: #cfcfcf;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-electrified='true'] > &_name {
|
||||||
|
background-color: #007599;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,32 +2,53 @@
|
|||||||
<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">
|
||||||
<button
|
<span v-for="(cp, i) in station.generalInfo.checkpoints" :key="i">
|
||||||
v-for="cp in station.generalInfo.checkpoints"
|
{{ (i > 0 && '•') || '' }}
|
||||||
:key="cp.checkpointName"
|
|
||||||
class="checkpoint_item btn btn--text"
|
<button
|
||||||
:class="{ current: selectedCheckpoint === cp.checkpointName }"
|
:key="cp.checkpointName"
|
||||||
@click="selectCheckpoint(cp)"
|
class="checkpoint_item"
|
||||||
>
|
:class="{ current: selectedCheckpoint === cp.checkpointName }"
|
||||||
{{ cp.checkpointName }}
|
@click="selectCheckpoint(cp)"
|
||||||
</button>
|
>
|
||||||
|
{{ cp.checkpointName }}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="timetable-list">
|
<div class="timetable-list">
|
||||||
<!-- <transition name="scenery-timetable-list-anim" mode="out-in"> -->
|
|
||||||
<!-- <div :key="store.dataStatuses.trains + selectedCheckpoint" class="scenery-timetable-list"> -->
|
|
||||||
<div style="padding-bottom: 5em" v-if="store.dataStatuses.trains == 0 && computedScheduledTrains.length == 0">
|
<div style="padding-bottom: 5em" v-if="store.dataStatuses.trains == 0 && computedScheduledTrains.length == 0">
|
||||||
<Loading />
|
<Loading />
|
||||||
</div>
|
</div>
|
||||||
@@ -36,123 +57,120 @@
|
|||||||
{{ $t('scenery.offline') }}
|
{{ $t('scenery.offline') }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="timetable-item empty" v-else-if="computedScheduledTrains.length == 0">
|
<span class="timetable-item empty" v-else-if="computedScheduledTrains.length == 0">
|
||||||
{{ $t('scenery.no-timetables') }}
|
{{ $t('scenery.no-timetables') }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div
|
<transition-group name="list-anim">
|
||||||
class="timetable-item"
|
<div
|
||||||
v-for="(scheduledTrain, i) in computedScheduledTrains"
|
class="timetable-item"
|
||||||
:key="i + 1"
|
v-for="(scheduledTrain, i) in computedScheduledTrains"
|
||||||
tabindex="0"
|
:key="scheduledTrain.trainId"
|
||||||
@click.prevent.stop="selectModalTrain(scheduledTrain.trainId)"
|
tabindex="0"
|
||||||
@keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId)"
|
@click.prevent.stop="selectModalTrain(scheduledTrain.trainId)"
|
||||||
>
|
@keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId)"
|
||||||
<span class="timetable-general">
|
>
|
||||||
<span class="general-info">
|
<span class="timetable-general">
|
||||||
<span class="info-number">
|
<span class="general-info">
|
||||||
<strong>{{ scheduledTrain.category }}</strong>
|
<span class="info-number">
|
||||||
{{ scheduledTrain.trainNo }}
|
<strong>{{ scheduledTrain.category }}</strong>
|
||||||
|
{{ scheduledTrain.trainNo }}
|
||||||
|
|
||||||
<span class="g-tooltip" v-if="scheduledTrain.stopInfo.comments">
|
<span class="g-tooltip" v-if="scheduledTrain.stopInfo.comments">
|
||||||
<img :src="getIcon('warning')" />
|
<img :src="getIcon('warning')" />
|
||||||
<span class="content" v-html="scheduledTrain.stopInfo.comments"> </span>
|
<span class="content" v-html="scheduledTrain.stopInfo.comments"> </span>
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
|
||||||
<span style="color: white">
|
|
||||||
{{ scheduledTrain.driverName }}
|
|
||||||
</span>
|
|
||||||
|
|
|
||||||
<span class="general-status">
|
|
||||||
<span :class="scheduledTrain.stopStatus">
|
|
||||||
{{ $t(`timetables.${scheduledTrain.stopStatus}`) }}
|
|
||||||
<span v-if="scheduledTrain.stopStatus == 'arriving'"> {{ scheduledTrain.prevStationName }}</span>
|
|
||||||
<span v-if="scheduledTrain.stopStatus.startsWith('departed')">{{
|
|
||||||
scheduledTrain.nextStationName
|
|
||||||
}}</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<div class="info-route">
|
|
||||||
<strong>{{ scheduledTrain.beginsAt }} - {{ scheduledTrain.terminatesAt }}</strong>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="timetable-schedule">
|
|
||||||
<span class="schedule-arrival">
|
|
||||||
<span class="arrival-time begins" v-if="scheduledTrain.stopInfo.beginsHere">
|
|
||||||
{{ $t('timetables.begins') }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="arrival-time" v-else>
|
|
||||||
<div v-if="scheduledTrain.stopInfo.arrivalDelay == 0">
|
|
||||||
<span>{{ timestampToString(scheduledTrain.stopInfo.arrivalTimestamp) }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<div>
|
|
||||||
<s style="margin-right: 0.2em" class="text--grayed">{{
|
|
||||||
timestampToString(scheduledTrain.stopInfo.arrivalTimestamp)
|
|
||||||
}}</s>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span>
|
|
||||||
{{ timestampToString(scheduledTrain.stopInfo.arrivalRealTimestamp) }}
|
|
||||||
({{ scheduledTrain.stopInfo.arrivalDelay > 0 ? '+' : '' }}{{ scheduledTrain.stopInfo.arrivalDelay }})
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</span>
|
||||||
</span>
|
|
|
||||||
</span>
|
<span>
|
||||||
|
{{ scheduledTrain.driverName }}
|
||||||
<span class="schedule-stop">
|
|
||||||
<span class="stop-time">
|
|
||||||
<span v-if="scheduledTrain.stopInfo.stopTime">
|
|
||||||
{{ scheduledTrain.stopInfo.stopTime }}
|
|
||||||
{{ scheduledTrain.stopInfo.stopType || 'pt' }}
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-else> </span>
|
<div class="info-route">
|
||||||
</span>
|
<strong>{{ scheduledTrain.beginsAt }} - {{ scheduledTrain.terminatesAt }}</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span class="arrow"></span>
|
<ScheduledTrainStatus :scheduledTrain="scheduledTrain" />
|
||||||
|
|
||||||
<span class="stop-line">
|
|
||||||
{{ scheduledTrain.arrivingLine }}
|
|
||||||
{{ scheduledTrain.arrivingLine && scheduledTrain.departureLine && '>' }}
|
|
||||||
{{ scheduledTrain.departureLine }}
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="schedule-departure">
|
<span class="timetable-schedule">
|
||||||
<span class="departure-time terminates" v-if="scheduledTrain.stopInfo.terminatesHere">
|
<span class="schedule-arrival">
|
||||||
{{ $t('timetables.terminates') }}
|
<span class="arrival-time begins" v-if="scheduledTrain.stopInfo.beginsHere">
|
||||||
</span>
|
{{ $t('timetables.begins') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
<span class="departure-time" v-else>
|
<span class="arrival-time" v-else>
|
||||||
<div v-if="scheduledTrain.stopInfo.departureDelay == 0">
|
<div v-if="scheduledTrain.stopInfo.arrivalDelay == 0">
|
||||||
<span>{{ timestampToString(scheduledTrain.stopInfo.departureTimestamp) }}</span>
|
<span>{{ timestampToString(scheduledTrain.stopInfo.arrivalTimestamp) }}</span>
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<div>
|
|
||||||
<s style="margin-right: 0.2em" class="text--grayed">{{
|
|
||||||
timestampToString(scheduledTrain.stopInfo.departureTimestamp)
|
|
||||||
}}</s>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div>
|
||||||
|
<s style="margin-right: 0.2em" class="text--grayed">{{
|
||||||
|
timestampToString(scheduledTrain.stopInfo.arrivalTimestamp)
|
||||||
|
}}</s>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{{ timestampToString(scheduledTrain.stopInfo.departureRealTimestamp) }}
|
{{ timestampToString(scheduledTrain.stopInfo.arrivalRealTimestamp) }}
|
||||||
({{ scheduledTrain.stopInfo.departureDelay > 0 ? '+' : ''
|
({{ scheduledTrain.stopInfo.arrivalDelay > 0 ? '+' : ''
|
||||||
}}{{ scheduledTrain.stopInfo.departureDelay }})
|
}}{{ scheduledTrain.stopInfo.arrivalDelay }})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="schedule-stop">
|
||||||
|
<span class="stop-time">
|
||||||
|
<span v-if="scheduledTrain.stopInfo.stopTime">
|
||||||
|
{{ scheduledTrain.stopInfo.stopTime }}
|
||||||
|
{{ scheduledTrain.stopInfo.stopType || 'pt' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
|
<span v-else> </span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="arrow"></span>
|
||||||
|
|
||||||
|
<span class="stop-line">
|
||||||
|
<span>
|
||||||
|
{{ scheduledTrain.arrivingLine }}
|
||||||
|
</span>
|
||||||
|
<span></span>
|
||||||
|
<span>
|
||||||
|
{{ scheduledTrain.departureLine }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="schedule-departure">
|
||||||
|
<span class="departure-time terminates" v-if="scheduledTrain.stopInfo.terminatesHere">
|
||||||
|
{{ $t('timetables.terminates') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="departure-time" v-else>
|
||||||
|
<div v-if="scheduledTrain.stopInfo.departureDelay == 0">
|
||||||
|
<span>{{ timestampToString(scheduledTrain.stopInfo.departureTimestamp) }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div>
|
||||||
|
<s style="margin-right: 0.2em" class="text--grayed">{{
|
||||||
|
timestampToString(scheduledTrain.stopInfo.departureTimestamp)
|
||||||
|
}}</s>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{{ timestampToString(scheduledTrain.stopInfo.departureRealTimestamp) }}
|
||||||
|
({{ scheduledTrain.stopInfo.departureDelay > 0 ? '+' : ''
|
||||||
|
}}{{ scheduledTrain.stopInfo.departureDelay }})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- </transition> -->
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -169,11 +187,12 @@ import Station from '../../scripts/interfaces/Station';
|
|||||||
import { useStore } from '../../store/store';
|
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';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryTimetable',
|
name: 'SceneryTimetable',
|
||||||
|
|
||||||
components: { SelectBox, Loading, TrainModal },
|
components: { SelectBox, Loading, TrainModal, ScheduledTrainStatus },
|
||||||
|
|
||||||
mixins: [dateMixin, routerMixin, imageMixin, modalTrainMixin],
|
mixins: [dateMixin, routerMixin, imageMixin, modalTrainMixin],
|
||||||
|
|
||||||
@@ -182,11 +201,14 @@ export default defineComponent({
|
|||||||
type: Object as PropType<Station>,
|
type: Object as PropType<Station>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
timetableOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
listOpen: false,
|
listOpen: false,
|
||||||
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
@@ -250,6 +272,10 @@ export default defineComponent({
|
|||||||
selectCheckpoint(cp: { checkpointName: string }) {
|
selectCheckpoint(cp: { checkpointName: string }) {
|
||||||
this.selectedCheckpoint = cp.checkpointName;
|
this.selectedCheckpoint = cp.checkpointName;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showTimetableOnlyView() {
|
||||||
|
this.$router.push(`${this.$route.fullPath}&timetableOnly=1`);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
@@ -265,12 +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';
|
||||||
// .scenery-timetable {
|
|
||||||
// height: 85vh;
|
|
||||||
// max-height: 900px;
|
|
||||||
// min-height: 450px;
|
|
||||||
// }
|
|
||||||
|
|
||||||
.scenery-timetable {
|
.scenery-timetable {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -279,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;
|
||||||
font-size: 1.4em;
|
|
||||||
|
gap: 0.5em;
|
||||||
|
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;
|
||||||
@@ -304,12 +337,14 @@ export default defineComponent({
|
|||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
margin: 0.5em auto;
|
margin: 0.5em auto;
|
||||||
padding: 0 0.5em;
|
padding: 0.5em;
|
||||||
max-width: 1100px;
|
max-width: 1100px;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||||
gap: 0 0.5em;
|
gap: 2em 0.5em;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
background: #353535;
|
background: #353535;
|
||||||
|
|
||||||
@@ -324,9 +359,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-general {
|
&-general {
|
||||||
padding: 0.5rem 0;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -337,6 +369,10 @@ export default defineComponent({
|
|||||||
&-schedule {
|
&-schedule {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(30px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(30px, 1fr));
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,18 +386,17 @@ export default defineComponent({
|
|||||||
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
padding: 0.75em 0;
|
|
||||||
.checkpoint_item {
|
|
||||||
&.current {
|
|
||||||
font-weight: bold;
|
|
||||||
color: $accentCol;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:last-child)::after {
|
margin-top: 0.5em;
|
||||||
margin: 0 0.5em;
|
|
||||||
content: '•';
|
button.checkpoint_item {
|
||||||
color: white;
|
color: #aaa;
|
||||||
}
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkpoint_item.current {
|
||||||
|
font-weight: bold;
|
||||||
|
color: $accentCol;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,7 +436,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-route {
|
.info-route {
|
||||||
margin-top: 0.5em;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,38 +451,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-status {
|
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
span.arriving {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.departed {
|
|
||||||
color: lime;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
&-away {
|
|
||||||
font-weight: bold;
|
|
||||||
color: #5ecc5e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
span.stopped {
|
|
||||||
color: #ffa600;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.online {
|
|
||||||
color: gold;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.terminated {
|
|
||||||
color: salmon;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.schedule {
|
.schedule {
|
||||||
&-arrival,
|
&-arrival,
|
||||||
&-stop,
|
&-stop,
|
||||||
@@ -458,23 +460,40 @@ export default defineComponent({
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
margin: 0 0.3rem;
|
margin: 0 0.3rem;
|
||||||
font-size: 1.1em;
|
font-size: 1.15em;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-stop {
|
&-stop {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
font-size: 0.85em;
|
font-size: 0.9em;
|
||||||
|
|
||||||
padding: 0.3em 0;
|
padding: 0.3em 0;
|
||||||
|
|
||||||
.stop-line {
|
.stop-line {
|
||||||
margin-top: 0.25em;
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
span {
|
||||||
|
width: 65px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
span:first-child {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
span:last-child {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.stop-time {
|
.stop-time {
|
||||||
transform: translateY(-0.25em);
|
position: absolute;
|
||||||
|
transform: translateY(-15px);
|
||||||
|
|
||||||
|
color: $accentCol;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -484,38 +503,9 @@ export default defineComponent({
|
|||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenery-timetable-list-anim {
|
@include smallScreen {
|
||||||
&-enter-from,
|
.timetable-item {
|
||||||
&-leave-to {
|
grid-template-columns: 1fr;
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-active {
|
|
||||||
transition: all 100ms ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-leave-active {
|
|
||||||
transition: all 100ms ease-out 100ms;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
.timetable {
|
|
||||||
&-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
font-size: 1.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-general {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-schedule {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,39 +1,54 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="scenery-timetables-history scenery-section">
|
<section class="scenery-table-section">
|
||||||
<Loading v-if="dataStatus != 2" />
|
<Loading v-if="dataStatus != DataStatus.Loaded" />
|
||||||
|
<div class="no-history" v-else-if="historyList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
||||||
|
|
||||||
<div class="list-warning" v-else-if="sceneryHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
<table class="scenery-history-table" v-else>
|
||||||
<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 historyList">
|
||||||
<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 }}
|
||||||
<div>{{ historyItem.route.replace('|', ' -> ') }}</div>
|
</td>
|
||||||
<!-- <div>{{ historyItem.routeDistance }} km</div> -->
|
<td>{{ historyItem.route.replace('|', ' -> ') }}</td>
|
||||||
<div>
|
<td>{{ historyItem.driverName }}</td>
|
||||||
{{ $t('scenery.timetable-author-title') }}:
|
<td>
|
||||||
<b v-if="historyItem.authorName">{{ historyItem.authorName }}</b>
|
<router-link
|
||||||
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
v-if="historyItem.authorName"
|
||||||
</div>
|
:to="`/journal/timetables?authorName=${historyItem.authorName}`"
|
||||||
|
>{{ historyItem.authorName }}
|
||||||
<!-- <div v-if="historyItem.authorId">{{ historyItem.authorName }}</div> -->
|
</router-link>
|
||||||
</li>
|
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
|
||||||
</ul>
|
</td>
|
||||||
|
<td>
|
||||||
|
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
|
||||||
|
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<div class="bottom-info">
|
||||||
|
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory()">
|
||||||
|
{{ $t('scenery.bottom-info') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { defineComponent, PropType } from 'vue';
|
import { defineComponent, PropType } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
@@ -42,37 +57,48 @@ import { TimetableHistory, SceneryTimetableHistory } from '../../scripts/interfa
|
|||||||
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 listObserverMixin from '../../mixins/listObserverMixin';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryTimetablesHistory',
|
name: 'SceneryTimetablesHistory',
|
||||||
mixins: [dateMixin],
|
mixins: [dateMixin, listObserverMixin],
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>,
|
type: Object as PropType<Station>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
sceneryHistoryList: [] as TimetableHistory[],
|
historyList: [] as TimetableHistory[],
|
||||||
dataStatus: DataStatus.Loading,
|
dataStatus: DataStatus.Loading,
|
||||||
|
DataStatus,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
this.fetchAPIData();
|
async activated() {
|
||||||
|
const fetchedHistory = await this.fetchAPIData();
|
||||||
|
if (fetchedHistory) this.historyList = fetchedHistory.timetables;
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async fetchAPIData(countFrom = 0, countLimit = 15) {
|
async fetchAPIData(countFrom = 0, countLimit = 15): Promise<SceneryTimetableHistory | null> {
|
||||||
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.dataStatus = DataStatus.Loaded;
|
this.dataStatus = DataStatus.Loaded;
|
||||||
|
return historyAPIData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
navigateToHistory() {
|
||||||
|
this.$router.push(`/journal/timetables?issuedFrom=${this.station.name}`);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: { Loading },
|
components: { Loading },
|
||||||
});
|
});
|
||||||
@@ -80,39 +106,5 @@ export default defineComponent({
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive.scss';
|
@import '../../styles/responsive.scss';
|
||||||
@import '../../styles/SceneryView/styles.scss';
|
@import '../../styles/sceneryViewTables.scss';
|
||||||
|
|
||||||
.list-warning {
|
|
||||||
padding: 1em 0.5em;
|
|
||||||
background-color: #444;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.history-list {
|
|
||||||
padding: 0 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-item {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 2fr 2fr 1fr;
|
|
||||||
gap: 1em;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
background-color: #353535;
|
|
||||||
padding: 0.5em;
|
|
||||||
margin: 0.5em 0;
|
|
||||||
|
|
||||||
line-height: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen {
|
|
||||||
.history-list {
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
.list-item {
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
font-size: 1.05em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<template>
|
||||||
|
<div class="general-status">
|
||||||
|
<span :class="computedScheduledTrain.stopStatus" :title="computedScheduledTrain.stopStatusDescription">
|
||||||
|
{{ computedScheduledTrain.stopStatusIndicator }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from 'vue';
|
||||||
|
import { ScheduledTrain, StopStatus } from '../../scripts/interfaces/ScheduledTrain';
|
||||||
|
|
||||||
|
interface ScheduledTrainComp extends ScheduledTrain {
|
||||||
|
stopStatusIndicator: string;
|
||||||
|
stopStatusDescription: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
scheduledTrain: {
|
||||||
|
type: Object as PropType<ScheduledTrain>,
|
||||||
|
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>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.general-status {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
|
||||||
|
span.arriving {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.departed {
|
||||||
|
color: lime;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&-away {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #5ecc5e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span.stopped {
|
||||||
|
color: #ffa600;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.online {
|
||||||
|
color: gold;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.terminated {
|
||||||
|
color: salmon;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -1,23 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="filter-option option">
|
<button
|
||||||
<label>
|
class="btn--action"
|
||||||
<input
|
:class="option.section"
|
||||||
type="checkbox"
|
:data-selected="option.value"
|
||||||
:name="option.name"
|
@click="handleLeftClick"
|
||||||
:defaultValue="option.defaultValue"
|
@dblclick="handleDbClick"
|
||||||
:id="option.id"
|
>
|
||||||
v-model="option.value"
|
{{ $t(`filters.${option.id}`) }}
|
||||||
@change="handleChange"
|
</button>
|
||||||
/>
|
|
||||||
<span v-if="option.id != 'troll'" :class="option.section + (option.value ? ' checked' : '')"
|
|
||||||
>{{ option.id != 'troll' ? $t(`filters.${option.id}`) : 'ARKADIA ZDRÓJ' }}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
|
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
||||||
|
|
||||||
interface FilterOption {
|
interface FilterOption {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -34,29 +29,54 @@ export default defineComponent({
|
|||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
emits: ['optionChange'],
|
|
||||||
methods: {
|
|
||||||
handleChange() {
|
|
||||||
if (this.option.name == 'troll') {
|
|
||||||
location.href = 'https://www.youtube.com/watch?v=HIcSWuKMwOw';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$emit('optionChange', {
|
setup() {
|
||||||
|
return {
|
||||||
|
filterStore: useStationFiltersStore(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
handleLeftClick() {
|
||||||
|
this.option.value = !this.option.value;
|
||||||
|
this.filterStore.lastClickedFilterId = '';
|
||||||
|
|
||||||
|
this.filterStore.changeFilterValue({
|
||||||
name: this.option.name,
|
name: this.option.name,
|
||||||
value: this.option.value,
|
value: !this.option.value,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup() {
|
handleDbClick(e: Event) {
|
||||||
return {};
|
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>
|
||||||
@import '../../styles/option.scss';
|
$realityCol: #e03b07;
|
||||||
|
|
||||||
$accessCol: #e03b07;
|
$accessCol: #e03b07;
|
||||||
$controlCol: #0085ff;
|
$controlCol: #0085ff;
|
||||||
$signalCol: #bf7c00;
|
$signalCol: #bf7c00;
|
||||||
@@ -64,83 +84,18 @@ $statusCol: #349b32;
|
|||||||
$saveCol: #28a826;
|
$saveCol: #28a826;
|
||||||
$routesCol: #9049c0;
|
$routesCol: #9049c0;
|
||||||
|
|
||||||
.option span {
|
button {
|
||||||
font-size: 0.9em;
|
padding: 0.25em;
|
||||||
&.checked {
|
border-radius: 0;
|
||||||
&.access {
|
|
||||||
background-color: $accessCol;
|
|
||||||
|
|
||||||
&::before {
|
&:focus-visible {
|
||||||
box-shadow: 0 0 6px 1px $accessCol;
|
outline: 1px solid white;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&.control {
|
&[data-selected='true'] {
|
||||||
background-color: $controlCol;
|
background-color: forestgreen;
|
||||||
|
font-weight: bold;
|
||||||
&::before {
|
|
||||||
box-shadow: 0 0 6px 1px $controlCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.signals {
|
|
||||||
background-color: $signalCol;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
box-shadow: 0 0 6px 1px $signalCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.routes {
|
|
||||||
background-color: $routesCol;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
box-shadow: 0 0 6px 1px $routesCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.status {
|
|
||||||
background-color: $statusCol;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
box-shadow: 0 0 6px 1px $statusCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.save {
|
|
||||||
background-color: $saveCol;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
box-shadow: 0 0 6px 1px $saveCol;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.troll {
|
|
||||||
background-color: firebrick;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
box-shadow: 0 0 6px 1px firebrick;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mode {
|
|
||||||
background-color: lightgreen;
|
|
||||||
color: black;
|
|
||||||
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
content: '';
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
border-radius: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,57 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="filter-card" v-click-outside="closeCard">
|
<section class="filter-card" v-click-outside="closeCard" @keydown.esc="closeCard">
|
||||||
<div class="card_btn">
|
<div class="card_controls">
|
||||||
<button class="btn btn--option" @click="toggleCard">
|
<button class="btn--filled btn--image" @click="toggleCard">
|
||||||
<img class="button_icon" :src="getIcon('filter2')" alt="icon-filter" />
|
<img class="button_icon" :src="getIcon('filter2')" alt="filter icon" />
|
||||||
{{ $t('options.filters') }}
|
{{ $t('options.filters') }} [F]
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<label for="scenery-search">
|
||||||
|
<input
|
||||||
|
id="scenery-search"
|
||||||
|
list="sceneries"
|
||||||
|
:placeholder="$t('sceneries.scenery-search')"
|
||||||
|
@focus="preventKeyDown = true"
|
||||||
|
@blur="preventKeyDown = false"
|
||||||
|
v-model="chosenSearchScenery"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<datalist id="sceneries">
|
||||||
|
<option v-for="scenery in sortedStationList" :value="scenery.name"></option>
|
||||||
|
</datalist>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition name="card-anim">
|
<transition name="card-anim">
|
||||||
<div class="card" v-if="isVisible">
|
<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 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">
|
||||||
<button @click="subHour">-</button>
|
<button class="btn--action" @click="subHour">-</button>
|
||||||
<span>{{
|
<span>{{
|
||||||
minimumHours == 0
|
minimumHours == 0
|
||||||
? $t('filters.now')
|
? $t('filters.now')
|
||||||
@@ -31,7 +59,7 @@
|
|||||||
? minimumHours + $t('filters.hour')
|
? minimumHours + $t('filters.hour')
|
||||||
: $t('filters.no-limit')
|
: $t('filters.no-limit')
|
||||||
}}</span>
|
}}</span>
|
||||||
<button @click="addHour">+</button>
|
<button class="btn--action" @click="addHour">+</button>
|
||||||
</span>
|
</span>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -42,11 +70,13 @@
|
|||||||
name="authors"
|
name="authors"
|
||||||
v-model="authorsInputValue"
|
v-model="authorsInputValue"
|
||||||
@input="handleAuthorsInput"
|
@input="handleAuthorsInput"
|
||||||
|
@focus="preventKeyDown = true"
|
||||||
|
@blur="preventKeyDown = false"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card_sliders">
|
<section class="card_sliders">
|
||||||
<div class="slider" v-for="(slider, i) in inputs.sliders" :key="i">
|
<div class="slider" v-for="(slider, i) in filterStore.inputs.sliders" :key="i">
|
||||||
<input
|
<input
|
||||||
class="slider-input"
|
class="slider-input"
|
||||||
type="range"
|
type="range"
|
||||||
@@ -63,39 +93,37 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card_actions">
|
|
||||||
<div>
|
|
||||||
<filter-option
|
|
||||||
@optionChange="saveFilters"
|
|
||||||
:option="{
|
|
||||||
id: 'save',
|
|
||||||
name: 'save',
|
|
||||||
section: 'mode',
|
|
||||||
value: saveOptions,
|
|
||||||
defaultValue: true,
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<action-button class="outlined" @click="resetFilters">
|
|
||||||
{{ $t('filters.reset') }}
|
|
||||||
</action-button>
|
|
||||||
<action-button class="outlined" @click="closeCard">{{ $t('filters.close') }}</action-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>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import { defineComponent, inject } from 'vue';
|
import { defineComponent, inject } from 'vue';
|
||||||
import inputData from '../../data/options.json';
|
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
import keyMixin from '../../mixins/keyMixin';
|
||||||
|
import routerMixin from '../../mixins/routerMixin';
|
||||||
import StorageManager from '../../scripts/managers/storageManager';
|
import StorageManager from '../../scripts/managers/storageManager';
|
||||||
|
import { useStationFiltersStore } from '../../store/stationFiltersStore';
|
||||||
import { useStore } from '../../store/store';
|
import { useStore } from '../../store/store';
|
||||||
|
|
||||||
import ActionButton from '../Global/ActionButton.vue';
|
import ActionButton from '../Global/ActionButton.vue';
|
||||||
@@ -103,12 +131,9 @@ import FilterOption from './FilterOption.vue';
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { ActionButton, FilterOption },
|
components: { ActionButton, FilterOption },
|
||||||
emits: ['changeFilterValue', 'invertFilters', 'resetFilters'],
|
mixins: [imageMixin, keyMixin, routerMixin],
|
||||||
mixins: [imageMixin],
|
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
|
||||||
inputs: { ...inputData },
|
|
||||||
saveOptions: false,
|
saveOptions: false,
|
||||||
STORAGE_KEY: 'options_saved',
|
STORAGE_KEY: 'options_saved',
|
||||||
|
|
||||||
@@ -118,15 +143,18 @@ export default defineComponent({
|
|||||||
currentRegion: { id: '', value: '' },
|
currentRegion: { id: '', value: '' },
|
||||||
|
|
||||||
delayInputTimer: -1,
|
delayInputTimer: -1,
|
||||||
|
chosenSearchScenery: '',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const isVisible = inject('isFilterCardVisible');
|
const isVisible = inject('isFilterCardVisible');
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
const filterStore = useStationFiltersStore();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isVisible,
|
isVisible,
|
||||||
store,
|
store,
|
||||||
|
filterStore,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -142,20 +170,41 @@ export default defineComponent({
|
|||||||
this.currentRegion = this.store.region;
|
this.currentRegion = this.store.region;
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
computed: {
|
||||||
handleChange(change: { name: string; value: boolean }) {
|
sortedStationList() {
|
||||||
this.$emit('changeFilterValue', {
|
return this.store.stationList
|
||||||
name: change.name,
|
.filter((s) => s.name.toLocaleLowerCase().includes(this.chosenSearchScenery.toLocaleLowerCase()))
|
||||||
value: !change.value,
|
.sort((s1, s2) => (s1.name > s2.name ? 1 : -1));
|
||||||
});
|
},
|
||||||
|
},
|
||||||
|
|
||||||
if (this.saveOptions) StorageManager.setBooleanValue(change.name, change.value);
|
watch: {
|
||||||
|
chosenSearchScenery(value: string) {
|
||||||
|
const chosenStation = this.store.stationList.find(({ name }) => name == value);
|
||||||
|
|
||||||
|
if (chosenStation) {
|
||||||
|
this.$router.push(`/scenery?station=${chosenStation.name.replace(/ /g, '_')}`);
|
||||||
|
this.chosenSearchScenery = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isVisible(value: boolean) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (value) (this.$refs['cardEl'] as HTMLDivElement).focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
// Override keyMixin function
|
||||||
|
onKeyDownFunction() {
|
||||||
|
this.isVisible = !this.isVisible;
|
||||||
},
|
},
|
||||||
|
|
||||||
handleInput(e: Event) {
|
handleInput(e: Event) {
|
||||||
const target = e.target as HTMLInputElement;
|
const target = e.target as HTMLInputElement;
|
||||||
|
|
||||||
this.$emit('changeFilterValue', {
|
this.filterStore.changeFilterValue({
|
||||||
name: target.name,
|
name: target.name,
|
||||||
value: target.value,
|
value: target.value,
|
||||||
});
|
});
|
||||||
@@ -172,7 +221,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
changeNumericFilterValue(name: string, value: number, saveToStorage = false) {
|
changeNumericFilterValue(name: string, value: number, saveToStorage = false) {
|
||||||
this.$emit('changeFilterValue', {
|
this.filterStore.changeFilterValue({
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
@@ -192,17 +241,8 @@ export default defineComponent({
|
|||||||
this.changeNumericFilterValue('onlineFromHours', this.minimumHours, true);
|
this.changeNumericFilterValue('onlineFromHours', this.minimumHours, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
invertFilters() {
|
saveFilters() {
|
||||||
this.inputs.options.forEach((option) => {
|
this.saveOptions = !this.saveOptions;
|
||||||
option.value = !option.value;
|
|
||||||
StorageManager.setBooleanValue(option.name, option.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$emit('invertFilters');
|
|
||||||
},
|
|
||||||
|
|
||||||
saveFilters(change: { value: any }) {
|
|
||||||
this.saveOptions = change.value;
|
|
||||||
|
|
||||||
if (!this.saveOptions) {
|
if (!this.saveOptions) {
|
||||||
StorageManager.unregisterStorage(this.STORAGE_KEY);
|
StorageManager.unregisterStorage(this.STORAGE_KEY);
|
||||||
@@ -211,28 +251,16 @@ export default defineComponent({
|
|||||||
|
|
||||||
StorageManager.registerStorage(this.STORAGE_KEY);
|
StorageManager.registerStorage(this.STORAGE_KEY);
|
||||||
|
|
||||||
this.inputs.options.forEach((option) => StorageManager.setBooleanValue(option.name, option.value));
|
this.filterStore.inputs.options.forEach((option) => StorageManager.setBooleanValue(option.name, !option.value));
|
||||||
|
this.filterStore.inputs.sliders.forEach((slider) => StorageManager.setNumericValue(slider.name, slider.value));
|
||||||
this.inputs.sliders.forEach((slider) => StorageManager.setNumericValue(slider.name, slider.value));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
resetFilters() {
|
resetFilters() {
|
||||||
this.inputs.options.forEach((option) => {
|
|
||||||
option.value = option.defaultValue;
|
|
||||||
StorageManager.setBooleanValue(option.name, option.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.inputs.sliders.forEach((slider) => {
|
|
||||||
slider.value = slider.defaultValue;
|
|
||||||
StorageManager.setNumericValue(slider.name, slider.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.authorsInputValue = '';
|
this.authorsInputValue = '';
|
||||||
|
|
||||||
this.minimumHours = 0;
|
this.minimumHours = 0;
|
||||||
this.changeNumericFilterValue('onlineFromHours', this.minimumHours, true);
|
this.changeNumericFilterValue('onlineFromHours', this.minimumHours, true);
|
||||||
|
this.filterStore.resetFilters();
|
||||||
this.$emit('resetFilters');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
closeCard() {
|
closeCard() {
|
||||||
@@ -264,28 +292,32 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
&_btn {
|
display: grid;
|
||||||
button {
|
grid-template-rows: 1fr auto;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
padding: 0.5em 1em;
|
&_info {
|
||||||
border-radius: 0.75em 0.75em 0 0;
|
background-color: #111;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
font-weight: bold;
|
&_controls {
|
||||||
}
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
img {
|
input {
|
||||||
width: 1.3em;
|
border-radius: 0.5em 0.5em 0 0;
|
||||||
margin-right: 0.25em;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_content {
|
&_content {
|
||||||
display: grid;
|
padding: 1em 0.5em;
|
||||||
grid-template-rows: 70px 1fr 100px 50px auto;
|
|
||||||
min-height: 0;
|
display: flex;
|
||||||
max-height: 100vh;
|
flex-direction: column;
|
||||||
|
|
||||||
|
gap: 1em;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_title {
|
&_title {
|
||||||
@@ -293,23 +325,9 @@ export default defineComponent({
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
|
|
||||||
margin: 0.5em 0;
|
|
||||||
|
|
||||||
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;
|
||||||
@@ -342,32 +360,18 @@ export default defineComponent({
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
font-size: 1.15em;
|
font-size: 1.2em;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
|
||||||
color: $accentCol;
|
span {
|
||||||
font-weight: bold;
|
min-width: 120px;
|
||||||
}
|
font-weight: bold;
|
||||||
|
|
||||||
span {
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
background: none;
|
|
||||||
padding: 0 0.45em;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
font-size: 1.35em;
|
|
||||||
|
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
color: $accentCol;
|
color: $accentCol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0.2em 0.6em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,23 +393,65 @@ export default defineComponent({
|
|||||||
input {
|
input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
border: 1px solid white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&_actions {
|
&_actions {
|
||||||
margin-top: 1em;
|
width: 100%;
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
|
.filter-option {
|
||||||
|
max-width: 50%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
margin-top: 0.5em;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 50%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
|
&[data-selected='true'] {
|
||||||
|
background-color: forestgreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card_options {
|
||||||
|
.option-section h3 {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
margin-bottom: 0.25em;
|
||||||
|
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
margin: 1em 0.25em;
|
padding: 0.15em;
|
||||||
|
color: coral;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.option {
|
.section-inputs {
|
||||||
font-size: 1.1em;
|
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;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,8 +481,13 @@ export default defineComponent({
|
|||||||
min-width: 25%;
|
min-width: 25%;
|
||||||
max-width: 120px;
|
max-width: 120px;
|
||||||
|
|
||||||
|
&:focus-visible ~ * {
|
||||||
|
color: gold;
|
||||||
|
}
|
||||||
|
|
||||||
&::-webkit-slider-thumb {
|
&::-webkit-slider-thumb {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
|
||||||
height: 20px;
|
height: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -182,30 +195,36 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="station_info" v-else>
|
<td class="station_info" v-else>
|
||||||
<img class="icon-info" :src="getImage('unknown.png')" alt="icon-unknown" :title="$t('desc.unknown')" />
|
<img class="icon-info" :src="getIcon('unknown')" alt="icon-unknown" :title="$t('desc.unknown')" />
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<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>
|
||||||
@@ -230,8 +249,10 @@ import stationInfoMixin from '../../mixins/stationInfoMixin';
|
|||||||
import styleMixin from '../../mixins/styleMixin';
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
import { DataStatus } from '../../scripts/enums/DataStatus';
|
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
import Station from '../../scripts/interfaces/Station';
|
import Station from '../../scripts/interfaces/Station';
|
||||||
|
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: {
|
||||||
@@ -239,48 +260,60 @@ export default defineComponent({
|
|||||||
type: Array as () => Station[],
|
type: Array as () => Station[],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
sorterActive: {
|
|
||||||
type: Object as () => {
|
|
||||||
index: number;
|
|
||||||
dir: number;
|
|
||||||
},
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
setFocusedStation: { type: Function, required: true },
|
|
||||||
changeSorter: { type: Function, required: true },
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
components: { Loading },
|
||||||
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: '',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
sorterActive() {
|
||||||
|
return this.stationFiltersStore.sorterActive;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
const stationFiltersStore = useStationFiltersStore();
|
||||||
|
|
||||||
const isDataLoaded = computed(() => {
|
const isDataLoaded = computed(() => {
|
||||||
return store.dataStatuses.sceneries != DataStatus.Loading;
|
return store.dataStatuses.sceneries != DataStatus.Loading;
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
isDataLoaded,
|
isDataLoaded,
|
||||||
|
stationFiltersStore,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
setScenery(name: string) {
|
setScenery(name: string) {
|
||||||
const station = this.stations.find((station) => station.name === name);
|
const station = this.stations.find((station) => station.name === name);
|
||||||
if (!station) return;
|
if (!station) return;
|
||||||
|
|
||||||
this.lastSelectedStationName = station.name;
|
this.lastSelectedStationName = station.name;
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'SceneryView',
|
name: 'SceneryView',
|
||||||
query: { station: station.name.replaceAll(' ', '_') },
|
query: { station: station.name.replaceAll(' ', '_') },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
openForumSite(e: Event, url: string | undefined) {
|
openForumSite(e: Event, url: string | undefined) {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
changeSorter(headerName: HeadIdsTypes) {
|
||||||
|
if (headerName == 'general' || headerName == 'routes') return;
|
||||||
|
|
||||||
|
this.stationFiltersStore.changeSorter(headerName);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: { Loading },
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -289,7 +322,7 @@ export default defineComponent({
|
|||||||
@import '../../styles/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
@import '../../styles/icons.scss';
|
@import '../../styles/icons.scss';
|
||||||
|
|
||||||
$rowCol: #4b4b4b;
|
$rowCol: #424242;
|
||||||
|
|
||||||
.change-anim {
|
.change-anim {
|
||||||
&-enter-active,
|
&-enter-active,
|
||||||
@@ -328,17 +361,23 @@ table {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thead tr {
|
thead tr {
|
||||||
background-color: $primaryCol;
|
background-color: $bgCol;
|
||||||
}
|
}
|
||||||
|
|
||||||
thead th {
|
thead th {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
min-width: 75px;
|
&.header-text {
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
padding: 0.5em;
|
&.header-image {
|
||||||
background-color: $primaryCol;
|
min-width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding: 0.5em 0.25em;
|
||||||
|
background-color: $bgCol;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -2,17 +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>
|
|
||||||
</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">
|
||||||
@@ -35,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"
|
||||||
@@ -89,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';
|
||||||
|
|
||||||
@@ -105,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;
|
||||||
@@ -144,10 +150,18 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.timetable-id {
|
.timetable-id {
|
||||||
margin-right: 0.3em;
|
|
||||||
color: #d2d2d2;
|
color: #d2d2d2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.warning-timeout {
|
||||||
|
background-color: #be3728;
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
padding: 0 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
.timetable_stops {
|
.timetable_stops {
|
||||||
font-size: 0.75em;
|
font-size: 0.75em;
|
||||||
}
|
}
|
||||||
@@ -156,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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +196,8 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.timetable_warnings {
|
.timetable_warnings {
|
||||||
color: black;
|
display: flex;
|
||||||
|
gap: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timetable_progress {
|
.timetable_progress {
|
||||||
@@ -253,10 +259,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
.train-stats {
|
.train-stats {
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
|
|
||||||
img {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.train_general {
|
.train_general {
|
||||||
|
|||||||
@@ -1,267 +1,226 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="train-options">
|
<div class="filters-options" @keydown.esc="showOptions = false">
|
||||||
<div class="options_wrapper">
|
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
||||||
<div class="options_content">
|
|
||||||
<div class="content_select">
|
|
||||||
<select-box
|
|
||||||
:itemList="translatedSorterOptions"
|
|
||||||
:defaultItemIndex="0"
|
|
||||||
@selected="changeSorter"
|
|
||||||
:prefix="$t('trains.sorter-prefix')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content_search">
|
<button class="filter-button btn--filled btn--image" @click="toggleShowOptions" ref="button">
|
||||||
<div class="search-box">
|
<img :src="getIcon('filter2')" alt="Open filters" />
|
||||||
<input class="search-input" v-model="searchedTrain" :placeholder="$t('trains.search-train')" />
|
{{ $t('options.filters') }} [F]
|
||||||
|
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<img class="search-exit" :src="getIcon('exit')" alt="exit-icon" @click="() => (searchedTrain = '')" />
|
<transition name="options-anim">
|
||||||
|
<div class="options_wrapper" v-if="showOptions">
|
||||||
|
<div class="options_content">
|
||||||
|
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
||||||
|
<div class="search_content">
|
||||||
|
<div class="search-box">
|
||||||
|
<input
|
||||||
|
class="search-input"
|
||||||
|
ref="initFocusedElement"
|
||||||
|
@focus="preventKeyDown = true"
|
||||||
|
@blur="preventKeyDown = false"
|
||||||
|
:placeholder="$t(`options.search-train`)"
|
||||||
|
v-model="searchedTrain"
|
||||||
|
/>
|
||||||
|
<button class="search-exit">
|
||||||
|
<img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear('train')" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search-box">
|
||||||
|
<input
|
||||||
|
class="search-input"
|
||||||
|
@focus="preventKeyDown = true"
|
||||||
|
@blur="preventKeyDown = false"
|
||||||
|
:placeholder="$t(`options.search-driver`)"
|
||||||
|
v-model="searchedDriver"
|
||||||
|
/>
|
||||||
|
<button class="search-exit">
|
||||||
|
<img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear('driver')" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="search-box">
|
<h1 class="option-title">{{ $t('options.sort-title') }}</h1>
|
||||||
<input class="search-input" v-model="searchedDriver" :placeholder="$t('trains.search-driver')" />
|
<div class="options_sorters">
|
||||||
|
<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>
|
||||||
|
|
||||||
<img class="search-exit" :src="getIcon('exit')" alt="exit-icon" @click="() => (searchedDriver = '')" />
|
<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
|
||||||
|
class="btn--option"
|
||||||
|
v-for="filter in trainFilterList.filter((f) => f.section == section)"
|
||||||
|
:data-inactive="!filter.isActive"
|
||||||
|
@click="onFilterChange(filter)"
|
||||||
|
>
|
||||||
|
{{ $t(`options.filter-${filter.id}`) }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-actions">
|
||||||
|
<div></div>
|
||||||
|
<button class="btn--action" @click="resetAllFilters">{{ $t('options.filter-reset') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</transition>
|
||||||
|
|
||||||
<div class="filters">
|
|
||||||
<span
|
|
||||||
:class="{ active: filter.isActive }"
|
|
||||||
class="filter"
|
|
||||||
v-for="filter in filterList"
|
|
||||||
:key="filter.id"
|
|
||||||
tabindex="0"
|
|
||||||
@contextmenu="
|
|
||||||
(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
"
|
|
||||||
@click.left="toggleFilter(filter)"
|
|
||||||
@keydown.enter="toggleFilter(filter)"
|
|
||||||
@click.right="setFilterOnly(filter)"
|
|
||||||
@keydown.space="setFilterOnly(filter)"
|
|
||||||
>
|
|
||||||
{{ $t(`trains.filter-${filter.id}`) }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span class="filter reset-btn" @click="resetFilters" tabindex="0">
|
|
||||||
{{ $t('trains.filter-reset') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, inject, TrainFilter } from 'vue';
|
import { defineComponent, inject, PropType } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
import keyMixin from '../../mixins/keyMixin';
|
||||||
|
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 },
|
components: { SelectBox, ActionButton },
|
||||||
emits: ['changeSearchedTrain', 'changeSearchedDriver', 'changeSorter'],
|
mixins: [imageMixin, keyMixin],
|
||||||
mixins: [imageMixin],
|
|
||||||
|
|
||||||
setup() {
|
props: {
|
||||||
const { t } = useI18n();
|
sorterOptionIds: {
|
||||||
|
type: Array as PropType<Array<string>>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
const sorterOptions = [
|
currentOptionsActive: {
|
||||||
{
|
type: Boolean,
|
||||||
id: 'distance',
|
default: false,
|
||||||
value: 'kilometraż',
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'progress',
|
|
||||||
value: 'przebyta trasa',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'delay',
|
|
||||||
value: 'opóźnienie',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'mass',
|
|
||||||
value: 'masa',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'speed',
|
|
||||||
value: 'prędkość',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'length',
|
|
||||||
value: 'długość',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let filterList = inject('filterList') as TrainFilter[];
|
|
||||||
|
|
||||||
const translatedSorterOptions = computed(() =>
|
|
||||||
sorterOptions.map(({ id }) => ({
|
|
||||||
id,
|
|
||||||
value: t(`trains.option-${id}`),
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
|
data() {
|
||||||
return {
|
return {
|
||||||
translatedSorterOptions,
|
showOptions: false,
|
||||||
searchedTrain: inject('searchedTrain') as string,
|
lastSelectedFilter: null as TrainFilter | null,
|
||||||
searchedDriver: inject('searchedDriver') as string,
|
TrainFilterSection,
|
||||||
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
|
||||||
filterList,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
searchedTrain: inject('searchedTrain') as string,
|
||||||
|
searchedDriver: inject('searchedDriver') as string,
|
||||||
|
|
||||||
|
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
||||||
|
trainFilterList: inject('filterList') as TrainFilter[],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
translatedSorterOptions() {
|
||||||
|
return this.$props.sorterOptionIds.map((id) => ({
|
||||||
|
id,
|
||||||
|
value: this.$t(`options.sort-${id}`),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
changeSorter(item: { id: string | number; value: string }) {
|
// Override keyMixin function
|
||||||
|
onKeyDownFunction() {
|
||||||
|
this.toggleShowOptions();
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleShowOptions() {
|
||||||
|
this.showOptions = !this.showOptions;
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (this.showOptions) (this.$refs['button'] as HTMLButtonElement)?.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
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;
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleFilter(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;
|
||||||
},
|
},
|
||||||
|
|
||||||
setFilterOnly(filter: TrainFilter) {
|
clearAllFilters() {
|
||||||
this.filterList.forEach((f) => (f.isActive = f.id == filter.id));
|
this.trainFilterList.forEach((filter) => {
|
||||||
|
filter.isActive = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
resetFilters() {
|
resetAllFilters() {
|
||||||
this.filterList.forEach((f) => (f.isActive = true));
|
this.trainFilterList.forEach((filter) => {
|
||||||
this.searchedDriver = "";
|
filter.isActive = true;
|
||||||
this.searchedTrain = "";
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onInputClear(id: 'driver' | 'train') {
|
||||||
|
if (id == 'driver') this.searchedDriver = '';
|
||||||
|
if (id == 'train') this.searchedTrain = '';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/responsive';
|
@import '../../styles/filters_options.scss';
|
||||||
|
|
||||||
.train-options {
|
.search_content > div {
|
||||||
@include smallScreen() {
|
margin: 0.5em auto;
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.options {
|
.search_content > button {
|
||||||
&_wrapper {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_content {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
.content_search,
|
|
||||||
.content_select {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
padding: 0.25em 0.25em 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
|
||||||
&-box {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
background: #333;
|
|
||||||
border-radius: 0.5em;
|
|
||||||
min-width: 200px;
|
|
||||||
margin-right: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-input {
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
min-width: 100%;
|
|
||||||
padding: 0.35em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-exit {
|
|
||||||
position: absolute;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
top: 50%;
|
|
||||||
right: 10px;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
|
|
||||||
width: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.filters {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
justify-content: center;
|
||||||
|
margin: 0 auto;
|
||||||
margin-top: 0.5em;
|
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter {
|
.options_sorters {
|
||||||
background: #333;
|
display: flex;
|
||||||
padding: 0.2em 0.25em;
|
grid-template-columns: repeat(3, 1fr);
|
||||||
margin: 0.25em 0.25em 0 0;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
color: gray;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: var(--clr-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.reset-btn {
|
|
||||||
color: salmon;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@include smallScreen() {
|
.options_filters > div {
|
||||||
.journal-options {
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
color: springgreen;
|
||||||
.options {
|
font-weight: bold;
|
||||||
&_wrapper {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_content {
|
&[data-inactive='true'] {
|
||||||
padding: 0 1em;
|
color: #aaa;
|
||||||
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.content_select {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content_search {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.search {
|
.filter-actions {
|
||||||
&-box,
|
display: flex;
|
||||||
&-button {
|
gap: 0.5em;
|
||||||
margin: 0.5em 0 0 0;
|
width: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
&-box {
|
margin-top: 1em;
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-button {
|
> * {
|
||||||
width: 80%;
|
width: 100%;
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section class="filter-card" v-click-outside="closeCard">
|
|
||||||
<div class="card_btn">
|
|
||||||
<action-button @click="toggleCard">
|
|
||||||
<img class="button_icon" :src="getIcon('filter2')" alt="icon-filter" />
|
|
||||||
<p>{{ $t('options.filters') }}</p>
|
|
||||||
</action-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<transition name="card-anim">
|
|
||||||
<div class="card_content card" v-if="isVisible">
|
|
||||||
<div class="card_exit" @click="closeCard"></div>
|
|
||||||
|
|
||||||
<div class="options_wrapper">
|
|
||||||
<div class="options_content">
|
|
||||||
<div class="content_select">
|
|
||||||
<select-box
|
|
||||||
:itemList="translatedSorterOptions"
|
|
||||||
:defaultItemIndex="0"
|
|
||||||
@selected="changeSorter"
|
|
||||||
:prefix="$t('trains.sorter-prefix')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content_search">
|
|
||||||
<div class="search-box">
|
|
||||||
<input class="search-input" v-model="searchedTrain" :placeholder="$t('trains.search-train')" />
|
|
||||||
|
|
||||||
<img class="search-exit" :src="getIcon('exit')" alt="exit-icon" @click="() => (searchedTrain = '')" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="search-box">
|
|
||||||
<input class="search-input" v-model="searchedDriver" :placeholder="$t('trains.search-driver')" />
|
|
||||||
|
|
||||||
<img class="search-exit" :src="getIcon('exit')" alt="exit-icon" @click="() => (searchedDriver = '')" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section class="card_actions flex">
|
|
||||||
<action-button class="outlined">
|
|
||||||
{{ $t('filters.reset') }}
|
|
||||||
</action-button>
|
|
||||||
<action-button class="outlined" @click="closeCard">{{ $t('filters.close') }}</action-button>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import inputData from "../../data/options.json";
|
|
||||||
|
|
||||||
import { TrainFilter, computed, defineComponent, inject } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import SelectBox from '../Global/SelectBox.vue';
|
|
||||||
import ActionButton from '../Global/ActionButton.vue';
|
|
||||||
import { sorterOptions } from '../../data/trainOptions';
|
|
||||||
import imageMixin from "../../mixins/imageMixin";
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { ActionButton, SelectBox },
|
|
||||||
emits: ['changeFilterValue', 'invertFilters', 'resetFilters'],
|
|
||||||
mixins: [imageMixin],
|
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
inputs: { ...inputData },
|
|
||||||
}),
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const isVisible = inject('isTrainOptionsCardVisible');
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
let filterList = inject('filterList') as TrainFilter[];
|
|
||||||
|
|
||||||
const translatedSorterOptions = computed(() =>
|
|
||||||
sorterOptions.map(({ id }) => ({
|
|
||||||
id,
|
|
||||||
value: t(`trains.option-${id}`),
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
translatedSorterOptions,
|
|
||||||
searchedTrain: inject('searchedTrain') as string,
|
|
||||||
searchedDriver: inject('searchedDriver') as string,
|
|
||||||
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
|
||||||
filterList,
|
|
||||||
isVisible,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
closeCard() {
|
|
||||||
this.isVisible = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleCard() {
|
|
||||||
this.isVisible = !this.isVisible;
|
|
||||||
},
|
|
||||||
|
|
||||||
changeSorter(item: { id: string | number; value: string }) {
|
|
||||||
this.sorterActive.id = item.id;
|
|
||||||
this.sorterActive.dir = -1;
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleFilter(filter: TrainFilter) {
|
|
||||||
filter.isActive = !filter.isActive;
|
|
||||||
},
|
|
||||||
|
|
||||||
setFilterOnly(filter: TrainFilter) {
|
|
||||||
this.filterList.forEach((f) => (f.isActive = f.id == filter.id));
|
|
||||||
},
|
|
||||||
|
|
||||||
resetFilters() {
|
|
||||||
this.filterList.forEach((f) => (f.isActive = true));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/responsive';
|
|
||||||
@import '../../styles/card';
|
|
||||||
|
|
||||||
|
|
||||||
.card {
|
|
||||||
section {
|
|
||||||
margin: 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&_title {
|
|
||||||
font-size: 2em;
|
|
||||||
font-weight: 700;
|
|
||||||
color: $accentCol;
|
|
||||||
|
|
||||||
margin: 0.5em 0;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -60,7 +60,9 @@
|
|||||||
<b>{{ stop.stopNameRAW }} </b>: <span v-html="stop.comments"></span>
|
<b>{{ stop.stopNameRAW }} </b>: <span v-html="stop.comments"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span v-if="stop.departureLine == train.timetableData!.followingStops[i + 1].arrivalLine && !/sbl/gi.test(stop.departureLine!)">
|
<span
|
||||||
|
v-if="stop.departureLine == train.timetableData!.followingStops[i + 1].arrivalLine && !/sbl/gi.test(stop.departureLine!)"
|
||||||
|
>
|
||||||
{{ stop.departureLine }}
|
{{ stop.departureLine }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -87,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({
|
||||||
@@ -104,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
|
||||||
@@ -175,10 +180,6 @@ $stopNameClr: #22a8d1;
|
|||||||
|
|
||||||
.train-schedule {
|
.train-schedule {
|
||||||
padding: 0 0.25em;
|
padding: 0 0.25em;
|
||||||
|
|
||||||
@include smallScreen() {
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.train-stock {
|
.train-stock {
|
||||||
@@ -198,6 +199,10 @@ ul.stock-list {
|
|||||||
color: #aaa;
|
color: #aaa;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 60px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule-wrapper {
|
.schedule-wrapper {
|
||||||
@@ -421,3 +426,4 @@ ul.stop_list > li.stop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +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>
|
||||||
|
|
||||||
<ul class="train-list">
|
<transition-group name="list-anim" tag="ul" class="train-list" v-else>
|
||||||
<li
|
<li
|
||||||
class="train-row"
|
class="train-row"
|
||||||
v-for="train in currentTrains"
|
v-for="train in currentTrains"
|
||||||
@@ -18,46 +22,37 @@
|
|||||||
>
|
>
|
||||||
<TrainInfo :train="train" />
|
<TrainInfo :train="train" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, inject, Ref, computed } from 'vue';
|
import { computed, defineComponent, inject, PropType, Ref } from 'vue';
|
||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
import returnBtnMixin from '../../mixins/returnBtnMixin';
|
import returnBtnMixin from '../../mixins/returnBtnMixin';
|
||||||
import Train from '../../scripts/interfaces/Train';
|
import Train from '../../scripts/interfaces/Train';
|
||||||
import { useStore } from '../../store/store';
|
import { useStore } from '../../store/store';
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
import TrainModal from '../Global/TrainModal.vue';
|
|
||||||
import TrainInfo from './TrainInfo.vue';
|
import TrainInfo from './TrainInfo.vue';
|
||||||
import TrainSchedule from './TrainSchedule.vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: { Loading, TrainInfo },
|
||||||
TrainSchedule,
|
|
||||||
TrainInfo,
|
|
||||||
Loading,
|
|
||||||
TrainModal,
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [returnBtnMixin, modalTrainMixin],
|
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
trains: {
|
trains: {
|
||||||
type: Array as () => Train[],
|
type: Array as PropType<Train[]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mixins: [returnBtnMixin, modalTrainMixin],
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
const searchedTrain = inject('searchedTrain') as Ref<string>;
|
const searchedTrain = inject('searchedTrain') as Ref<string>;
|
||||||
const searchedDriver = inject('searchedDriver') as Ref<string>;
|
const searchedDriver = inject('searchedDriver') as Ref<string>;
|
||||||
|
|
||||||
const currentTrains = computed(() => {
|
const currentTrains = computed(() => {
|
||||||
return props.trains;
|
return props.trains;
|
||||||
});
|
});
|
||||||
@@ -67,58 +62,29 @@ export default defineComponent({
|
|||||||
searchedDriver,
|
searchedDriver,
|
||||||
currentTrains,
|
currentTrains,
|
||||||
store,
|
store,
|
||||||
|
sorterActive: inject('sorterActive') as {
|
||||||
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
id: string | number;
|
||||||
distanceLimitExceeded: computed(
|
dir: number;
|
||||||
() => props.trains.findIndex(({ timetableData }) => timetableData && timetableData.routeDistance > 200) != -1
|
},
|
||||||
),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
activated() {
|
activated() {
|
||||||
const query = this.$route.query;
|
const query = this.$route.query;
|
||||||
|
|
||||||
if (query.trainNo && query.driverName) {
|
if (query.trainNo && query.driverName) {
|
||||||
this.searchedDriver = query.driverName.toString();
|
this.searchedDriver = query.driverName.toString();
|
||||||
this.searchedTrain = query.trainNo.toString();
|
this.searchedTrain = query.trainNo.toString();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.selectModalTrain(query.driverName + <string>query.trainNo);
|
this.selectModalTrain(query.driverName! + query.trainNo!.toString());
|
||||||
}, 20);
|
}, 20);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
|
||||||
enter(el: HTMLElement) {
|
|
||||||
const maxHeight = getComputedStyle(el).height;
|
|
||||||
|
|
||||||
el.style.height = '0px';
|
|
||||||
|
|
||||||
getComputedStyle(el);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
el.style.height = maxHeight;
|
|
||||||
}, 10);
|
|
||||||
},
|
|
||||||
|
|
||||||
afterEnter(el: HTMLElement) {
|
|
||||||
el.style.height = 'auto';
|
|
||||||
},
|
|
||||||
|
|
||||||
leave(el: HTMLElement) {
|
|
||||||
el.style.height = getComputedStyle(el).height;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
el.style.height = '0px';
|
|
||||||
}, 10);
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<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,
|
||||||
@@ -139,11 +105,10 @@ export default defineComponent({
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
padding: 1em 0;
|
padding: 1em 0;
|
||||||
margin: 1em 0;
|
|
||||||
|
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
|
|
||||||
background: #333;
|
background: #1a1a1a;
|
||||||
}
|
}
|
||||||
|
|
||||||
img.train-image {
|
img.train-image {
|
||||||
@@ -156,11 +121,31 @@ img.train-image {
|
|||||||
background: var(--clr-warning);
|
background: var(--clr-warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeouts-warning {
|
||||||
|
background-color: #333;
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.05em;
|
||||||
|
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-timeout {
|
||||||
|
background-color: #be3728;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
width: 1.25em;
|
||||||
|
height: 1.25em;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.train {
|
.train {
|
||||||
&-list {
|
&-list {
|
||||||
overflow: auto;
|
position: relative;
|
||||||
|
|
||||||
margin-top: 1em;
|
|
||||||
|
|
||||||
@include smallScreen() {
|
@include smallScreen() {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { JournalFilterSection, JournalFilterType } from '../../scripts/enums/JournalFilterType';
|
||||||
|
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
|
||||||
|
|
||||||
|
export const journalTimetableFilters: JournalFilter[] = [
|
||||||
|
{
|
||||||
|
id: JournalFilterType.ALL,
|
||||||
|
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.ACTIVE,
|
||||||
|
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.FULFILLED,
|
||||||
|
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.ABANDONED,
|
||||||
|
filterSection: JournalFilterSection.TIMETABLE_STATUS,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.TWR_SKR,
|
||||||
|
filterSection: JournalFilterSection.TWRSKR,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.TWR,
|
||||||
|
filterSection: JournalFilterSection.TWRSKR,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: JournalFilterType.SKR,
|
||||||
|
filterSection: JournalFilterSection.TWRSKR,
|
||||||
|
isActive: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { TrainFilterSection, TrainFilterType } from '../../scripts/enums/TrainFilterType';
|
||||||
|
import { TrainFilter } from '../../scripts/interfaces/Trains/TrainFilter';
|
||||||
|
|
||||||
|
export const trainFilters: TrainFilter[] = [
|
||||||
|
{
|
||||||
|
id: TrainFilterType.twr,
|
||||||
|
section: TrainFilterSection.TRAIN_TYPE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TrainFilterType.skr,
|
||||||
|
section: TrainFilterSection.TRAIN_TYPE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TrainFilterType.common,
|
||||||
|
section: TrainFilterSection.TRAIN_TYPE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: TrainFilterType.passenger,
|
||||||
|
section: TrainFilterSection.TIMETABLE_TYPE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TrainFilterType.freight,
|
||||||
|
section: TrainFilterSection.TIMETABLE_TYPE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TrainFilterType.other,
|
||||||
|
section: TrainFilterSection.TIMETABLE_TYPE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: TrainFilterType.withComments,
|
||||||
|
section: TrainFilterSection.COMMENTS,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TrainFilterType.noComments,
|
||||||
|
section: TrainFilterSection.COMMENTS,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
id: TrainFilterType.withTimetable,
|
||||||
|
section: TrainFilterSection.TIMETABLE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: TrainFilterType.noTimetable,
|
||||||
|
section: TrainFilterSection.TIMETABLE,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const sorterOptions = [
|
||||||
|
{
|
||||||
|
id: 'distance',
|
||||||
|
value: 'kilometraż',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'id',
|
||||||
|
value: 'id rozkładu',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'progress',
|
||||||
|
value: 'przebyta trasa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'delay',
|
||||||
|
value: 'opóźnienie',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'mass',
|
||||||
|
value: 'masa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'speed',
|
||||||
|
value: 'prędkość',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'length',
|
||||||
|
value: 'długość',
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { JournalFilter } from "vue";
|
|
||||||
import { JournalFilterType } from "../scripts/enums/JournalFilterType";
|
|
||||||
|
|
||||||
export const journalTimetableFilters: JournalFilter[] = [
|
|
||||||
{
|
|
||||||
id: JournalFilterType.all,
|
|
||||||
filterSection: "timetable-status",
|
|
||||||
isActive: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: JournalFilterType.active,
|
|
||||||
filterSection: "timetable-status",
|
|
||||||
isActive: false
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: JournalFilterType.fulfilled,
|
|
||||||
filterSection: "timetable-status",
|
|
||||||
isActive: false
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: JournalFilterType.abandoned,
|
|
||||||
filterSection: "timetable-status",
|
|
||||||
isActive: false
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export const journalDispatcherFilters: JournalFilter[] = []
|
|
||||||
@@ -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,20 +215,10 @@
|
|||||||
{
|
{
|
||||||
"id": "unavailableStatus",
|
"id": "unavailableStatus",
|
||||||
"name": "unavailableStatus",
|
"name": "unavailableStatus",
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "status",
|
"section": "status",
|
||||||
"value": true,
|
"value": true,
|
||||||
"defaultValue": true
|
"defaultValue": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "troll",
|
|
||||||
"name": "troll",
|
|
||||||
"iconName": "",
|
|
||||||
|
|
||||||
"section": "troll",
|
|
||||||
"value": true,
|
|
||||||
"defaultValue": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sliders": [
|
"sliders": [
|
||||||
@@ -263,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
|
||||||
@@ -271,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,60 +0,0 @@
|
|||||||
import { TrainFilter } from "vue";
|
|
||||||
import { TrainFilterType } from "../scripts/enums/TrainFilterType";
|
|
||||||
|
|
||||||
export const trainFilters: TrainFilter[] = [
|
|
||||||
{
|
|
||||||
id: TrainFilterType.twr,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: TrainFilterType.skr,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: TrainFilterType.passenger,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: TrainFilterType.freight,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: TrainFilterType.other,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: TrainFilterType.comments,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: TrainFilterType.noTimetable,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const sorterOptions = [
|
|
||||||
{
|
|
||||||
id: 'distance',
|
|
||||||
value: 'kilometraż',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'progress',
|
|
||||||
value: 'przebyta trasa',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'delay',
|
|
||||||
value: 'opóźnienie',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'mass',
|
|
||||||
value: 'masa',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'speed',
|
|
||||||
value: 'prędkość',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'length',
|
|
||||||
value: 'długość',
|
|
||||||
}
|
|
||||||
];
|
|
||||||
@@ -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: ",
|
||||||
@@ -72,17 +84,86 @@
|
|||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"filters": "FILTERS",
|
"filters": "FILTERS",
|
||||||
"donate": "DONATE"
|
"donate": "DONATE",
|
||||||
|
|
||||||
|
"search-button": "Search",
|
||||||
|
"reset-button": "Reset",
|
||||||
|
|
||||||
|
"sort-title": "SORT BY:",
|
||||||
|
"filter-title": "FILTER BY:",
|
||||||
|
"search-title": "SEARCH:",
|
||||||
|
|
||||||
|
"search-train-no": "Train no. / #",
|
||||||
|
"search-train": "Train no.",
|
||||||
|
"search-driver": "Driver name",
|
||||||
|
"search-dispatcher": "Dispatcher name",
|
||||||
|
"search-station": "Scenery name",
|
||||||
|
"search-author": "Timetable author name",
|
||||||
|
"search-issuedFrom": "Origin scenery name",
|
||||||
|
"search-timetables-date": "Timetable date (UTC+2 / CEST)",
|
||||||
|
"search-dispatchers-date": "Service date (UTC+2 / CEST)",
|
||||||
|
"search-date": "Date (UTC+2 / CEST)",
|
||||||
|
|
||||||
|
"sort-mass": "mass",
|
||||||
|
"sort-speed": "speed",
|
||||||
|
"sort-length": "length",
|
||||||
|
"sort-routeDistance": "route distance",
|
||||||
|
"sort-timetable": "train no.",
|
||||||
|
"sort-progress": "route progress",
|
||||||
|
"sort-delay": "current delay",
|
||||||
|
"sort-id": "timetable id",
|
||||||
|
|
||||||
|
"sort-allStopsCount": "total stops",
|
||||||
|
"sort-beginDate": "date",
|
||||||
|
"sort-timetableId": "timetable ID",
|
||||||
|
"sort-timestampFrom": "date",
|
||||||
|
"sort-duration": "duration",
|
||||||
|
|
||||||
|
"filter-noComments": "NO COMMENTS",
|
||||||
|
"filter-withComments": "COMMENTS",
|
||||||
|
"filter-twr": "HIGH RISK CARGO",
|
||||||
|
"filter-skr": "EXCEEDED GAUGE",
|
||||||
|
"filter-twr-skr": "ALL TYPES",
|
||||||
|
"filter-common": "NO WARNINGS",
|
||||||
|
"filter-passenger": "PASSENGER",
|
||||||
|
"filter-freight": "FREIGHT",
|
||||||
|
"filter-other": "OTHER",
|
||||||
|
"filter-noTimetable": "NO TIMETABLE",
|
||||||
|
"filter-withTimetable": "TIMETABLE",
|
||||||
|
|
||||||
|
"filter-reset": "RESET FILTERS",
|
||||||
|
"filter-clear": "CLEAR FILTERS",
|
||||||
|
|
||||||
|
"filter-section-timetable-status": "TIMETABLE STATUS",
|
||||||
|
"filter-section-twrskr": "WARNINGS",
|
||||||
|
|
||||||
|
"filter-all": "ALL ENTRIES",
|
||||||
|
"filter-abandoned": "ABANDONED",
|
||||||
|
"filter-fulfilled": "FULFILLED",
|
||||||
|
"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",
|
||||||
@@ -90,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",
|
||||||
@@ -116,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"
|
||||||
},
|
},
|
||||||
@@ -128,10 +219,13 @@
|
|||||||
"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",
|
||||||
"no-stations": "No stations to show here!"
|
"timetableConfirmed": "Confirmed timetables",
|
||||||
|
"timetableUnconfirmed": "Unconfirmed timetables",
|
||||||
|
"no-stations": "No stations to show here!",
|
||||||
|
"scenery-search": "Search for scenery..."
|
||||||
},
|
},
|
||||||
"trains": {
|
"trains": {
|
||||||
"no-trains": "No trains to show here!",
|
"no-trains": "No trains to show here!",
|
||||||
@@ -150,28 +244,6 @@
|
|||||||
"current-signal": "at signal",
|
"current-signal": "at signal",
|
||||||
"current-track": "on track",
|
"current-track": "on track",
|
||||||
|
|
||||||
"option-mass": "mass",
|
|
||||||
"option-speed": "speed",
|
|
||||||
"option-length": "length",
|
|
||||||
"option-distance": "distance",
|
|
||||||
"option-timetable": "train no.",
|
|
||||||
"option-progress": "route progress",
|
|
||||||
"option-delay": "current delay",
|
|
||||||
"option-comments": "comments",
|
|
||||||
|
|
||||||
"filter-comments": "comments",
|
|
||||||
"filter-twr": "TWR",
|
|
||||||
"filter-skr": "SKR",
|
|
||||||
"filter-passenger": "passenger",
|
|
||||||
"filter-freight": "freight",
|
|
||||||
"filter-other": "other",
|
|
||||||
"filter-noTimetable": "no timetable",
|
|
||||||
"filter-reset": "X RESET",
|
|
||||||
|
|
||||||
"sorter-prefix": "Sort: ",
|
|
||||||
"search-train": "Train no.",
|
|
||||||
"search-driver": "Driver name",
|
|
||||||
|
|
||||||
"delayed": "Delayed: ",
|
"delayed": "Delayed: ",
|
||||||
"preponed": "Ahead of schedule: ",
|
"preponed": "Ahead of schedule: ",
|
||||||
"on-time": "On time",
|
"on-time": "On time",
|
||||||
@@ -195,36 +267,18 @@
|
|||||||
"last-seen-min": "since one minute",
|
"last-seen-min": "since one minute",
|
||||||
"last-seen-ago": "since {minutes} minutes",
|
"last-seen-ago": "since {minutes} minutes",
|
||||||
|
|
||||||
"scenery-offline": "Offline ride"
|
"scenery-offline": "Offline ride",
|
||||||
|
"timeout": "An error occured while trying to refresh SWDR timetable data!"
|
||||||
},
|
},
|
||||||
"journal": {
|
"journal": {
|
||||||
"title": "DISPATCHER HISTORY",
|
"title": "DISPATCHER HISTORY",
|
||||||
"loading": "Loading dispatcher history data...",
|
"loading": "Loading dispatcher history data...",
|
||||||
"no-history": "No dispatcher history found!",
|
"no-history": "No dispatcher history found!",
|
||||||
|
"data-refreshed-at": "Data refreshed at",
|
||||||
|
|
||||||
"section-timetables": "TIMETABLES",
|
"section-timetables": "TIMETABLES",
|
||||||
"section-dispatchers": "DISPATCHERS",
|
"section-dispatchers": "DISPATCHERS",
|
||||||
|
|
||||||
"search": "Search",
|
|
||||||
"search-train": "Train no. / #",
|
|
||||||
"search-driver": "Driver name",
|
|
||||||
"search-dispatcher": "Dispatcher name",
|
|
||||||
"search-station": "Scenery name",
|
|
||||||
|
|
||||||
"sort-prefix": "Sort: ",
|
|
||||||
|
|
||||||
"option-distance": "distance",
|
|
||||||
"option-total-stops": "total stops",
|
|
||||||
"option-beginDate": "date",
|
|
||||||
"option-timetableId": "timetable ID",
|
|
||||||
"option-timestampFrom": "date",
|
|
||||||
"option-duration": "duration",
|
|
||||||
|
|
||||||
"filter-all": "ALL ENTRIES",
|
|
||||||
"filter-abandoned": "ABANDONED",
|
|
||||||
"filter-fulfilled": "FULFILLED",
|
|
||||||
"filter-active": "ACTIVE",
|
|
||||||
|
|
||||||
"no-further-data": "No further data for current parameters",
|
"no-further-data": "No further data for current parameters",
|
||||||
"loading-further-data": "Loading...",
|
"loading-further-data": "Loading...",
|
||||||
|
|
||||||
@@ -238,8 +292,50 @@
|
|||||||
|
|
||||||
"online-since": "ONLINE SINCE",
|
"online-since": "ONLINE SINCE",
|
||||||
"duty-lasted": "The duty lasted",
|
"duty-lasted": "The duty lasted",
|
||||||
"minutes": "{minutes} mins",
|
|
||||||
"hours": "{hours}h {minutes} mins"
|
"hours": "{value} hour | {value} hours",
|
||||||
|
"minutes": "{value} min | {value} mins",
|
||||||
|
"seconds": "{value} s",
|
||||||
|
|
||||||
|
"stock-info": "EXTRA INFO",
|
||||||
|
"stock-length": "Length",
|
||||||
|
"stock-mass": "Mass",
|
||||||
|
"stock-max-speed": "Max. speed",
|
||||||
|
|
||||||
|
"load-data": "Load further data...",
|
||||||
|
|
||||||
|
"last-seen-at": "Last seen at",
|
||||||
|
"currently-at": "Currently at",
|
||||||
|
|
||||||
|
"stats-title": "DRIVING STATISTICS OF",
|
||||||
|
|
||||||
|
"stats-timetables": "TIMETABLES",
|
||||||
|
"stats-longest-timetable": "LONGEST TIMETABLE",
|
||||||
|
"stats-avg-timetable": "AVERAGE TIMETABLE LENGTH",
|
||||||
|
"stats-distance": "DISTANCE",
|
||||||
|
"stats-stations": "STATIONS",
|
||||||
|
|
||||||
|
"timetable-stats-title": "Daily stats on {date}",
|
||||||
|
"timetable-stats-total": "Issued timetables: {count} (total distance: {distance})",
|
||||||
|
"timetable-stats-longest": "The longest timetable: #{id} (made by {author} for {driver}, distance: {distance})",
|
||||||
|
"timetable-stats-most-active-dr": "The most active dispatcher: {dispatcher} (created {count})",
|
||||||
|
"timetable-stats-most-active-dr-many": "The most active dispatchers: {dispatchers} (created {count} each)",
|
||||||
|
"timetable-stats-most-active-driver": "The most active driver: {driver} (total driven distance: {distance})",
|
||||||
|
"timetable-stats-longest-duties": "The longest service: {dispatcher} at {station} (duration: {duration})",
|
||||||
|
|
||||||
|
"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! :/",
|
||||||
|
|
||||||
|
"timetable-location-signal": "signal:",
|
||||||
|
"timetable-location-route": "route:"
|
||||||
},
|
},
|
||||||
"scenery": {
|
"scenery": {
|
||||||
"users": "PLAYERS ONLINE",
|
"users": "PLAYERS ONLINE",
|
||||||
@@ -254,22 +350,41 @@
|
|||||||
"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",
|
||||||
|
|
||||||
|
"dispatchers-history-hash": "Hash",
|
||||||
|
"dispatchers-history-dispatcher": "Dispatcher",
|
||||||
|
"dispatchers-history-level": "Level",
|
||||||
|
"dispatchers-history-rate": "Rate",
|
||||||
|
"dispatchers-history-date": "Service 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)",
|
||||||
|
|
||||||
|
"bottom-info": "Show full history in the Journal tab"
|
||||||
},
|
},
|
||||||
"availability": {
|
"availability": {
|
||||||
"title": "Availability",
|
"title": "Availability",
|
||||||
@@ -281,14 +396,22 @@
|
|||||||
},
|
},
|
||||||
"timetables": {
|
"timetables": {
|
||||||
"timetable-only": "Switch to timetable-only view",
|
"timetable-only": "Switch to timetable-only view",
|
||||||
"online": "At station",
|
"end": "Timetable terminates here",
|
||||||
"departed": "Dispatched to:",
|
"terminated": "Timetable terminated",
|
||||||
"departed-away": "Departed to:",
|
|
||||||
"arriving": "Arriving from:",
|
|
||||||
"stopped": "Stopped",
|
|
||||||
"terminated": "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!",
|
||||||
@@ -74,9 +84,79 @@
|
|||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"filters": "FILTRY",
|
"filters": "FILTRY",
|
||||||
"donate": "WESPRZYJ"
|
"donate": "WESPRZYJ",
|
||||||
|
|
||||||
|
"search-button": "Szukaj",
|
||||||
|
"reset-button": "Zresetuj",
|
||||||
|
|
||||||
|
"sort-title": "SORTUJ WG:",
|
||||||
|
"filter-title": "FILTRUJ WG:",
|
||||||
|
"search-title": "SZUKAJ:",
|
||||||
|
|
||||||
|
"search-train-no": "Nr pociągu",
|
||||||
|
"search-train": "Nr pociągu / #",
|
||||||
|
"search-driver": "Nick maszynisty",
|
||||||
|
"search-dispatcher": "Nick dyżurnego",
|
||||||
|
"search-station": "Nazwa scenerii",
|
||||||
|
"search-author": "Nick autora rozkładu jazdy",
|
||||||
|
"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-routeDistance": "kilometraż",
|
||||||
|
"sort-allStopsCount": "stacje",
|
||||||
|
"sort-beginDate": "data",
|
||||||
|
"sort-timetableId": "ID rozkładu",
|
||||||
|
"sort-timestampFrom": "data",
|
||||||
|
"sort-duration": "czas dyżuru",
|
||||||
|
"sort-id": "id rozkładu",
|
||||||
|
|
||||||
|
"sort-mass": "masa",
|
||||||
|
"sort-speed": "prędkość",
|
||||||
|
"sort-length": "długość",
|
||||||
|
"sort-timetable": "nr pociągu",
|
||||||
|
"sort-progress": "przebyta trasa",
|
||||||
|
"sort-delay": "opóźnienie",
|
||||||
|
"sort-comments": "uwagi ekspl.",
|
||||||
|
|
||||||
|
"filter-withComments": "UWAGI EKSPLOATACYJNE",
|
||||||
|
"filter-noComments": "BEZ UWAG",
|
||||||
|
"filter-twr": "WYS. RYZYKA",
|
||||||
|
"filter-skr": "SKRAJNIA",
|
||||||
|
"filter-twr-skr": "WSZYSTKIE",
|
||||||
|
"filter-common": "ZWYKŁE",
|
||||||
|
"filter-passenger": "PASAŻERSKIE",
|
||||||
|
"filter-freight": "TOWAROWE",
|
||||||
|
"filter-other": "INNE",
|
||||||
|
"filter-noTimetable": "BEZ RJ",
|
||||||
|
"filter-withTimetable": "ROZKŁAD JAZDY",
|
||||||
|
|
||||||
|
"filter-reset": "ZRESETUJ FILTRY",
|
||||||
|
"filter-clear": "WYŁĄCZ FILTRY",
|
||||||
|
|
||||||
|
"filter-section-timetable-status": "STATUS ROZKŁADU JAZDY",
|
||||||
|
"filter-section-twrskr": "UWAGI",
|
||||||
|
|
||||||
|
"filter-all": "WSZYSTKIE",
|
||||||
|
"filter-abandoned": "PORZUCONE",
|
||||||
|
"filter-fulfilled": "WYPEŁNIONE",
|
||||||
|
"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",
|
||||||
@@ -92,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",
|
||||||
@@ -112,28 +203,33 @@
|
|||||||
"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",
|
||||||
"no-stations": "Brak stacji do wyświetlenia!"
|
"timetableConfirmed": "Zatwierdzone rozkłady jazdy",
|
||||||
|
"timetableUnconfirmed": "Niezatwierdzone rozkłady jazdy",
|
||||||
|
"no-stations": "Brak stacji do wyświetlenia!",
|
||||||
|
"scenery-search": "Wyszukaj scenerię..."
|
||||||
},
|
},
|
||||||
"trains": {
|
"trains": {
|
||||||
"no-trains": "Brak pociągów do wyświetlenia!",
|
"no-trains": "Brak pociągów do wyświetlenia!",
|
||||||
@@ -152,28 +248,6 @@
|
|||||||
"current-signal": "przy semaforze",
|
"current-signal": "przy semaforze",
|
||||||
"current-track": "na szlaku",
|
"current-track": "na szlaku",
|
||||||
|
|
||||||
"option-mass": "masa",
|
|
||||||
"option-speed": "prędkość",
|
|
||||||
"option-length": "długość",
|
|
||||||
"option-distance": "kilometraż",
|
|
||||||
"option-timetable": "nr pociągu",
|
|
||||||
"option-progress": "przebyta trasa",
|
|
||||||
"option-delay": "opóźnienie",
|
|
||||||
"option-comments": "uwagi ekspl.",
|
|
||||||
|
|
||||||
"filter-comments": "uwagi ekspl.",
|
|
||||||
"filter-twr": "TWR",
|
|
||||||
"filter-skr": "SKR",
|
|
||||||
"filter-passenger": "pasażerskie",
|
|
||||||
"filter-freight": "towarowe",
|
|
||||||
"filter-other": "inne",
|
|
||||||
"filter-noTimetable": "bez RJ",
|
|
||||||
"filter-reset": "X RESETUJ",
|
|
||||||
|
|
||||||
"sorter-prefix": "Sortuj: ",
|
|
||||||
"search-train": "Numer pociągu",
|
|
||||||
"search-driver": "Nick maszynisty",
|
|
||||||
|
|
||||||
"delayed": "Opóźniony: ",
|
"delayed": "Opóźniony: ",
|
||||||
"preponed": "Przed czasem: ",
|
"preponed": "Przed czasem: ",
|
||||||
"on-time": "Planowo",
|
"on-time": "Planowo",
|
||||||
@@ -197,43 +271,27 @@
|
|||||||
"last-seen-min": "od minuty",
|
"last-seen-min": "od minuty",
|
||||||
"last-seen-ago": "od {minutes} minut",
|
"last-seen-ago": "od {minutes} minut",
|
||||||
|
|
||||||
"scenery-offline": "Przejazd offline"
|
"scenery-offline": "Przejazd offline",
|
||||||
|
|
||||||
|
"timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR"
|
||||||
},
|
},
|
||||||
"journal": {
|
"journal": {
|
||||||
"title": "HISTORIA DYŻURÓW",
|
"title": "HISTORIA DYŻURÓW",
|
||||||
"loading": "Ładowanie historii dyżurów...",
|
"loading": "Ładowanie historii dyżurów...",
|
||||||
"no-history": "Brak historii dyżurów dla tej scenerii!",
|
"no-history": "Brak historii dyżurów dla tej scenerii!",
|
||||||
|
"data-refreshed-at": "Dane odświeżone o",
|
||||||
|
|
||||||
"section-timetables": "ROZKŁADY JAZDY",
|
"section-timetables": "ROZKŁADY JAZDY",
|
||||||
"section-dispatchers": "DYŻURNI",
|
"section-dispatchers": "DYŻURNI",
|
||||||
|
|
||||||
"search": "Szukaj",
|
|
||||||
"search-train": "Nr pociągu / #",
|
|
||||||
"search-driver": "Nick maszynisty",
|
|
||||||
"search-dispatcher": "Nick dyżurnego",
|
|
||||||
"search-station": "Nazwa scenerii",
|
|
||||||
|
|
||||||
"sort-prefix": "Sortuj: ",
|
|
||||||
|
|
||||||
"option-distance": "kilometraż",
|
|
||||||
"option-total-stops": "stacje",
|
|
||||||
"option-beginDate": "data",
|
|
||||||
"option-timetableId": "ID rozkładu",
|
|
||||||
"option-timestampFrom": "data",
|
|
||||||
"option-duration": "czas dyżuru",
|
|
||||||
|
|
||||||
"filter-all": "WSZYSTKIE",
|
|
||||||
"filter-abandoned": "PORZUCONE",
|
|
||||||
"filter-fulfilled": "WYPEŁNIONE",
|
|
||||||
"filter-active": "AKTYWNE",
|
|
||||||
|
|
||||||
"no-further-data": "Brak dalszych wyników dla podanych parametrów",
|
"no-further-data": "Brak dalszych wyników dla podanych parametrów",
|
||||||
"loading-further-data": "Ładowanie...",
|
"loading-further-data": "Ładowanie...",
|
||||||
|
|
||||||
"online-since": "ONLINE OD",
|
"online-since": "ONLINE OD",
|
||||||
"duty-lasted": "Dyżur trwał",
|
"duty-lasted": "Dyżur trwał",
|
||||||
"minutes": "{minutes} min.",
|
"hours": "{value} godz.",
|
||||||
"hours": "{hours} godz. {minutes} min.",
|
"minutes": "{value} min.",
|
||||||
|
"seconds": "{value} sek.",
|
||||||
|
|
||||||
"route-length": "Kilometraż:",
|
"route-length": "Kilometraż:",
|
||||||
"station-count": "Stacje:",
|
"station-count": "Stacje:",
|
||||||
@@ -241,7 +299,46 @@
|
|||||||
"timetable-day": "Rozkład z dnia",
|
"timetable-day": "Rozkład z dnia",
|
||||||
"timetable-active": "AKTYWNY",
|
"timetable-active": "AKTYWNY",
|
||||||
"timetable-fulfilled": "WYPEŁNIONY",
|
"timetable-fulfilled": "WYPEŁNIONY",
|
||||||
"timetable-abandoned": "PORZUCONY"
|
"timetable-abandoned": "PORZUCONY",
|
||||||
|
|
||||||
|
"stock-info": "DODATKOWE INFORMACJE",
|
||||||
|
"stock-length": "Długość",
|
||||||
|
"stock-mass": "Masa",
|
||||||
|
"stock-max-speed": "Prędkość maks.",
|
||||||
|
|
||||||
|
"load-data": "Pobierz dalszą historię...",
|
||||||
|
|
||||||
|
"stats-title": "STATYSTYKI MASZYNISTY",
|
||||||
|
|
||||||
|
"last-seen-at": "Ostatnio widziany na: ",
|
||||||
|
"currently-at": "Obecnie na scenerii: ",
|
||||||
|
|
||||||
|
"stats-timetables": "ROZKŁADY JAZDY",
|
||||||
|
"stats-longest-timetable": "NAJDŁUŻSZY RJ",
|
||||||
|
"stats-avg-timetable": "ŚREDNIA DŁUGOŚĆ RJ",
|
||||||
|
"stats-distance": "DYSTANS",
|
||||||
|
"stats-stations": "STACJE",
|
||||||
|
|
||||||
|
"timetable-stats-total": "Stworzone rozkłady jazdy: {count} (łączny dystans: {distance})",
|
||||||
|
"timetable-stats-longest": "Najdłuższy rozkład jazdy: #{id} (stworzony przez dyżurnego {author} dla maszynisty {driver} o dystansie {distance})",
|
||||||
|
"timetable-stats-most-active-dr": "Najaktywniejszy dyżurny: {dispatcher} (stworzył {count})",
|
||||||
|
"timetable-stats-most-active-dr-many": "Najaktywniejsi dyżurni: {dispatchers} (stworzyli po {count})",
|
||||||
|
"timetable-stats-most-active-driver": "Najaktywniejszy maszynista: {driver} (łączny przejechany dystans: {distance})",
|
||||||
|
"timetable-stats-longest-duties": "Najdłuższa służba: {dispatcher} na scenerii {station} (czas trwania: {duration})",
|
||||||
|
|
||||||
|
"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! :/",
|
||||||
|
|
||||||
|
"timetable-location-signal": "semafor:",
|
||||||
|
"timetable-location-route": "szlak:"
|
||||||
},
|
},
|
||||||
"scenery": {
|
"scenery": {
|
||||||
"users": "GRACZE ONLINE",
|
"users": "GRACZE ONLINE",
|
||||||
@@ -254,24 +351,43 @@
|
|||||||
"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",
|
||||||
|
|
||||||
|
"dispatchers-history-hash": "Hash",
|
||||||
|
"dispatchers-history-dispatcher": "Dyżurny",
|
||||||
|
"dispatchers-history-level": "Poziom",
|
||||||
|
"dispatchers-history-rate": "Ocena",
|
||||||
|
"dispatchers-history-date": "Data służby",
|
||||||
|
|
||||||
"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)",
|
||||||
|
|
||||||
|
"bottom-info": "Pokaż pełną historię w zakładce Dziennika"
|
||||||
},
|
},
|
||||||
"availability": {
|
"availability": {
|
||||||
"title": "Dostępność",
|
"title": "Dostępność",
|
||||||
@@ -283,14 +399,22 @@
|
|||||||
},
|
},
|
||||||
"timetables": {
|
"timetables": {
|
||||||
"timetable-only": "Wyodrębnij rozkłady jazdy",
|
"timetable-only": "Wyodrębnij rozkłady jazdy",
|
||||||
"online": "Na stacji",
|
"end": "Koniec rozkładu jazdy",
|
||||||
"departed": "Odprawiony do:",
|
"terminated": "Rozkład jazdy zakończony",
|
||||||
"departed-away": "Odjechał do:",
|
|
||||||
"arriving": "W drodze z:",
|
|
||||||
"stopped": "Postój",
|
|
||||||
"terminated": "Skończył bieg",
|
|
||||||
"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,
|
||||||
|
|||||||
@@ -28,6 +28,19 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
stringToDate(dateString?: string) {
|
||||||
|
return dateString ? new Date(dateString) : null;
|
||||||
|
},
|
||||||
|
|
||||||
|
parseDateToTimeString(date: Date | null) {
|
||||||
|
return (
|
||||||
|
date?.toLocaleTimeString('pl-PL', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
}) || ''
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
timestampToString(timestamp: number | null) {
|
timestampToString(timestamp: number | null) {
|
||||||
return timestamp
|
return timestamp
|
||||||
? new Date(timestamp).toLocaleTimeString('pl-PL', {
|
? new Date(timestamp).toLocaleTimeString('pl-PL', {
|
||||||
@@ -37,14 +50,21 @@ export default defineComponent({
|
|||||||
: '';
|
: '';
|
||||||
},
|
},
|
||||||
|
|
||||||
calculateDuration(timestampMs: number) {
|
calculateDuration(timestampMs: number, showSeconds = false) {
|
||||||
|
const secondsTotal = Math.floor(timestampMs / 1000);
|
||||||
const minsTotal = Math.round(timestampMs / 60000);
|
const minsTotal = Math.round(timestampMs / 60000);
|
||||||
const hoursTotal = Math.floor(minsTotal / 60);
|
const hoursTotal = Math.floor(minsTotal / 60);
|
||||||
const minsInHour = minsTotal % 60;
|
const minsInHour = minsTotal % 60;
|
||||||
|
|
||||||
return minsTotal > 60
|
return minsTotal >= 60
|
||||||
? this.$t('journal.hours', { hours: hoursTotal, minutes: minsInHour })
|
? `${this.$t('journal.hours', { value: hoursTotal }, hoursTotal)} ${this.$t(
|
||||||
: this.$t('journal.minutes', { minutes: minsTotal });
|
'journal.minutes',
|
||||||
|
{ value: minsInHour },
|
||||||
|
minsInHour
|
||||||
|
)}`
|
||||||
|
: showSeconds && secondsTotal <= 60
|
||||||
|
? this.$t('journal.seconds', { value: secondsTotal }, secondsTotal)
|
||||||
|
: this.$t('journal.minutes', { value: minsTotal }, minsTotal);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
preventKeyDown: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
activated() {
|
||||||
|
window.addEventListener('keydown', this.handleKeyDown);
|
||||||
|
},
|
||||||
|
|
||||||
|
deactivated() {
|
||||||
|
window.removeEventListener('keydown', this.handleKeyDown);
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onKeyDownFunction() {},
|
||||||
|
|
||||||
|
handleKeyDown(e: KeyboardEvent) {
|
||||||
|
if (!e.key) return;
|
||||||
|
if (e.key.toLowerCase() == 'f' && !this.preventKeyDown && !e.ctrlKey && !e.altKey) this.onKeyDownFunction();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data: () => ({
|
||||||
|
observer: null as IntersectionObserver | null,
|
||||||
|
observerTarget: null as Element | null,
|
||||||
|
}),
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
mountObserver(actionFunction: () => void, target: Element) {
|
||||||
|
this.observer = new IntersectionObserver((entries) => {
|
||||||
|
console.log(entries);
|
||||||
|
|
||||||
|
if (entries[0].intersectionRatio > 0.5) actionFunction();
|
||||||
|
}, { threshold: 0.2 });
|
||||||
|
|
||||||
|
this.observer.observe(target);
|
||||||
|
},
|
||||||
|
|
||||||
|
unmountObserver() {
|
||||||
|
if (!this.observerTarget) return;
|
||||||
|
|
||||||
|
this.observer?.unobserve(this.observerTarget);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -2,16 +2,12 @@ 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(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
|
||||||
console.log('Mixin mounted');
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
chosenTrain() {
|
chosenTrain() {
|
||||||
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
|
return this.store.trainList.find((train) => train.trainId == this.store.chosenModalTrainId);
|
||||||
@@ -21,10 +17,15 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
selectModalTrain(trainId: string) {
|
selectModalTrain(trainId: string) {
|
||||||
this.store.chosenModalTrainId = trainId;
|
this.store.chosenModalTrainId = trainId;
|
||||||
|
document.body.classList.add('no-scroll');
|
||||||
},
|
},
|
||||||
|
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.store.chosenModalTrainId = undefined;
|
this.store.chosenModalTrainId = undefined;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.classList.remove('no-scroll');
|
||||||
|
}, 150);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
activated() {
|
activated() {
|
||||||
window.addEventListener('scroll', this.handleScroll);
|
window.addEventListener('wheel', this.handleScroll);
|
||||||
},
|
},
|
||||||
|
|
||||||
deactivated() {
|
deactivated() {
|
||||||
window.removeEventListener('scroll', this.handleScroll);
|
window.removeEventListener('wheel', this.handleScroll);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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> = [
|
||||||
{
|
{
|
||||||
@@ -12,42 +12,32 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
path: '/trains',
|
path: '/trains',
|
||||||
name: 'TrainsView',
|
name: 'TrainsView',
|
||||||
component: () => import('../views/TrainsView.vue'),
|
component: () => import('../views/TrainsView.vue'),
|
||||||
props: (route) => ({ train: route.query.train, driver: route.query.driver }),
|
props: (route) => ({ train: route.query.train, driver: route.query.driver, trainId: route.query.trainId }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/scenery',
|
path: '/scenery',
|
||||||
name: 'SceneryView',
|
name: 'SceneryView',
|
||||||
component: () => import('../views/SceneryView.vue'),
|
component: () => import('../views/SceneryView.vue'),
|
||||||
props: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
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(.*)',
|
||||||
@@ -59,7 +49,7 @@ const router = createRouter({
|
|||||||
scrollBehavior(to, from) {
|
scrollBehavior(to, from) {
|
||||||
if (to.name == 'SceneryView' && from.name) return { el: `.app_main` };
|
if (to.name == 'SceneryView' && from.name) return { el: `.app_main` };
|
||||||
|
|
||||||
if (from.name == 'SceneryView' && to.name == 'StationsView') return { el: `.last-selected`, top: 20 };
|
// if (from.name == 'SceneryView' && to.name == 'StationsView') return { el: `.last-selected`, top: 20 };
|
||||||
},
|
},
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes,
|
routes,
|
||||||
|
|||||||
@@ -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,4 +1,4 @@
|
|||||||
export const enum DataStatus {
|
export enum DataStatus {
|
||||||
Initialized = -1,
|
Initialized = -1,
|
||||||
Loading = 0,
|
Loading = 0,
|
||||||
Error = 1,
|
Error = 1,
|
||||||
|
|||||||
@@ -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,26 +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;
|
||||||
|
|
||||||
stopLabel: string;
|
terminatesAt: string;
|
||||||
stopStatus: string;
|
beginsAt: string;
|
||||||
stopStatusID: number;
|
|
||||||
|
prevStationName: string;
|
||||||
|
nextStationName: string;
|
||||||
|
|
||||||
|
arrivingLine: string | null;
|
||||||
|
departureLine: string | null;
|
||||||
|
|
||||||
|
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;
|
||||||
@@ -38,7 +41,7 @@ export default interface Station {
|
|||||||
maxUsers: number;
|
maxUsers: number;
|
||||||
currentUsers: number;
|
currentUsers: number;
|
||||||
|
|
||||||
spawns: { spawnName: string; spawnLength: number }[];
|
spawns: { spawnName: string; spawnLength: number; isElectrified: boolean }[];
|
||||||
dispatcherRate: number;
|
dispatcherRate: number;
|
||||||
dispatcherName: string;
|
dispatcherName: string;
|
||||||
dispatcherExp: number;
|
dispatcherExp: number;
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -19,9 +20,11 @@ export default interface Train {
|
|||||||
online: boolean;
|
online: boolean;
|
||||||
lastSeen: number;
|
lastSeen: number;
|
||||||
region: string;
|
region: string;
|
||||||
|
|
||||||
cars: string[];
|
cars: string[];
|
||||||
|
|
||||||
|
isTimeout: boolean;
|
||||||
|
isSupporter: boolean;
|
||||||
|
|
||||||
timetableData?: {
|
timetableData?: {
|
||||||
timetableId: number;
|
timetableId: number;
|
||||||
category: string;
|
category: string;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
export interface DispatcherHistory {
|
export interface DispatcherHistory {
|
||||||
|
id: string;
|
||||||
|
|
||||||
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,52 @@
|
|||||||
|
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;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
mostActiveDrivers: {
|
||||||
|
name: string;
|
||||||
|
distance: number;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
longestDuties: {
|
||||||
|
name: string;
|
||||||
|
duration: number;
|
||||||
|
station: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITimetablesDailyStatsResponse {
|
||||||
|
totalTimetables: number;
|
||||||
|
distanceSum: number;
|
||||||
|
distanceAvg: number;
|
||||||
|
maxTimetable: TimetableHistory | null;
|
||||||
|
|
||||||
|
mostActiveDispatchers: {
|
||||||
|
name: string;
|
||||||
|
count: number;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
mostActiveDrivers: {
|
||||||
|
name: string;
|
||||||
|
distance: number;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
longestDuties: {
|
||||||
|
name: string;
|
||||||
|
duration: number;
|
||||||
|
station: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,13 +1,22 @@
|
|||||||
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;
|
||||||
sceneriesString: string;
|
sceneriesString: string;
|
||||||
|
currentLocation: string[];
|
||||||
|
|
||||||
routeDistance: number;
|
routeDistance: number;
|
||||||
currentDistance: number;
|
currentDistance: number;
|
||||||
@@ -26,10 +35,33 @@ export interface TimetableHistory {
|
|||||||
|
|
||||||
authorName?: string;
|
authorName?: string;
|
||||||
authorId?: number;
|
authorId?: number;
|
||||||
|
|
||||||
|
stopsString?: string;
|
||||||
|
|
||||||
|
stockString?: string;
|
||||||
|
stockHistory: string[];
|
||||||
|
|
||||||
|
stockMass?: number;
|
||||||
|
stockLength?: number;
|
||||||
|
maxSpeed?: number;
|
||||||
|
|
||||||
|
hashesString?: string;
|
||||||
|
currentSceneryName?: string;
|
||||||
|
currentSceneryHash?: string;
|
||||||
|
|
||||||
|
routeSceneries?: string;
|
||||||
|
|
||||||
|
checkpointArrivals?: string[];
|
||||||
|
checkpointDepartures?: string[];
|
||||||
|
|
||||||
|
checkpointArrivalsScheduled?: string[];
|
||||||
|
checkpointDeparturesScheduled?: string[];
|
||||||
|
|
||||||
|
checkpointStopTypes?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SceneryTimetableHistory {
|
export interface SceneryTimetableHistory {
|
||||||
sceneryTimetables: TimetableHistory[];
|
timetables: TimetableHistory[];
|
||||||
totalCount: number;
|
// totalCount: number;
|
||||||
sceneryName: string;
|
// sceneryName: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { JournalTimetableSorter } from '../../types/JournalTimetablesTypes';
|
||||||
|
|
||||||
|
export interface TimetablesQueryParams {
|
||||||
|
driverName?: string;
|
||||||
|
trainNo?: string;
|
||||||
|
timetableId?: 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;
|
||||||
@@ -21,42 +62,7 @@ export default interface TrainAPIData {
|
|||||||
lastSeen: number;
|
lastSeen: number;
|
||||||
|
|
||||||
region: string;
|
region: string;
|
||||||
|
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,12 +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 { DispatcherStatsAPIData } from '../scripts/interfaces/api/DispatcherStatsAPIData';
|
import StationAPIData from '../api/StationAPIData';
|
||||||
import { DriverStatsAPIData } from '../scripts/interfaces/api/DriverStatsAPIData';
|
import { TrainAPIData } from '../api/TrainAPIData';
|
||||||
import StationAPIData from '../scripts/interfaces/api/StationAPIData';
|
import Station from '../Station';
|
||||||
import TrainAPIData from '../scripts/interfaces/api/TrainAPIData';
|
import Train from '../Train';
|
||||||
import Station from '../scripts/interfaces/Station';
|
import { DispatcherStatsAPIData } from '../api/DispatcherStatsAPIData';
|
||||||
import Train from '../scripts/interfaces/Train';
|
import { DriverStatsAPIData } from '../api/DriverStatsAPIData';
|
||||||
|
|
||||||
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
|
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
|
||||||
|
|
||||||
@@ -24,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;
|
||||||
@@ -42,19 +45,33 @@ export interface StoreState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
listenerLaunched: boolean;
|
listenerLaunched: boolean;
|
||||||
|
blockScroll: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface APIData {
|
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,292 +0,0 @@
|
|||||||
import Filter from '../interfaces/Filter';
|
|
||||||
import Station from '../interfaces/Station';
|
|
||||||
import StorageManager from './storageManager';
|
|
||||||
|
|
||||||
const sortStations = (a: Station, b: Station, sorter: { index: number; dir: number }) => {
|
|
||||||
switch (sorter.index) {
|
|
||||||
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 sorter.dir == 1 ? a.name.localeCompare(b.name) : b.name.localeCompare(a.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') 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class StationFilterManager {
|
|
||||||
private 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,
|
|
||||||
};
|
|
||||||
|
|
||||||
private filters: Filter = { ...this.filterInitStates };
|
|
||||||
|
|
||||||
private sorter: { index: number; dir: number } = { index: 0, dir: 1 };
|
|
||||||
|
|
||||||
checkFilters() {
|
|
||||||
if (!StorageManager.isRegistered('options_saved')) return;
|
|
||||||
|
|
||||||
Object.keys(this.filterInitStates).forEach((filterKey) => {
|
|
||||||
if (StorageManager.isRegistered(filterKey)) return;
|
|
||||||
|
|
||||||
const filterType = typeof this.filterInitStates[filterKey];
|
|
||||||
|
|
||||||
if (filterType === 'boolean')
|
|
||||||
StorageManager.setBooleanValue(filterKey, !this.filterInitStates[filterKey] as boolean);
|
|
||||||
|
|
||||||
if (filterType === 'number')
|
|
||||||
StorageManager.setNumericValue(filterKey, this.filterInitStates[filterKey] as number);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getFilteredStationList(stationList: Station[], region: string): Station[] {
|
|
||||||
return stationList
|
|
||||||
.map((station) => {
|
|
||||||
if (station.onlineInfo && station.onlineInfo.region != region) {
|
|
||||||
delete station.onlineInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
return station;
|
|
||||||
})
|
|
||||||
.filter((station) => filterStations(station, this.filters))
|
|
||||||
.sort((a, b) => sortStations(a, b, this.sorter));
|
|
||||||
}
|
|
||||||
|
|
||||||
changeFilterValue(filter: { name: string; value: number }) {
|
|
||||||
this.filters[filter.name] = filter.value;
|
|
||||||
|
|
||||||
// if(filter.name == 'authors')
|
|
||||||
}
|
|
||||||
|
|
||||||
resetFilters() {
|
|
||||||
this.filters = { ...this.filterInitStates };
|
|
||||||
}
|
|
||||||
|
|
||||||
invertFilters() {
|
|
||||||
Object.keys(this.filters).forEach((prop) => {
|
|
||||||
if (typeof this.filters[prop] !== 'boolean') return;
|
|
||||||
|
|
||||||
this.filters[prop] = !this.filters[prop];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
changeSorter(index: number) {
|
|
||||||
if (index > 4 && index < 7) return;
|
|
||||||
|
|
||||||
if (index == this.sorter.index) this.sorter.dir = -1 * this.sorter.dir;
|
|
||||||
else this.sorter.dir = 1;
|
|
||||||
|
|
||||||
this.sorter.index = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSorter() {
|
|
||||||
return this.sorter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -23,6 +23,13 @@ export default class StorageManager {
|
|||||||
window.localStorage.setItem(key, val);
|
window.localStorage.setItem(key, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static setValue(key: string, val: any) {
|
||||||
|
if (typeof val == 'boolean') this.setBooleanValue(key, val);
|
||||||
|
else if (typeof val == 'number') this.setNumericValue(key, val);
|
||||||
|
else if (typeof val == 'string') this.setStringValue(key, val);
|
||||||
|
else this.setStringValue(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
static removeValue(key: string) {
|
static removeValue(key: string) {
|
||||||
window.localStorage.removeItem(key);
|
window.localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,115 +1,130 @@
|
|||||||
import { TrainFilter } from "vue";
|
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,8 @@
|
|||||||
|
export type JournalDispatcherSearcher = {
|
||||||
|
[key in 'search-dispatcher' | 'search-station' | 'search-date']: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface JournalDispatcherSorter {
|
||||||
|
id: 'timestampFrom' | 'duration';
|
||||||
|
dir: -1 | 1;
|
||||||
|
}
|
||||||
@@ -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';
|
||||||
|
|
||||||
@@ -66,40 +66,41 @@ export const parseSpawns = (spawnString: string) => {
|
|||||||
const spawnArray = spawn.split(',');
|
const spawnArray = spawn.split(',');
|
||||||
const spawnName = spawnArray[6] ? spawnArray[6] : spawnArray[0];
|
const spawnName = spawnArray[6] ? spawnArray[6] : spawnArray[0];
|
||||||
const spawnLength = parseInt(spawnArray[2]);
|
const spawnLength = parseInt(spawnArray[2]);
|
||||||
|
const isElectrified = spawnArray[3] == 'True';
|
||||||
|
|
||||||
return { spawnName, spawnLength };
|
return { spawnName, spawnLength, isElectrified };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -117,31 +118,37 @@ export function getScheduledTrain(train: Train, trainStopIndex: number, stationN
|
|||||||
let prevStationName = '',
|
let prevStationName = '',
|
||||||
nextStationName = '';
|
nextStationName = '';
|
||||||
|
|
||||||
|
let prevDepartureLine: string | null = null,
|
||||||
|
nextArrivalLine: string | null = null;
|
||||||
|
|
||||||
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;
|
prevStationName = followingStops[i].stopNameRAW.replace(/,.*/g, '');
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
nextStationName = followingStops[i].stopNameRAW.replace(/,.*/g, '');
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let departureLine: string | null = trainStop.departureLine;
|
let departureLine: string | null = null;
|
||||||
let arrivingLine: string | null = trainStop.arrivalLine;
|
let arrivingLine: string | null = null;
|
||||||
|
|
||||||
for (let i = trainStopIndex; i < followingStops.length; i++) {
|
for (let i = trainStopIndex; i < followingStops.length; i++) {
|
||||||
const currentStop = followingStops[i];
|
const currentStop = followingStops[i];
|
||||||
|
|
||||||
if (currentStop.departureLine == null) break;
|
if (currentStop.departureLine == null) continue;
|
||||||
|
|
||||||
if (!/-|_|it|sbl/gi.test(currentStop.departureLine)) {
|
if (!/-|_|it|sbl/gi.test(currentStop.departureLine)) {
|
||||||
departureLine = currentStop.departureLine;
|
departureLine = currentStop.departureLine;
|
||||||
|
nextArrivalLine = followingStops[i + 1]?.arrivalLine || null;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,10 +156,12 @@ export function getScheduledTrain(train: Train, trainStopIndex: number, stationN
|
|||||||
for (let i = trainStopIndex; i >= 0; i--) {
|
for (let i = trainStopIndex; i >= 0; i--) {
|
||||||
const currentStop = followingStops[i];
|
const currentStop = followingStops[i];
|
||||||
|
|
||||||
if (currentStop.arrivalLine == null) break;
|
if (currentStop.arrivalLine == null) continue;
|
||||||
|
|
||||||
if (!/-|_|it|sbl/gi.test(currentStop.arrivalLine)) {
|
if (!/-|_|it|sbl/gi.test(currentStop.arrivalLine)) {
|
||||||
arrivingLine = currentStop.arrivalLine;
|
arrivingLine = currentStop.arrivalLine;
|
||||||
|
prevDepartureLine = followingStops[i - 1]?.departureLine || null;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,6 +170,9 @@ export function getScheduledTrain(train: Train, trainStopIndex: number, stationN
|
|||||||
trainNo: train.trainNo,
|
trainNo: train.trainNo,
|
||||||
trainId: train.trainId,
|
trainId: train.trainId,
|
||||||
|
|
||||||
|
signal: train.signal,
|
||||||
|
connectedTrack: train.connectedTrack,
|
||||||
|
|
||||||
driverName: train.driverName,
|
driverName: train.driverName,
|
||||||
driverId: train.driverId,
|
driverId: train.driverId,
|
||||||
currentStationName: train.currentStationName,
|
currentStationName: train.currentStationName,
|
||||||
@@ -179,5 +191,8 @@ export function getScheduledTrain(train: Train, trainStopIndex: number, stationN
|
|||||||
|
|
||||||
arrivingLine,
|
arrivingLine,
|
||||||
departureLine,
|
departureLine,
|
||||||
|
|
||||||
|
nextArrivalLine,
|
||||||
|
prevDepartureLine,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export const useJournalFiltersStore = defineStore('journalFiltersStore', {
|
||||||
|
state: () => ({
|
||||||
|
timetableFilters: {
|
||||||
|
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import inputData from '../data/options.json';
|
||||||
|
import Station from '../scripts/interfaces/Station';
|
||||||
|
import StorageManager from '../scripts/managers/storageManager';
|
||||||
|
import { useStore } from './store';
|
||||||
|
import { filterInitStates } from '../scripts/constants/stores/initFilterStates';
|
||||||
|
import { filterStations, sortStations } from '../scripts/utils/filterUtils';
|
||||||
|
import { HeadIdsTypes } from '../scripts/data/stationHeaderNames';
|
||||||
|
|
||||||
|
export const useStationFiltersStore = defineStore('stationFiltersStore', {
|
||||||
|
state() {
|
||||||
|
return {
|
||||||
|
inputs: inputData,
|
||||||
|
filters: { ...filterInitStates },
|
||||||
|
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: {
|
||||||
|
getFilteredStationList(stationList: Station[], region: string): Station[] {
|
||||||
|
return stationList
|
||||||
|
.map((station) => {
|
||||||
|
if (station.onlineInfo && station.onlineInfo.region != region) {
|
||||||
|
delete station.onlineInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return station;
|
||||||
|
})
|
||||||
|
.filter((station) => filterStations(station, this.filters))
|
||||||
|
.sort((a, b) => sortStations(a, b, this.sorterActive));
|
||||||
|
},
|
||||||
|
|
||||||
|
setupFilters() {
|
||||||
|
if (!StorageManager.isRegistered('options_saved')) return;
|
||||||
|
|
||||||
|
this.inputs.options.forEach((option) => {
|
||||||
|
if (!StorageManager.isRegistered(option.name)) return;
|
||||||
|
const savedValue = StorageManager.getBooleanValue(option.name);
|
||||||
|
|
||||||
|
this.filters[option.name] = savedValue;
|
||||||
|
option.value = !savedValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.inputs.sliders.forEach((slider) => {
|
||||||
|
if (!StorageManager.isRegistered(slider.name)) return;
|
||||||
|
const savedValue = StorageManager.getNumericValue(slider.name);
|
||||||
|
|
||||||
|
this.filters[slider.name] = savedValue;
|
||||||
|
slider.value = savedValue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
changeFilterValue(filter: { name: string; value: any }) {
|
||||||
|
this.filters[filter.name] = filter.value;
|
||||||
|
|
||||||
|
if (StorageManager.isRegistered('options_saved')) StorageManager.setValue(filter.name, filter.value);
|
||||||
|
},
|
||||||
|
|
||||||
|
resetFilters() {
|
||||||
|
this.filters = { ...filterInitStates };
|
||||||
|
|
||||||
|
this.inputs.options.forEach((option) => {
|
||||||
|
option.value = option.defaultValue;
|
||||||
|
StorageManager.setBooleanValue(option.name, !option.defaultValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.inputs.sliders.forEach((slider) => {
|
||||||
|
slider.value = slider.defaultValue;
|
||||||
|
StorageManager.setNumericValue(slider.name, slider.defaultValue);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
resetSectionOptions(section: string) {
|
||||||
|
this.inputs.options.forEach((option) => {
|
||||||
|
if (option.section != section) return;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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,7 +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: () =>
|
||||||
@@ -24,6 +24,7 @@ export const useStore = defineStore('store', {
|
|||||||
|
|
||||||
stationList: [],
|
stationList: [],
|
||||||
trainList: [],
|
trainList: [],
|
||||||
|
routesList: [],
|
||||||
|
|
||||||
sceneryData: [],
|
sceneryData: [],
|
||||||
lastDispatcherStatuses: [],
|
lastDispatcherStatuses: [],
|
||||||
@@ -34,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,
|
||||||
|
|
||||||
@@ -51,6 +54,9 @@ export const useStore = defineStore('store', {
|
|||||||
trains: DataStatus.Loading,
|
trains: DataStatus.Loading,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
currentStatsTab: null,
|
||||||
|
|
||||||
|
blockScroll: false,
|
||||||
listenerLaunched: false,
|
listenerLaunched: false,
|
||||||
} as StoreState),
|
} as StoreState),
|
||||||
|
|
||||||
@@ -93,6 +99,10 @@ export const useStore = defineStore('store', {
|
|||||||
cars: stock.slice(1),
|
cars: stock.slice(1),
|
||||||
|
|
||||||
lastSeen: train.lastSeen,
|
lastSeen: train.lastSeen,
|
||||||
|
isTimeout: train.isTimeout,
|
||||||
|
|
||||||
|
isSupporter: train.driverIsSupporter,
|
||||||
|
driverLevel: train.driverLevel,
|
||||||
|
|
||||||
timetableData: timetable
|
timetableData: timetable
|
||||||
? {
|
? {
|
||||||
@@ -106,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) {
|
||||||
@@ -216,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);
|
||||||
@@ -277,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;
|
||||||
},
|
},
|
||||||
@@ -331,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) => {
|
||||||
@@ -358,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();
|
||||||
});
|
});
|
||||||
@@ -395,4 +401,3 @@ export const useStore = defineStore('store', {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,37 @@
|
|||||||
@import 'responsive.scss';
|
@import 'responsive.scss';
|
||||||
|
@import 'animations.scss';
|
||||||
// Animations
|
|
||||||
.warning {
|
|
||||||
&-enter-from,
|
|
||||||
&-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-active {
|
|
||||||
transition: all 150ms ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-leave-active {
|
|
||||||
transition: all 150ms ease-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Styles
|
//Styles
|
||||||
|
|
||||||
.journal-wrapper {
|
.list_wrapper {
|
||||||
width: 1350px;
|
overflow-y: auto;
|
||||||
|
height: 90vh;
|
||||||
|
min-height: 550px;
|
||||||
|
|
||||||
|
padding-right: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.journal-list {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.journal_wrapper {
|
||||||
|
max-width: 1350px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
padding: 1em 0;
|
padding: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.journal_refreshed-date {
|
||||||
|
background-color: #333;
|
||||||
|
color: #ddd;
|
||||||
|
text-align: end;
|
||||||
|
|
||||||
|
padding: 0.25em;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
.journal_warning {
|
.journal_warning {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
@@ -38,28 +47,47 @@
|
|||||||
|
|
||||||
.journal_item,
|
.journal_item,
|
||||||
.journal_warning {
|
.journal_warning {
|
||||||
background: #202020;
|
background-color: #1a1a1a;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
margin: 1em 0;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.journal_top-bar {
|
.journal_top-bar {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.btn {
|
.btn--load-data {
|
||||||
padding: 0.5em 0.7em;
|
padding: 0.5em 1em;
|
||||||
|
display: flex;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include smallScreen() {
|
@include smallScreen() {
|
||||||
.journal-wrapper {
|
.list_wrapper {
|
||||||
font-size: 1.25em;
|
font-size: 1.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.journal_top-bar {
|
.journal_top-bar {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.journal_refreshed-date {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (orientation: landscape) {
|
||||||
|
.list_wrapper {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
@import 'variables.scss';
|
||||||
|
@import 'responsive.scss';
|
||||||
|
|
||||||
|
.stats-tab {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
box-shadow: 0 0 5px 1px $accentCol;
|
||||||
|
|
||||||
|
padding: 1em;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-stats {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5em;
|
||||||
|
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-badge {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
span {
|
||||||
|
background-color: $accentCol;
|
||||||
|
color: black;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.2em 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
span:first-child {
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include smallScreen {
|
||||||
|
.journal-stats {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-stats {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||