Compare commits

...

24 Commits

Author SHA1 Message Date
Spythere ea5c9e0028 Merge do wersji 1.17.0
Wersja 1.17.0
2023-09-06 15:43:52 +02:00
Spythere eb7c2d7132 revamp postoju i strzałek w RJ scenerii 2023-09-05 16:48:26 +02:00
Spythere ee7c50f59b szybkie filtry (wip) 2023-09-05 16:10:38 +02:00
Spythere 439f59fedc poprawki filtrów scenerii 2023-09-05 15:40:32 +02:00
Spythere c47d839ce3 poprawki kolorów 2023-09-04 18:50:12 +02:00
Spythere f77c13cbcf srjp: poprawki dostępności modalu 2023-09-04 18:37:52 +02:00
Spythere dbbbd33100 poprawki dzienników 2023-09-03 19:24:32 +02:00
Spythere 14d13360a8 dziennik dr: animacje 2023-09-03 18:47:01 +02:00
Spythere dc862252ba dziennik dr: kolumna regionów 2023-09-03 18:34:45 +02:00
Spythere e5fe727ccd aktualizacja URLi 2023-09-03 17:58:55 +02:00
Spythere e836bbed0c szybkie filtry (wip) 2023-09-02 19:36:49 +02:00
Spythere d4438fd215 station filters active indicator 2023-09-02 18:53:23 +02:00
Spythere 1550849360 dzienniki 2023-09-02 18:47:01 +02:00
Spythere 9d1dc4ffca bump 1.17 2023-08-31 22:46:41 +02:00
Spythere 0397fa788d zmiana wyglądu listy dzienników 2023-08-31 22:45:14 +02:00
Spythere 6e5696b0a6 daily stats hotfix 2023-08-31 21:46:05 +02:00
Spythere 4537341a57 odświeżony wygląd dziennika RJ 2023-08-30 20:31:40 +02:00
Spythere c35c74bd4a Merge pull request #52
fix: data wcześniejszego przyjazdu dla 0pt
2023-08-27 23:07:01 +02:00
Spythere 25735c5e6e fix: data wcześniejszego przyjazdu dla 0pt 2023-08-27 22:45:36 +02:00
Spythere 41e60bc69e Merge do wersji 1.16.3 2023-07-06 01:54:21 +02:00
Spythere 933bdecb3c bump: 1.16.3 2023-07-02 14:50:55 +02:00
Spythere 10e183d96b zamiana infinite scrolla na przyciski 2023-07-02 14:50:18 +02:00
Spythere 5429d39f5e tłumaczenia historii dr scenerii 2023-07-01 23:39:08 +02:00
Spythere ff31e7f903 bump: 1.16.2.1 2023-07-01 23:29:48 +02:00
40 changed files with 1238 additions and 1064 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.16.2", "version": "1.17.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
-1
View File
@@ -1,7 +1,6 @@
@import './styles/responsive.scss'; @import './styles/responsive.scss';
@import './styles/variables.scss'; @import './styles/variables.scss';
@import './styles/global.scss'; @import './styles/global.scss';
@import './styles/scenery_status.scss';
// VUE ROUTE CHANGE ANIMATION // VUE ROUTE CHANGE ANIMATION
.view-anim { .view-anim {
+62
View File
@@ -0,0 +1,62 @@
<template>
<div class="progress-bar">
<span class="bar-bg"></span>
<span class="bar-fg" :style="{ width: `${~~progressPercent}%`, backgroundColor: bgColor }"></span>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
progressPercent: {
type: Number,
required: true,
},
progressType: {
type: String,
required: false,
},
},
computed: {
bgColor() {
switch (this.progressType) {
case 'abandoned':
return 'salmon';
default:
return 'springgreen';
}
},
},
});
</script>
<style lang="scss" scoped>
.progress-bar {
position: relative;
width: 6em;
height: 1em;
margin: 0.5em 0;
.bar-fg,
.bar-bg {
position: absolute;
height: 1em;
width: 100%;
left: 0;
}
.bar-fg {
background-color: springgreen;
}
.bar-bg {
background-color: #5b5b5b;
}
}
</style>
@@ -0,0 +1,90 @@
<template>
<span class="status-badge" :class="statusID" v-if="isOnline">
{{ $t(`status.${statusID}`) }}
{{ statusID == 'online' ? timestampToString(statusTimestamp!) : '' }}
</span>
<span class="status-badge free" v-else>
{{ $t('status.free') }}
</span>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import dateMixin from '../../mixins/dateMixin';
export default defineComponent({
props: {
statusID: {
type: String,
},
statusTimestamp: {
type: Number,
},
isOnline: {
type: Boolean,
},
},
mixins: [dateMixin],
});
</script>
<style lang="scss" scoped>
$free: #8a8a8a;
$ending: #e6c300;
$no-limit: #117fc9;
$unav: #ff3d5d;
$brb: #e6a100;
$no-space: #222;
$online: #09a116;
$unknown: rgb(185, 60, 60);
.status-badge {
border-radius: 1rem;
font-weight: 500;
padding: 0.2em 0.55em;
background-color: $online;
&.free {
background-color: $free;
font-size: 0.95em;
}
&.ending {
background-color: $ending;
color: black;
font-size: 0.9em;
}
&.no-limit {
background-color: $no-limit;
font-size: 0.85em;
}
&.not-signed,
&.unavailable {
background-color: $unav;
font-size: 0.85em;
}
&.brb {
background-color: $brb;
color: black;
font-size: 0.95em;
}
&.no-space {
background-color: $no-space;
border: 1px solid white;
color: white;
font-size: 0.85em;
}
&.unknown {
background-color: $unknown;
font-size: 0.95em;
}
}
</style>
+119 -119
View File
@@ -1,119 +1,119 @@
<template> <template>
<span class="stop-date"> <span class="stop-date">
<span <span
class="date arrival" class="date arrival"
v-if="!stop.beginsHere" v-if="!stop.beginsHere"
:class="{ :class="{
delayed: stop.arrivalDelay > 0 && stop.confirmed, delayed: stop.arrivalDelay > 0 && (stop.confirmed || stop.stopped),
preponed: stop.arrivalDelay < 0 && stop.confirmed, preponed: stop.arrivalDelay < 0 && (stop.confirmed || stop.stopped),
'on-time': stop.arrivalDelay == 0 && stop.confirmed, 'on-time': stop.arrivalDelay == 0 && stop.confirmed,
}" }"
> >
<span v-if="stop.arrivalDelay != 0 && stop.confirmed"> <span v-if="stop.arrivalDelay != 0 && (stop.confirmed || stop.stopped)">
<s>{{ timestampToString(stop.arrivalTimestamp) }}</s> <s>{{ timestampToString(stop.arrivalTimestamp) }}</s>
{{ timestampToString(stop.arrivalRealTimestamp) }} {{ timestampToString(stop.arrivalRealTimestamp) }}
({{ stop.arrivalDelay > 0 ? '+' : '' }}{{ stop.arrivalDelay }}) ({{ stop.arrivalDelay > 0 ? '+' : '' }}{{ stop.arrivalDelay }})
</span> </span>
<span v-else> <span v-else>
{{ timestampToString(stop.arrivalTimestamp) }} {{ timestampToString(stop.arrivalTimestamp) }}
</span> </span>
</span> </span>
<span class="date stop" v-if="stop.stopTime" :class="stop.stopType.replace(', ', '-')"> <span class="date stop" v-if="stop.stopTime || stop.stopped" :class="stop.stopType.replace(', ', '-')">
{{ stop.stopTime }} {{ stop.stopType == '' ? 'pt' : stop.stopType }} {{ stop.stopTime }} {{ stop.stopType == '' ? 'pt' : stop.stopType }}
</span> </span>
<span <span
class="date departure" class="date departure"
v-if="!stop.terminatesHere && stop.stopTime != 0" v-if="!stop.terminatesHere && (stop.stopTime != 0 || stop.stopped)"
:class="{ :class="{
delayed: stop.departureDelay > 0 && stop.confirmed, delayed: stop.departureDelay > 0 && stop.confirmed,
preponed: stop.departureDelay < 0 && stop.confirmed, preponed: stop.departureDelay < 0 && stop.confirmed,
}" }"
> >
<span v-if="stop.departureDelay != 0 && stop.confirmed"> <span v-if="stop.departureDelay != 0 && stop.confirmed">
<s>{{ timestampToString(stop.departureTimestamp) }}</s> <s>{{ timestampToString(stop.departureTimestamp) }}</s>
{{ timestampToString(stop.departureRealTimestamp) }} {{ timestampToString(stop.departureRealTimestamp) }}
({{ stop.departureDelay > 0 ? '+' : '' }}{{ stop.departureDelay }}) ({{ stop.departureDelay > 0 ? '+' : '' }}{{ stop.departureDelay }})
</span> </span>
<span v-else> <span v-else>
{{ timestampToString(stop.departureTimestamp) }} {{ timestampToString(stop.departureTimestamp) }}
</span> </span>
</span> </span>
</span> </span>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import dateMixin from '../../mixins/dateMixin'; import dateMixin from '../../mixins/dateMixin';
import TrainStop from '../../scripts/interfaces/TrainStop'; import TrainStop from '../../scripts/interfaces/TrainStop';
export default defineComponent({ export default defineComponent({
mixins: [dateMixin], mixins: [dateMixin],
props: { props: {
stop: { stop: {
type: Object as () => TrainStop, type: Object as () => TrainStop,
required: true, required: true,
}, },
}, },
setup() { setup() {
return {}; return {};
}, },
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
$preponedClr: lime; $preponedClr: lime;
$delayedClr: salmon; $delayedClr: salmon;
$dateClr: #525151; $dateClr: #525151;
$stopExchangeClr: #db8e29; $stopExchangeClr: #db8e29;
$stopDefaultClr: #252525; $stopDefaultClr: #252525;
.stop-date { .stop-date {
display: flex; display: flex;
align-items: center; align-items: center;
.date { .date {
background: $dateClr; background: $dateClr;
padding: 0.3em 0.5em; padding: 0.3em 0.5em;
} }
.stop { .stop {
&.ph, &.ph,
&.ph-pm, &.ph-pm,
&.pm { &.pm {
background: $stopExchangeClr; background: $stopExchangeClr;
} }
background: $stopDefaultClr; background: $stopDefaultClr;
} }
.arrival, .arrival,
.departure { .departure {
&.delayed { &.delayed {
s { s {
color: #999; color: #999;
} }
span { span {
color: $delayedClr; color: $delayedClr;
} }
} }
&.preponed { &.preponed {
s { s {
color: #999; color: #999;
} }
span { span {
color: $preponedClr; color: $preponedClr;
} }
} }
} }
} }
</style> </style>
+1 -1
View File
@@ -119,7 +119,7 @@
<b class="text--primary">{{ stats.mostActiveDrivers[0].name }}</b> <b class="text--primary">{{ stats.mostActiveDrivers[0].name }}</b>
</template> </template>
<template #distance> <template #distance>
<b class="text--primary">{{ stats.mostActiveDrivers[0].distance }} km</b> <b class="text--primary">{{ stats.mostActiveDrivers[0].distance.toFixed(2) }} km</b>
</template> </template>
</i18n-t> </i18n-t>
</div> </div>
@@ -1,57 +1,106 @@
<template> <template>
<transition-group class="journal-list" tag="ul" name="list-anim"> <div>
<li <transition name="status-anim" mode="out-in">
v-for="item in computedDispatcherHistory" <div :key="dataStatus">
:key="typeof item === 'string' ? item : item.timestampFrom + item.dispatcherId" <div class="journal_warning" v-if="store.isOffline">
:class="{ sticky: typeof item == 'string' }" {{ $t('app.offline') }}
> </div>
<div v-if="typeof item == 'string'" class="journal_day">
{{ item }}
</div>
<div <Loading v-else-if="dataStatus == DataStatus.Loading" />
v-else
class="journal_item" <div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
:class="{ online: item.isOnline }" {{ $t('app.error') }}
@click="navigateToScenery(item.stationName, item.isOnline)" </div>
@keydown.enter="navigateToScenery(item.stationName, item.isOnline)"
tabindex="0" <div class="journal_warning" v-else-if="dispatcherHistory.length == 0">
> {{ $t('app.no-result') }}
<span class="item-general"> </div>
<b
v-if="item.dispatcherLevel !== null" <div v-else>
class="level-badge dispatcher" <table class="scenery-history-table">
:style="calculateExpStyle(item.dispatcherLevel, item.dispatcherIsSupporter)" <thead>
<th>{{ $t('journal.history-name') }}</th>
<th>{{ $t('journal.history-hash') }}</th>
<th>{{ $t('journal.history-dispatcher') }}</th>
<th>{{ $t('journal.history-level') }}</th>
<th>{{ $t('journal.history-rate') }}</th>
<th>{{ $t('journal.history-region') }}</th>
<th>{{ $t('journal.history-date') }}</th>
</thead>
<tbody>
<transition-group name="list-anim">
<tr v-for="historyItem in dispatcherHistory" :key="historyItem.id">
<td>
<router-link :to="`/journal/dispatchers?sceneryName=${historyItem.stationName}`">
<b>{{ historyItem.stationName }}</b>
</router-link>
</td>
<td>#{{ historyItem.stationHash }}</td>
<td>
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
<b>{{ historyItem.dispatcherName }}</b>
</router-link>
</td>
<td>
<b
v-if="historyItem.dispatcherLevel !== null"
class="level-badge dispatcher"
:style="calculateExpStyle(historyItem.dispatcherLevel, historyItem.dispatcherIsSupporter)"
>
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
</b>
</td>
<td class="text--primary">
<b>{{ historyItem.dispatcherRate }}</b>
</td>
<td>
<b class="region-badge" :aria-describedby="historyItem.region">{{
regions.find((r) => r.id == historyItem.region)?.value || '???'
}}</b>
</td>
<td style="min-width: 200px" class="time">
<span v-if="historyItem.timestampTo" class="text--offline">
<b>{{ $d(historyItem.timestampFrom) }}</b>
{{ timestampToString(historyItem.timestampFrom) }}
- {{ timestampToString(historyItem.timestampTo) }} ({{
calculateDuration(historyItem.currentDuration)
}})
</span>
<span class="dispatcher-online" v-else>
<b class="text--online">
<router-link :to="`/scenery?station=${historyItem.stationName}`">{{
$t('journal.online-since')
}}</router-link>
{{ timestampToString(historyItem.timestampFrom) }}
</b>
({{ calculateDuration(historyItem.currentDuration) }})
</span>
</td>
</tr>
</transition-group>
</tbody>
</table>
<button
class="btn btn--option btn--load-data"
v-if="!scrollNoMoreData && scrollDataLoaded && dispatcherHistory.length > 15"
@click="addHistoryData"
> >
{{ item.dispatcherLevel >= 2 ? item.dispatcherLevel : 'L' }} {{ $t('journal.load-data') }}
</b> </button>
</div>
<b class="text--primary">{{ item.dispatcherName }}</b> &bull; <b>{{ item.stationName }}</b>
<span class="text--grayed">&nbsp;#{{ item.stationHash }}&nbsp;</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' }}&nbsp; </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">
&gt;
{{ new Date(item.timestampTo).toLocaleTimeString('pl-PL', { timeStyle: 'short' }) }}
({{ $t('journal.duty-lasted') }} {{ calculateDuration(item.currentDuration!) }})
</span>
</span>
</div> </div>
</li> </transition>
</transition-group>
<div class="journal_warning" v-if="scrollNoMoreData">
{{ $t('journal.no-further-data') }}
</div>
<div class="journal_warning" v-else-if="!scrollDataLoaded">
{{ $t('journal.loading-further-data') }}
</div>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -60,19 +109,47 @@ import dateMixin from '../../mixins/dateMixin';
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData'; import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
import styleMixin from '../../mixins/styleMixin'; import styleMixin from '../../mixins/styleMixin';
import imageMixin from '../../mixins/imageMixin'; import imageMixin from '../../mixins/imageMixin';
import { DataStatus } from '../../scripts/enums/DataStatus';
import { useStore } from '../../store/store';
import Loading from '../Global/Loading.vue';
import { regions } from '../../data/options.json';
export default defineComponent({ export default defineComponent({
components: { Loading },
mixins: [dateMixin, styleMixin, imageMixin],
props: { props: {
dispatcherHistory: { dispatcherHistory: {
type: Array as PropType<DispatcherHistory[]>, type: Array as PropType<DispatcherHistory[]>,
required: true, required: true,
}, },
scrollNoMoreData: {
type: Boolean,
},
scrollDataLoaded: {
type: Boolean,
},
addHistoryData: {
type: Function as PropType<() => void>,
},
dataStatus: {
type: Number as PropType<DataStatus>,
},
}, },
mixins: [dateMixin, styleMixin, imageMixin], data() {
return {
DataStatus,
store: useStore(),
regions,
};
},
computed: { computed: {
computedDispatcherHistory() { computedDispatcherHistory() {
console.log(this.dispatcherHistory.length);
return this.dispatcherHistory.reduce((acc, historyItem, i) => { return this.dispatcherHistory.reduce((acc, historyItem, i) => {
if (this.isAnotherDay(i - 1, i)) acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL')); if (this.isAnotherDay(i - 1, i)) acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL'));
acc.push(historyItem); acc.push(historyItem);
@@ -105,79 +182,58 @@ export default defineComponent({
@import '../../styles/animations.scss'; @import '../../styles/animations.scss';
@import '../../styles/responsive.scss'; @import '../../styles/responsive.scss';
@import '../../styles/badge.scss'; @import '../../styles/badge.scss';
@import '../../styles/JournalSection.scss';
@import '../../styles/variables.scss'; @import '../../styles/variables.scss';
@import '../../styles/JournalSection.scss';
li.sticky { table.scenery-history-table {
position: sticky; --_bg-table: #111;
top: 0; --_bg-head: #101010;
} --_bg-row: #2f2f2f;
.journal_item { width: 100%;
display: flex; border-collapse: collapse;
justify-content: space-between; position: relative;
align-items: center; text-align: center;
flex-wrap: wrap;
text-align: left;
gap: 0.5em 1em; thead {
position: sticky;
line-height: 1.7em; top: 0;
padding: 0.75em; background-color: var(--_bg-head);
&.online {
cursor: pointer;
} }
span[data-status='true'] { th {
padding: 0.5em;
}
tr {
background-color: var(--_bg-row);
border-bottom: 2px solid black;
&:last-child {
border: none;
}
}
td {
padding: 0.75em;
.level-badge {
margin: 0 auto;
}
}
@media screen and (max-width: 550px) {
font-size: 0.9em;
}
}
.text {
&--online {
color: springgreen; color: springgreen;
} }
span[data-status='false'] { &--offline {
color: salmon; color: #ddd;
}
}
.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> </style>
@@ -1,212 +1,250 @@
<template> <template>
<transition-group class="journal-list" tag="ul" name="list-anim"> <div class="journal-list">
<li <transition name="status-anim" mode="out-in">
v-for="{ timetable, stockHistoryComp, stops, showExtraInfo, ...item } in computedTimetableHistory" <div :key="dataStatus">
class="journal_item" <div class="journal_warning" v-if="store.isOffline">
:key="timetable.id" {{ $t('app.offline') }}
@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>&nbsp;{{ timetable.trainNo }}</strong>
</span>
&bull;
<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>
<div class="info-route"> <Loading v-else-if="dataStatus == DataStatus.Loading" />
<b>{{ timetable.route.replace('|', ' - ') }}</b>
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.error') }}
</div> </div>
<hr /> <div v-else-if="timetableHistory.length == 0" class="journal_warning">
{{ $t('app.no-result') }}
<!-- 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">
&gt;
<span v-if="!showExtraInfo.value && i == 1 && stops.length > 2">
... (+{{ stops.length - 2 }}) &gt;
</span>
</span>
<span class="stop-name">{{ stop.stopName }}</span>
<span v-html="stop.html"></span>
</span>
</div> </div>
<!-- Status RJ --> <div v-else>
<div class="info-status" style="margin: 0.5em 0"> <transition-group tag="ul" name="list-anim">
<span>
<b>{{ $t('journal.route-length') }}</b>
{{ !timetable.fulfilled ? timetable.currentDistance + ' /' : '' }}
{{ timetable.routeDistance }} km
</span>
&bull;
<span>
<b>{{ $t('journal.station-count') }}</b>
{{ timetable.confirmedStopsCount }} /
{{ timetable.allStopsCount }}
</span>
<span class="text--grayed" v-if="!timetable.fulfilled && timetable.currentSceneryName">
&bull;
<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]">&lpar;</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]">&rpar;</span>
</b>
</span>
</div>
<!-- Info o autorze RJ -->
<div class="info-author" v-if="timetable.authorName">
<b class="text--grayed">{{ $t('journal.dispatcher-name') }}&nbsp;</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 <li
v-for="(car, i) in (item.currentHistoryIndex.value == 0 v-for="{ timetable, stockHistoryComp, stops, showExtraInfo, ...item } in computedTimetableHistory"
? timetable.stockString class="journal_item"
: stockHistoryComp[item.currentHistoryIndex.value].stockString :key="timetable.id"
).split(';')" @click="showExtraInfo.value = !showExtraInfo.value"
:key="i"
> >
<img <div class="journal_item-info">
@error="onImageError" <div class="info-general">
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${car.split(':')[0]}.png`" <span
:alt="car" class="general-train"
/> tabindex="0"
<div>{{ car.replace(/_/g, ' ').split(':')[0] }}</div> @click.stop="showTimetable(timetable, $event.currentTarget)"
@keydown.enter="showTimetable(timetable, $event.currentTarget)"
>
<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>&nbsp;{{ timetable.trainNo }}</strong>
</span>
&bull;
<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"
>{{
new Date(timetable.createdAt).getTime() - new Date(timetable.beginDate).getTime() < 0
? localeDateTime(timetable.createdAt, $i18n.locale)
: localeDateTime(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" v-if="showExtraInfo.value == true">
<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">
&gt;
<span v-if="!showExtraInfo.value && i == 1 && stops.length > 2">
... (+{{ stops.length - 2 }}) &gt;
</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">
<ProgressBar
:progressPercent="~~((timetable.currentDistance / timetable.routeDistance) * 100)"
:progressType="!timetable.fulfilled && timetable.terminated ? 'abandoned' : ''"
/>
<span>
<span :style="{ color: timetable.fulfilled ? 'lightgreen' : timetable.terminated ? 'salmon' : '' }">
{{ timetable.currentDistance + ' km' }}
</span>
<span> / </span>
<span class="text--primary">{{ timetable.routeDistance }} km</span>
|
<span class="text--grayed">{{ timetable.confirmedStopsCount }}/{{ timetable.allStopsCount }}</span>
</span>
<span class="text--grayed" v-if="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]">&lpar;</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]">&rpar;</span>
</b>
</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.dispatcher-name') }}</span>
<span>{{ timetable.authorName }}</span>
</span>
</div>
<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> </li>
</ul> </transition-group>
<button
class="btn btn--option btn--load-data"
v-if="!scrollNoMoreData && scrollDataLoaded && timetableHistory.length >= 15"
@click="addHistoryData"
>
{{ $t('journal.load-data') }}
</button>
</div> </div>
</div> </div>
</li> </transition>
</transition-group>
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
<div class="journal_warning" v-else-if="!scrollDataLoaded">{{ $t('journal.loading-further-data') }}</div>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -215,17 +253,40 @@ import dateMixin from '../../mixins/dateMixin';
import imageMixin from '../../mixins/imageMixin'; import imageMixin from '../../mixins/imageMixin';
import modalTrainMixin from '../../mixins/modalTrainMixin'; import modalTrainMixin from '../../mixins/modalTrainMixin';
import styleMixin from '../../mixins/styleMixin'; import styleMixin from '../../mixins/styleMixin';
import { DataStatus } from '../../scripts/enums/DataStatus';
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData'; import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
import { useStore } from '../../store/store';
import Loading from '../Global/Loading.vue';
import ProgressBar from '../Global/ProgressBar.vue';
export default defineComponent({ export default defineComponent({
components: { ProgressBar, Loading },
mixins: [dateMixin, imageMixin, modalTrainMixin, styleMixin],
props: { props: {
timetableHistory: { timetableHistory: {
type: Array as PropType<TimetableHistory[]>, type: Array as PropType<TimetableHistory[]>,
required: true, required: true,
}, },
scrollNoMoreData: {
type: Boolean,
},
scrollDataLoaded: {
type: Boolean,
},
addHistoryData: {
type: Function as PropType<() => void>,
},
dataStatus: {
type: Number as PropType<DataStatus>,
},
}, },
mixins: [dateMixin, imageMixin, modalTrainMixin, styleMixin], data() {
return {
DataStatus,
store: useStore(),
};
},
computed: { computed: {
computedTimetableHistory() { computedTimetableHistory() {
@@ -236,7 +297,6 @@ export default defineComponent({
.reverse() .reverse()
.map((h) => { .map((h) => {
const historyData = h.split('@'); const historyData = h.split('@');
return { return {
updatedAt: new Date(Number(historyData[0])).toLocaleTimeString(this.$i18n.locale, { updatedAt: new Date(Number(historyData[0])).toLocaleTimeString(this.$i18n.locale, {
hour: '2-digit', hour: '2-digit',
@@ -247,7 +307,6 @@ export default defineComponent({
stockLength: Number(historyData[3]) || undefined, stockLength: Number(historyData[3]) || undefined,
}; };
}), }),
showExtraInfo: ref(false), showExtraInfo: ref(false),
stops: this.getTimetableStops(timetable), stops: this.getTimetableStops(timetable),
currentHistoryIndex: ref(0), currentHistoryIndex: ref(0),
@@ -273,6 +332,7 @@ export default defineComponent({
return stopNames.map((stopName, i) => { return stopNames.map((stopName, i) => {
const confirmed = i < timetable.confirmedStopsCount; const confirmed = i < timetable.confirmedStopsCount;
if (i == 0) return { stopName, html: beginDateHTML, confirmed }; if (i == 0) return { stopName, html: beginDateHTML, confirmed };
if (i == stopNames.length - 1) return { stopName, html: endDateHTML, confirmed }; if (i == stopNames.length - 1) return { stopName, html: endDateHTML, confirmed };
@@ -281,14 +341,6 @@ export default defineComponent({
const arrivalDateScheduled = this.stringToDate(timetable.checkpointArrivalsScheduled?.at(i)); const arrivalDateScheduled = this.stringToDate(timetable.checkpointArrivalsScheduled?.at(i));
const arrivalDateReal = this.stringToDate(timetable.checkpointArrivals?.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 = const arrivalHTML =
(arrivalDateReal && arrivalDateScheduled && arrivalDateReal?.getTime() != arrivalDateScheduled?.getTime() (arrivalDateReal && arrivalDateScheduled && arrivalDateReal?.getTime() != arrivalDateScheduled?.getTime()
? `<s class="text--grayed">${this.parseDateToTimeString(arrivalDateScheduled)}</s> ` ? `<s class="text--grayed">${this.parseDateToTimeString(arrivalDateScheduled)}</s> `
@@ -302,19 +354,16 @@ export default defineComponent({
: '') + this.parseDateToTimeString(departureDateReal || departureDateScheduled); : '') + this.parseDateToTimeString(departureDateReal || departureDateScheduled);
let html = `${arrivalHTML}${departureHTML ? ` / ${departureHTML}` : ''}`; let html = `${arrivalHTML}${departureHTML ? ` / ${departureHTML}` : ''}`;
if (html) html = ` (${html})`;
if (html) html = ` (${html})`;
return { stopName, html, confirmed }; return { stopName, html, confirmed };
}); });
}, },
showTimetable(timetable: TimetableHistory, target: EventTarget | null) {
if (timetable?.terminated) return;
showTimetable(timetable: TimetableHistory) { this.selectModalTrain(timetable.driverName + timetable.trainNo.toString(), target);
if (!timetable) return;
if (timetable.terminated) return;
this.selectModalTrain(timetable.driverName + timetable.trainNo.toString());
}, },
onImageError(e: Event) { onImageError(e: Event) {
const imageEl = e.target as HTMLImageElement; const imageEl = e.target as HTMLImageElement;
imageEl.src = this.getImage('unknown.png'); imageEl.src = this.getImage('unknown.png');
@@ -377,9 +426,17 @@ hr {
&-extended { &-extended {
margin-top: 0.5em; margin-top: 0.5em;
} }
&-status {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.5em;
}
} }
.general-train { .general-train {
cursor: pointer;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
@@ -421,11 +478,10 @@ ul.stock-list {
} }
} }
// badge.scss
.badges { .badges {
display: flex; display: flex;
gap: 0.25em; gap: 0.25em;
// badge.scss
} }
.stock-history { .stock-history {
@@ -440,14 +496,14 @@ ul.stock-list {
} }
.stop-list { .stop-list {
display: flex; word-wrap: break-word;
flex-wrap: wrap;
gap: 0.25em; gap: 0.25em;
font-size: 0.95em;
color: #adadad; color: #adadad;
&-item[data-confirmed='true'] { &-item[data-confirmed='true'] {
color: #a3eba3; color: lightgreen;
.stop-name { .stop-name {
font-weight: bold; font-weight: bold;
@@ -457,7 +513,6 @@ ul.stock-list {
.btn--show { .btn--show {
display: flex; display: flex;
margin-top: 1em;
font-weight: bold; font-weight: bold;
padding: 0.2em 0.45em; padding: 0.2em 0.45em;
@@ -476,6 +531,10 @@ ul.stock-list {
justify-content: center; justify-content: center;
} }
.info-status {
justify-content: center;
}
.btn--show { .btn--show {
margin: 1em auto 0 auto; margin: 1em auto 0 auto;
} }
@@ -1,21 +1,15 @@
<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>
<table class="scenery-history-table" v-else-if="historyList.length"> <table class="scenery-history-table" v-else="historyList.length">
<thead> <thead>
<!-- <th>{{ $t('scenery.timetables-history-id') }}</th> <th>{{ $t('scenery.dispatchers-history-hash') }}</th>
<th>{{ $t('scenery.timetables-history-number') }}</th> <th>{{ $t('scenery.dispatchers-history-dispatcher') }}</th>
<th>{{ $t('scenery.timetables-history-route') }}</th> <th>{{ $t('scenery.dispatchers-history-level') }}</th>
<th>{{ $t('scenery.timetables-history-driver') }}</th> <th>{{ $t('scenery.dispatchers-history-rate') }}</th>
<th>{{ $t('scenery.timetables-history-author') }}</th> <th>{{ $t('scenery.dispatchers-history-date') }}</th>
<th>{{ $t('scenery.timetables-history-date') }}</th> -->
<th>Hash</th>
<th>Dyżurny</th>
<th>Poziom</th>
<th>Ocena</th>
<th>Data</th>
</thead> </thead>
<tbody> <tbody>
@@ -55,10 +49,13 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div class="no-history" v-else>{{ $t('scenery.history-list-empty') }}</div>
<div ref="bottomDiv"></div>
</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">
@@ -87,36 +84,35 @@ export default defineComponent({
return { return {
historyList: [] as DispatcherHistory[], historyList: [] as DispatcherHistory[],
dataStatus: DataStatus.Loading, dataStatus: DataStatus.Loading,
DataStatus,
}; };
}, },
mounted() { async activated() {
this.mountObserver(this.fireObserverAction, this.$refs['bottomDiv'] as Element); // if (this.historyList.length == 0) {
const fetchedHistory = await this.fetchAPIData();
if (fetchedHistory) this.historyList = fetchedHistory;
// }
}, },
unmounted() {
this.unmountObserver();
},
activated() {
if (this.historyList.length == 0) this.fetchAPIData();
},
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.historyList.push(...historyAPIData);
this.dataStatus = DataStatus.Loaded; this.dataStatus = DataStatus.Loaded;
return historyAPIData;
} catch (error) { } catch (error) {
this.dataStatus = DataStatus.Error;
console.error(error); console.error(error);
return null;
} }
}, },
navigateToHistory() {
fireObserverAction() { this.$router.push(`/journal/dispatchers?sceneryName=${this.station.name}`);
if (this.historyList.length > 0 && this.dataStatus == DataStatus.Loaded)
this.fetchAPIData(this.historyList.length);
}, },
}, },
components: { Loading }, components: { Loading },
+7 -1
View File
@@ -4,7 +4,9 @@
{{ station.name }} {{ station.name }}
</a> </a>
<div class="scenery-abbrev">{{ $t('scenery.abbrev') }} <b>{{ station.generalInfo?.abbr }}</b></div> <div class="scenery-abbrev">
{{ $t('scenery.abbrev') }} <b>{{ station.generalInfo?.abbr }}</b>
</div>
<div class="scenery-hash" v-if="station.onlineInfo?.hash">#{{ station.onlineInfo.hash }}</div> <div class="scenery-hash" v-if="station.onlineInfo?.hash">#{{ station.onlineInfo.hash }}</div>
</section> </section>
@@ -28,6 +30,10 @@ 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; font-size: 3em;
@@ -21,18 +21,11 @@
</span> </span>
</div> </div>
<span class="status-badge" v-if="station.onlineInfo && onlineFrom > 0"> <StationStatusBadge
OD {{ new Date(onlineFrom).toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' }) }} :statusID="station.onlineInfo?.statusID"
</span> :isOnline="station.onlineInfo ? true : false"
:statusTimestamp="station.onlineInfo?.statusTimestamp"
<span class="status-badge" v-if="station.onlineInfo" :class="station.onlineInfo.statusID"> />
{{ $t(`status.${station.onlineInfo.statusID}`) }}
{{ station.onlineInfo.statusID == 'online' ? timestampToString(station.onlineInfo.statusTimestamp) : '' }}
</span>
<span class="status-badge free" v-else>
{{ $t('status.free') }}
</span>
</section> </section>
</template> </template>
@@ -43,20 +36,21 @@ import imageMixin from '../../../mixins/imageMixin';
import routerMixin from '../../../mixins/routerMixin'; import routerMixin from '../../../mixins/routerMixin';
import styleMixin from '../../../mixins/styleMixin'; import styleMixin from '../../../mixins/styleMixin';
import Station from '../../../scripts/interfaces/Station'; import Station from '../../../scripts/interfaces/Station';
import StationStatusBadge from '../../Global/StationStatusBadge.vue';
export default defineComponent({ export default defineComponent({
mixins: [styleMixin, dateMixin, routerMixin, imageMixin], mixins: [styleMixin, dateMixin, routerMixin, imageMixin],
props: { props: {
station: { station: {
type: Object as () => Station, type: Object as () => Station,
default: {}, default: {},
},
onlineFrom: {
type: Number,
default: -1,
},
}, },
components: { StationStatusBadge }
onlineFrom: {
type: Number,
default: -1,
},
},
}); });
</script> </script>
@@ -104,3 +98,4 @@ export default defineComponent({
} }
} }
</style> </style>
@@ -1,133 +1,131 @@
<template> <template>
<section class="info-user-list"> <section class="info-user-list">
<h3 class="user-header section-header"> <h3 class="user-header section-header">
<img :src="getIcon('user')" alt="icon-user" /> <img :src="getIcon('user')" alt="icon-user" />
&nbsp;{{ $t('scenery.users') }} &nbsp; &nbsp;{{ $t('scenery.users') }} &nbsp;
<span class="text--primary">{{ station.onlineInfo?.currentUsers || '0' }}</span <span class="text--primary">{{ station.onlineInfo?.currentUsers || '0' }}</span
>&nbsp;/&nbsp;<span class="text--primary">{{ station.onlineInfo?.maxUsers || '0' }}</span> >&nbsp;/&nbsp;<span class="text--primary">{{ station.onlineInfo?.maxUsers || '0' }}</span>
</h3> </h3>
<div <div
v-for="(train, i) in computedStationTrains" v-for="(train, i) in computedStationTrains"
class="badge user" class="badge user"
:class="train.stopStatus" :class="train.stopStatus"
:key="train.trainId" :key="train.trainId"
tabindex="0" tabindex="0"
@click="selectModalTrain(train.trainId)" @click.prevent="selectModalTrain(train.trainId, $event.currentTarget)"
@keydown.enter="selectModalTrain(train.trainId)" @keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)"
> >
<span class="user_train">{{ train.trainNo }}</span> <span class="user_train">{{ train.trainNo }}</span>
<span class="user_name">{{ train.driverName }}</span> <span class="user_name">{{ train.driverName }}</span>
</div> </div>
<div class="badge user badge-none" v-if="!computedStationTrains || computedStationTrains.length == 0"> <div class="badge user badge-none" v-if="!computedStationTrains || computedStationTrains.length == 0">
{{ $t('scenery.no-users') }} {{ $t('scenery.no-users') }}
</div> </div>
</section> </section>
</template> </template>
<script lang="ts"> <script lang="ts">
import { computed, defineComponent } from 'vue';
import { computed, defineComponent } from 'vue'; import imageMixin from '../../../mixins/imageMixin';
import imageMixin from '../../../mixins/imageMixin'; import modalTrainMixin from '../../../mixins/modalTrainMixin';
import modalTrainMixin from '../../../mixins/modalTrainMixin'; import routerMixin from '../../../mixins/routerMixin';
import routerMixin from '../../../mixins/routerMixin'; import Station from '../../../scripts/interfaces/Station';
import Station from '../../../scripts/interfaces/Station'; import { useStore } from '../../../store/store';
import { useStore } from '../../../store/store';
export default defineComponent({
export default defineComponent({ mixins: [routerMixin, imageMixin, modalTrainMixin],
mixins: [routerMixin, imageMixin, modalTrainMixin],
props: {
props: { station: {
station: { type: Object as () => Station,
type: Object as () => Station, default: {},
default: {}, },
}, },
},
setup(props) {
setup(props) { const store = useStore();
const store = useStore();
const computedStationTrains = computed(() => {
const computedStationTrains = computed(() => { if (!props.station) return [];
if (!props.station) return [];
const station = props.station as Station;
const station = props.station as Station; if (!station.onlineInfo) return [];
if (!station.onlineInfo) return []; if (!station.onlineInfo.stationTrains) return [];
if (!station.onlineInfo.stationTrains) return [];
return station.onlineInfo.stationTrains.map((train) => {
return station.onlineInfo.stationTrains.map((train) => { const scheduledTrainStatus = station.onlineInfo?.scheduledTrains?.find((st) => st.trainNo === train.trainNo);
const scheduledTrainStatus = station.onlineInfo?.scheduledTrains?.find((st) => st.trainNo === train.trainNo);
return {
return { ...train,
...train, stopStatus: scheduledTrainStatus?.stopStatus || 'no-timetable',
stopStatus: scheduledTrainStatus?.stopStatus || 'no-timetable', };
}; });
}); });
});
return { computedStationTrains, store };
return { computedStationTrains, store }; },
}, });
}); </script>
</script>
<style lang="scss" scoped>
<style lang="scss" scoped> $no-timetable: #aaa;
$no-timetable: #aaa; $departed: springgreen;
$departed: springgreen; $stopped: #ffa600;
$stopped: #ffa600; $online: gold;
$online: gold; $terminated: salmon;
$terminated: salmon; $disconnected: slategray;
$disconnected: slategray;
.info-user-list {
.info-user-list { width: 100%;
width: 100%;
ul {
ul { display: flex;
display: flex; flex-wrap: wrap;
flex-wrap: wrap; justify-content: center;
justify-content: center; }
} }
}
.user {
.user { cursor: pointer;
cursor: pointer;
&_train {
&_train { color: black;
color: black; background-color: $no-timetable;
background-color: $no-timetable;
transition: background-color 200ms;
transition: background-color 200ms; -ms-transition: background-color 200ms;
-ms-transition: background-color 200ms; -webkit-transition: background-color 200ms;
-webkit-transition: background-color 200ms; }
}
&.no-timetable .user_train {
&.no-timetable .user_train { background-color: $no-timetable;
background-color: $no-timetable; }
}
&.departed > &_train {
&.departed > &_train { background-color: $departed;
background-color: $departed; }
}
&.stopped > &_train {
&.stopped > &_train { background-color: $stopped;
background-color: $stopped; }
}
&.online > &_train {
&.online > &_train { background-color: $online;
background-color: $online; }
}
&.terminated > &_train {
&.terminated > &_train { background-color: $terminated;
background-color: $terminated; }
}
&.disconnected > &_train {
&.disconnected > &_train { background-color: $disconnected;
background-color: $disconnected; }
}
&.offline {
&.offline { background: firebrick;
background: firebrick; pointer-events: none;
pointer-events: none; }
} }
} </style>
</style>
+32 -78
View File
@@ -67,8 +67,8 @@
v-for="(scheduledTrain, i) in computedScheduledTrains" v-for="(scheduledTrain, i) in computedScheduledTrains"
:key="scheduledTrain.trainId" :key="scheduledTrain.trainId"
tabindex="0" tabindex="0"
@click.prevent.stop="selectModalTrain(scheduledTrain.trainId)" @click.prevent.stop="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
@keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId)" @keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
> >
<span class="timetable-general"> <span class="timetable-general">
<span class="general-info"> <span class="general-info">
@@ -121,25 +121,17 @@
</span> </span>
<span class="schedule-stop"> <span class="schedule-stop">
<span class="stop-time"> <span class="stop-connection">
<span v-if="scheduledTrain.stopInfo.stopTime"> {{ scheduledTrain.arrivingLine }}
{{ scheduledTrain.stopInfo.stopTime }}
{{ scheduledTrain.stopInfo.stopType || 'pt' }}
</span>
<span v-else>&nbsp;</span>
</span> </span>
<span class="arrow"></span> <span class="stop-time">
{{ scheduledTrain.stopInfo.stopTime || '' }}
{{ scheduledTrain.stopInfo.stopTime ? scheduledTrain.stopInfo.stopType || 'pt' : '' }}
</span>
<span class="stop-line"> <span class="stop-connection">
<span> {{ scheduledTrain.departureLine }}
{{ scheduledTrain.arrivingLine }}
</span>
<span></span>
<span>
{{ scheduledTrain.departureLine }}
</span>
</span> </span>
</span> </span>
@@ -341,8 +333,8 @@ export default defineComponent({
max-width: 1100px; max-width: 1100px;
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2em 0.5em; gap: 1.2em 0.5em;
overflow: hidden; overflow: hidden;
@@ -368,7 +360,9 @@ export default defineComponent({
&-schedule { &-schedule {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(30px, 1fr)); grid-template-columns: repeat(3, 1fr);
gap: 0.2em;
align-items: center;
width: 100%; width: 100%;
max-width: 400px; max-width: 400px;
@@ -400,33 +394,6 @@ export default defineComponent({
} }
} }
.arrow {
border: solid white;
border-width: 0 2px 2px 0;
display: inline-block;
padding: 2px;
margin-left: 50px;
position: relative;
transform: rotate(-45deg);
&::before {
content: '';
position: absolute;
display: block;
width: 55px;
height: 3px;
top: 4px;
left: 4px;
transform: translate(-100%, -1px) rotate(45deg);
transform-origin: right bottom;
background: white;
}
}
.general-info { .general-info {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -453,47 +420,34 @@ export default defineComponent({
.schedule { .schedule {
&-arrival, &-arrival,
&-stop,
&-departure { &-departure {
display: flex;
justify-content: center;
align-items: center;
margin: 0 0.3rem;
font-size: 1.15em; font-size: 1.15em;
} }
&-stop { &-stop {
position: relative; display: grid;
display: flex; grid-template-columns: repeat(3, 1fr);
flex-direction: column; gap: 0.5em;
font-size: 0.9em; align-items: end;
padding: 0.3em 0; .stop-connection {
font-size: 0.95em;
.stop-line {
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 {
position: absolute; position: relative;
transform: translateY(-15px); inline-size: max-content;
align-self: center;
font-size: 0.9em;
color: $accentCol; color: $accentCol;
&::after {
content: '\027F6';
display: block;
font-size: 2.2em;
line-height: 0.65em;
}
} }
} }
} }
@@ -1,8 +1,9 @@
<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>
<table class="scenery-history-table" v-else-if="historyList.length"> <table class="scenery-history-table" v-else>
<thead> <thead>
<th>{{ $t('scenery.timetables-history-id') }}</th> <th>{{ $t('scenery.timetables-history-id') }}</th>
<th>{{ $t('scenery.timetables-history-number') }}</th> <th>{{ $t('scenery.timetables-history-number') }}</th>
@@ -26,7 +27,7 @@
<td> <td>
<router-link <router-link
v-if="historyItem.authorName" v-if="historyItem.authorName"
:to="`/journal/dispatchers?dispatcherName=${historyItem.authorName}`" :to="`/journal/timetables?authorName=${historyItem.authorName}`"
>{{ historyItem.authorName }} >{{ historyItem.authorName }}
</router-link> </router-link>
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i> <i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
@@ -38,10 +39,13 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div class="no-history" v-else>{{ $t('scenery.history-list-empty') }}</div>
<div ref="bottomDiv"></div>
</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">
@@ -69,37 +73,31 @@ export default defineComponent({
return { return {
historyList: [] as TimetableHistory[], historyList: [] as TimetableHistory[],
dataStatus: DataStatus.Loading, dataStatus: DataStatus.Loading,
DataStatus,
}; };
}, },
mounted() { async activated() {
this.mountObserver(this.fireObserverAction, this.$refs['bottomDiv'] as Element); const fetchedHistory = await this.fetchAPIData();
}, if (fetchedHistory) this.historyList = fetchedHistory.timetables;
unmounted() {
this.unmountObserver();
},
activated() {
if (this.historyList.length == 0) this.fetchAPIData();
}, },
methods: { methods: {
async fetchAPIData(countFrom = 0, countLimit = 15) { async fetchAPIData(countFrom = 0, countLimit = 15): Promise<SceneryTimetableHistory | null> {
try { try {
const requestString = `${URLs.stacjownikAPI}/api/getIssuedTimetables?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.historyList.push(...historyAPIData.timetables);
this.dataStatus = DataStatus.Loaded; this.dataStatus = DataStatus.Loaded;
return historyAPIData;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return null;
} }
}, },
fireObserverAction() { navigateToHistory() {
if (this.historyList.length > 0 && this.dataStatus == DataStatus.Loaded) this.$router.push(`/journal/timetables?issuedFrom=${this.station.name}`);
this.fetchAPIData(this.historyList.length);
}, },
}, },
components: { Loading }, components: { Loading },
+41 -43
View File
@@ -1,13 +1,10 @@
<template> <template>
<button <label @dblclick="handleDbClick">
class="btn--action" <input v-model="option.value" type="checkbox" :class="option.section" :name="option.id" />
:class="option.section" <span>
:data-selected="option.value" {{ $t(`filters.${option.id}`) }}
@click="handleLeftClick" </span>
@dblclick="handleDbClick" </label>
>
{{ $t(`filters.${option.id}`) }}
</button>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -36,38 +33,24 @@ export default defineComponent({
}; };
}, },
methods: { watch: {
handleLeftClick() { 'option.value'() {
this.option.value = !this.option.value; this.filterStore.changeFilterValue(this.option.name, !this.option.value);
this.filterStore.lastClickedFilterId = '';
this.filterStore.changeFilterValue({
name: this.option.name,
value: !this.option.value,
});
}, },
},
methods: {
handleDbClick(e: Event) { handleDbClick(e: Event) {
e.preventDefault(); e.preventDefault();
this.filterStore.lastClickedFilterId = this.option.id; this.filterStore.lastClickedFilterId = this.option.id;
this.option.value = true; this.option.value = true;
this.filterStore.changeFilterValue({
name: this.option.name,
value: !this.option.value,
});
this.filterStore.inputs.options this.filterStore.inputs.options
.filter((option) => { .filter((option) => {
return option.section == this.option.section && option.id != this.option.id; return option.section == this.option.section && option.id != this.option.id;
}) })
.forEach((option) => { .forEach((option) => {
this.filterStore.changeFilterValue({
name: option.name,
value: this.option.value,
});
option.value = !this.option.value; option.value = !this.option.value;
}); });
}, },
@@ -76,25 +59,40 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
$realityCol: #e03b07; @import '../../styles/variables.scss';
$accessCol: #e03b07;
$controlCol: #0085ff;
$signalCol: #bf7c00;
$statusCol: #349b32;
$saveCol: #28a826;
$routesCol: #9049c0;
button { label {
padding: 0.25em; position: relative;
border-radius: 0; user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
&:focus-visible { span {
outline: 1px solid white; cursor: pointer;
display: inline-block;
width: 100%;
text-align: center;
padding: 0.25em;
background-color: #444;
} }
&[data-selected='true'] { span:hover {
background-color: forestgreen; background-color: #555;
font-weight: bold; }
input[type='checkbox'] {
cursor: pointer;
position: absolute;
opacity: 0;
&:checked + span {
background-color: forestgreen;
font-weight: bold;
}
&:focus-visible + span {
outline: 1px solid $accentCol;
}
} }
} }
</style> </style>
@@ -4,6 +4,7 @@
<button class="btn--filled btn--image" @click="toggleCard"> <button class="btn--filled btn--image" @click="toggleCard">
<img class="button_icon" :src="getIcon('filter2')" alt="filter icon" /> <img class="button_icon" :src="getIcon('filter2')" alt="filter icon" />
{{ $t('options.filters') }} [F] {{ $t('options.filters') }} [F]
<span class="active-indicator" v-if="!filterStore.areFiltersAtDefault"></span>
</button> </button>
<label for="scenery-search"> <label for="scenery-search">
@@ -29,6 +30,22 @@
<p class="card_info" v-html="$t('filters.desc')"></p> <p class="card_info" v-html="$t('filters.desc')"></p>
<section class="card_options"> <section class="card_options">
<!-- QUICK ACTIONS (TODO) -->
<!-- <div class="quick-actions">
<h3 class="text--primary">{{ $t('filters.sections.quick') }}</h3>
<hr />
<div>
<button class="btn--action" style="width: 100%" @click="filterStore.handleQuickAction('all-available')">
{{ $t('filters.all-available') }}
</button>
<button class="btn--action" style="width: 100%" @click="filterStore.handleQuickAction('all-free')">
{{ $t('filters.all-free') }}
</button>
</div>
</div> -->
<div class="option-section" v-for="section in filterStore.inputs.optionSections"> <div class="option-section" v-for="section in filterStore.inputs.optionSections">
<h3 class="text--primary"> <h3 class="text--primary">
{{ $t(`filters.sections.${section}`) }} {{ $t(`filters.sections.${section}`) }}
@@ -39,7 +56,7 @@
<hr /> <hr />
<div class="section-inputs"> <div class="section-inputs">
<filter-option <FilterOption
v-for="(option, i) in filterStore.inputs.options.filter((o) => o.section == section)" v-for="(option, i) in filterStore.inputs.options.filter((o) => o.section == section)"
:option="option" :option="option"
:key="i" :key="i"
@@ -176,6 +193,10 @@ export default defineComponent({
.filter((s) => s.name.toLocaleLowerCase().includes(this.chosenSearchScenery.toLocaleLowerCase())) .filter((s) => s.name.toLocaleLowerCase().includes(this.chosenSearchScenery.toLocaleLowerCase()))
.sort((s1, s2) => (s1.name > s2.name ? 1 : -1)); .sort((s1, s2) => (s1.name > s2.name ? 1 : -1));
}, },
currentOptionsActive() {
return true;
},
}, },
watch: { watch: {
@@ -204,10 +225,7 @@ export default defineComponent({
handleInput(e: Event) { handleInput(e: Event) {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
this.filterStore.changeFilterValue({ this.filterStore.changeFilterValue(target.name, target.value);
name: target.name,
value: target.value,
});
if (this.saveOptions) StorageManager.setStringValue(target.name, target.value); if (this.saveOptions) StorageManager.setStringValue(target.name, target.value);
}, },
@@ -221,11 +239,7 @@ export default defineComponent({
}, },
changeNumericFilterValue(name: string, value: number, saveToStorage = false) { changeNumericFilterValue(name: string, value: number, saveToStorage = false) {
this.filterStore.changeFilterValue({ this.filterStore.changeFilterValue(name, value);
name,
value,
});
if (this.saveOptions && saveToStorage) StorageManager.setNumericValue(name, value); if (this.saveOptions && saveToStorage) StorageManager.setNumericValue(name, value);
}, },
@@ -426,33 +440,30 @@ export default defineComponent({
} }
} }
.card_options { .option-section h3 {
.option-section h3 { display: flex;
display: flex; align-items: center;
align-items: center; margin-bottom: 0.25em;
margin-bottom: 0.25em;
gap: 0.5em; gap: 0.5em;
button { button {
padding: 0.15em; padding: 0.15em;
color: coral; color: coral;
}
} }
}
.section-inputs { .section-inputs {
display: grid; display: grid;
// flex-wrap: wrap; grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.5em;
// grid-template-rows: repeat(3, 1fr); margin: 1em 0;
gap: 0.5em; }
margin: 1em 0;
// @include smallScreen() { .quick-actions div {
// grid-template-columns: repeat(auto-fit, minmax(8em, 1fr)); display: flex;
// grid-template-rows: auto; margin: 1em 0;
// } gap: 1em;
}
} }
.slider { .slider {
+7 -15
View File
@@ -1,9 +1,5 @@
<template> <template>
<section class="station_table"> <section class="station_table">
<button class="return-btn" @click="scrollToTop" v-if="showReturnButton">
<img :src="icons.arrow" alt="return arrow" />
</button>
<div class="table_wrapper"> <div class="table_wrapper">
<table> <table>
<thead> <thead>
@@ -93,16 +89,11 @@
</td> </td>
<td class="station_status"> <td class="station_status">
<span class="status-badge" :class="station.onlineInfo.statusID" v-if="station.onlineInfo"> <StationStatusBadge
{{ $t(`status.${station.onlineInfo.statusID}`) }} :statusID="station.onlineInfo?.statusID"
{{ :isOnline="station.onlineInfo ? true : false"
station.onlineInfo.statusID == 'online' ? timestampToString(station.onlineInfo.statusTimestamp) : '' :statusTimestamp="station.onlineInfo?.statusTimestamp"
}} />
</span>
<span class="status-badge free" v-else>
{{ $t('status.free') }}
</span>
</td> </td>
<td class="station_dispatcher-name"> <td class="station_dispatcher-name">
@@ -253,6 +244,7 @@ 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'; import { HeadIdsTypes, headIconsIds, headIds } from '../../scripts/data/stationHeaderNames';
import StationStatusBadge from '../Global/StationStatusBadge.vue';
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -262,7 +254,7 @@ export default defineComponent({
}, },
}, },
components: { Loading }, components: { Loading, StationStatusBadge },
mixins: [styleMixin, dateMixin, stationInfoMixin, returnBtnMixin, imageMixin], mixins: [styleMixin, dateMixin, stationInfoMixin, returnBtnMixin, imageMixin],
data: () => ({ data: () => ({
+4 -35
View File
@@ -1,5 +1,5 @@
<template> <template>
<div class="train-info" tabindex="0"> <div class="train-info">
<section class="train-route"> <section class="train-route">
<div class="train_general"> <div class="train_general">
<b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b> <b class="warning-timeout" v-if="train.isTimeout" :title="$t('trains.timeout')">?</b>
@@ -41,13 +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 class="timetable_progress-bar"> <ProgressBar :progressPercent="confirmedPercentage(train.timetableData.followingStops)" />
<span class="bar-bg"></span>
<span
class="bar-fg"
:style="{ width: `${Math.floor(confirmedPercentage(train.timetableData.followingStops))}%` }"
></span>
</span>
<span class="timetable_progress-distance"> <span class="timetable_progress-distance">
&nbsp; {{ currentDistance(train.timetableData.followingStops) }} km / &nbsp; {{ currentDistance(train.timetableData.followingStops) }} km /
@@ -96,6 +90,7 @@ import imageMixin from '../../mixins/imageMixin';
import styleMixin from '../../mixins/styleMixin'; 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';
import ProgressBar from '../Global/ProgressBar.vue';
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -103,14 +98,13 @@ export default defineComponent({
type: Object as () => Train, type: Object as () => Train,
required: true, required: true,
}, },
extended: { extended: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
}, },
mixins: [trainInfoMixin, imageMixin, styleMixin], mixins: [trainInfoMixin, imageMixin, styleMixin],
components: { ProgressBar },
}); });
</script> </script>
@@ -206,31 +200,6 @@ export default defineComponent({
flex-wrap: wrap; flex-wrap: wrap;
} }
.timetable_progress-bar {
position: relative;
width: 6em;
height: 1em;
margin: 0.5em 0;
.bar-fg,
.bar-bg {
position: absolute;
height: 1em;
width: 100%;
left: 0;
}
.bar-fg {
background-color: springgreen;
}
.bar-bg {
background-color: #5b5b5b;
}
}
.timetable_progress-distance { .timetable_progress-distance {
margin-right: 0.25em; margin-right: 0.25em;
} }
+3 -2
View File
@@ -17,8 +17,9 @@
class="train-row" class="train-row"
v-for="train in currentTrains" v-for="train in currentTrains"
:key="train.trainId" :key="train.trainId"
@click.stop="selectModalTrain(train.trainId)" tabindex="0"
@keydown.enter="selectModalTrain(train.trainId)" @click.stop="selectModalTrain(train.trainId, $event.currentTarget)"
@keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)"
> >
<TrainInfo :train="train" /> <TrainInfo :train="train" />
</li> </li>
+23 -3
View File
@@ -146,6 +146,7 @@
"desc": " &bull; Left mouse click: select / unselect chosen filter <br /> &bull; Double left click: unselect all filters but chosen from a <b class='text--primary'>group</b> <br /> &bull; <span style='color: coral'>RESET</span>: reset all filters from a <b class='text--primary'>group</b>", "desc": " &bull; Left mouse click: select / unselect chosen filter <br /> &bull; Double left click: unselect all filters but chosen from a <b class='text--primary'>group</b> <br /> &bull; <span style='color: coral'>RESET</span>: reset all filters from a <b class='text--primary'>group</b>",
"sections": { "sections": {
"quick": "QUICK FILTERS",
"reality": "SCENERY REALITY", "reality": "SCENERY REALITY",
"package-access": "IN-GAME AVAILABILITY", "package-access": "IN-GAME AVAILABILITY",
"access": "GENERAL AVAILABILITY", "access": "GENERAL AVAILABILITY",
@@ -156,6 +157,9 @@
"status": "ONLINE STATUS" "status": "ONLINE STATUS"
}, },
"all-available": "ALL AVAILABLE",
"all-free": "CURRENTLY FREE",
"endingStatus": "ENDS SOON", "endingStatus": "ENDS SOON",
"afkStatus": "AFK", "afkStatus": "AFK",
"noSpaceStatus": "NO SPACE", "noSpaceStatus": "NO SPACE",
@@ -284,7 +288,7 @@
"route-length": "Route length:", "route-length": "Route length:",
"station-count": "Stations:", "station-count": "Stations:",
"dispatcher-name": "Created by", "dispatcher-name": "Author",
"timetable-day": "Timetable created at", "timetable-day": "Timetable created at",
"timetable-active": "ACTIVE", "timetable-active": "ACTIVE",
"timetable-fulfilled": "FULFILLED", "timetable-fulfilled": "FULFILLED",
@@ -335,7 +339,15 @@
"stats-error": "Oops! An unexpected error occurred while trying to fetch statistics! :/", "stats-error": "Oops! An unexpected error occurred while trying to fetch statistics! :/",
"timetable-location-signal": "signal:", "timetable-location-signal": "signal:",
"timetable-location-route": "route:" "timetable-location-route": "route:",
"history-name": "Scenery name",
"history-hash": "Hash",
"history-dispatcher": "Dispatcher",
"history-level": "Level",
"history-rate": "Rate",
"history-region": "Region",
"history-date": "Service date"
}, },
"scenery": { "scenery": {
"users": "PLAYERS ONLINE", "users": "PLAYERS ONLINE",
@@ -370,13 +382,21 @@
"timetables-history-author": "TT author", "timetables-history-author": "TT author",
"timetables-history-date": "Date", "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)", "pragotron-link": "Timetable pallet board (beta)",
"tablice-link": "Timetable summary board (by Thundo)" "tablice-link": "Timetable summary board (by Thundo)",
"bottom-info": "Show full history in the Journal tab"
}, },
"availability": { "availability": {
"title": "Availability", "title": "Availability",
+23 -3
View File
@@ -147,6 +147,7 @@
"desc": " &bull; Kliknięcie: zaznaczenie / odznaczenie filtru <br /> &bull; Podwójne kliknięcie: odznaczenie reszty filtrów z <b class='text--primary'>grupy</b> <br /> &bull; <span style='color: coral'>RESET</span>: zresetowanie filtrów z <b class='text--primary'>grupy</b>", "desc": " &bull; Kliknięcie: zaznaczenie / odznaczenie filtru <br /> &bull; Podwójne kliknięcie: odznaczenie reszty filtrów z <b class='text--primary'>grupy</b> <br /> &bull; <span style='color: coral'>RESET</span>: zresetowanie filtrów z <b class='text--primary'>grupy</b>",
"sections": { "sections": {
"quick": "SZYBKIE FILTRY",
"reality": "FIKCYJNOŚĆ SCENERII", "reality": "FIKCYJNOŚĆ SCENERII",
"package-access": "DOSTĘPNOŚĆ W PACZCE", "package-access": "DOSTĘPNOŚĆ W PACZCE",
"access": "DOSTĘPNOŚĆ OGÓLNA", "access": "DOSTĘPNOŚĆ OGÓLNA",
@@ -157,6 +158,9 @@
"status": "STATUS ONLINE" "status": "STATUS ONLINE"
}, },
"all-available": "WSZYSTKIE DOSTĘPNE",
"all-free": "WSZYSTKIE WOLNE",
"endingStatus": "KOŃCZY", "endingStatus": "KOŃCZY",
"afkStatus": "Z/W", "afkStatus": "Z/W",
"noSpaceStatus": "BRAK MIEJSCA", "noSpaceStatus": "BRAK MIEJSCA",
@@ -295,7 +299,7 @@
"route-length": "Kilometraż:", "route-length": "Kilometraż:",
"station-count": "Stacje:", "station-count": "Stacje:",
"dispatcher-name": "Wystawiony przez dyżurnego", "dispatcher-name": "Autor",
"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",
@@ -338,7 +342,15 @@
"stats-error": "Ups! Wystąpił błąd podczas próby pobrania statystyk! :/", "stats-error": "Ups! Wystąpił błąd podczas próby pobrania statystyk! :/",
"timetable-location-signal": "semafor:", "timetable-location-signal": "semafor:",
"timetable-location-route": "szlak:" "timetable-location-route": "szlak:",
"history-name": "Sceneria",
"history-hash": "Hash",
"history-dispatcher": "Dyżurny",
"history-level": "Poziom",
"history-rate": "Ocena",
"history-region": "Region",
"history-date": "Data służby"
}, },
"scenery": { "scenery": {
"users": "GRACZE ONLINE", "users": "GRACZE ONLINE",
@@ -373,13 +385,21 @@
"timetables-history-author": "Autor RJ", "timetables-history-author": "Autor RJ",
"timetables-history-date": "Data", "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)", "pragotron-link": "Paletowa tablica informacyjna (beta)",
"tablice-link": "Tablica informacyjna zbiorcza (autorstwa Thundo)" "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ść",
+7
View File
@@ -21,6 +21,13 @@ export default defineComponent({
}); });
}, },
localeDateTime(dateString: string, locale: string) {
return new Date(dateString).toLocaleString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
timeStyle: 'short',
dateStyle: 'medium'
});
},
localeTime(dateString: string, locale: string) { localeTime(dateString: string, locale: string) {
return new Date(dateString).toLocaleTimeString(locale == 'pl' ? 'pl-PL' : 'en-GB', { return new Date(dateString).toLocaleTimeString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
hour: '2-digit', hour: '2-digit',
+4 -2
View File
@@ -9,8 +9,10 @@ export default defineComponent({
methods: { methods: {
mountObserver(actionFunction: () => void, target: Element) { mountObserver(actionFunction: () => void, target: Element) {
this.observer = new IntersectionObserver((entries) => { this.observer = new IntersectionObserver((entries) => {
if (entries[0].intersectionRatio > 0) actionFunction(); console.log(entries);
});
if (entries[0].intersectionRatio > 0.5) actionFunction();
}, { threshold: 0.2 });
this.observer.observe(target); this.observer.observe(target);
}, },
+4 -2
View File
@@ -1,4 +1,4 @@
import { defineComponent } from 'vue'; import { Ref, defineComponent } from 'vue';
import { useStore } from '../store/store'; import { useStore } from '../store/store';
export default defineComponent({ export default defineComponent({
@@ -15,15 +15,17 @@ export default defineComponent({
}, },
methods: { methods: {
selectModalTrain(trainId: string) { selectModalTrain(trainId: string, target?: EventTarget | null) {
this.store.chosenModalTrainId = trainId; this.store.chosenModalTrainId = trainId;
document.body.classList.add('no-scroll'); document.body.classList.add('no-scroll');
if (target) this.store.modalLastClickedTarget = target;
}, },
closeModal() { closeModal() {
this.store.chosenModalTrainId = undefined; this.store.chosenModalTrainId = undefined;
setTimeout(() => { setTimeout(() => {
(this.store.modalLastClickedTarget as any)?.focus();
document.body.classList.remove('no-scroll'); document.body.classList.remove('no-scroll');
}, 150); }, 150);
}, },
@@ -7,6 +7,7 @@ export interface DispatcherHistory {
dispatcherLevel: number | null; dispatcherLevel: number | null;
dispatcherRate: number; dispatcherRate: number;
dispatcherIsSupporter: boolean; dispatcherIsSupporter: boolean;
dispatcherStatus?: number;
isOnline: boolean; isOnline: boolean;
lastOnlineTimestamp: number; lastOnlineTimestamp: number;
region: string; region: string;
@@ -6,6 +6,7 @@ import Station from '../Station';
import Train from '../Train'; import Train from '../Train';
import { DispatcherStatsAPIData } from '../api/DispatcherStatsAPIData'; import { DispatcherStatsAPIData } from '../api/DispatcherStatsAPIData';
import { DriverStatsAPIData } from '../api/DriverStatsAPIData'; import { DriverStatsAPIData } from '../api/DriverStatsAPIData';
import { Ref } from 'vue';
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault'; export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
@@ -46,6 +47,7 @@ export interface StoreState {
listenerLaunched: boolean; listenerLaunched: boolean;
blockScroll: boolean; blockScroll: boolean;
modalLastClickedTarget: EventTarget | null;
} }
export interface APIData { export interface APIData {
+1 -1
View File
@@ -2,6 +2,6 @@ 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:3001' ? 'http://localhost:3001'
: 'https://spythere.pl', : 'https://stacjownik.spythere.pl',
stacjownikAPIDev: 'localhost:3000', stacjownikAPIDev: 'localhost:3000',
}; };
+28 -11
View File
@@ -58,10 +58,29 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
}); });
}, },
changeFilterValue(filter: { name: string; value: any }) { // Quick actions (TODO)
this.filters[filter.name] = filter.value; handleQuickAction(actionName: string) {
// switch (actionName) {
// case 'all-available':
// this.resetFilters();
// this.inputs.options
// .filter((option) => /^(free|non-public)/.test(option.id))
// .forEach((option) => (option.value = !option.defaultValue));
// break;
// case 'all-free':
// this.resetFilters();
// this.inputs.options
// .filter((option) => /^(free|occupied)/.test(option.id))
// .forEach((option) => (option.value = !option.defaultValue));
// break;
// default:
// break;
// }
},
if (StorageManager.isRegistered('options_saved')) StorageManager.setValue(filter.name, filter.value); changeFilterValue(name: string, value: any) {
this.filters[name] = value;
if (StorageManager.isRegistered('options_saved')) StorageManager.setValue(name, value);
}, },
resetFilters() { resetFilters() {
@@ -79,14 +98,12 @@ export const useStationFiltersStore = defineStore('stationFiltersStore', {
}, },
resetSectionOptions(section: string) { resetSectionOptions(section: string) {
this.inputs.options.forEach((option) => { this.inputs.options
if (option.section != section) return; .filter((option) => option.section == section)
.forEach((option) => {
option.value = option.defaultValue; option.value = option.defaultValue;
this.filters[option.id] = !option.defaultValue; StorageManager.setBooleanValue(option.name, !option.defaultValue);
});
StorageManager.setBooleanValue(option.name, !option.defaultValue);
});
}, },
changeSorter(headerName: HeadIdsTypes) { changeSorter(headerName: HeadIdsTypes) {
+2 -3
View File
@@ -58,6 +58,7 @@ export const useStore = defineStore('store', {
blockScroll: false, blockScroll: false,
listenerLaunched: false, listenerLaunched: false,
modalLastClickedTarget: null
} as StoreState), } as StoreState),
actions: { actions: {
@@ -286,9 +287,7 @@ export const useStore = defineStore('store', {
}, },
async fetchStationsGeneralInfo() { async fetchStationsGeneralInfo() {
const sceneryData: StationJSONData[] = await ( const sceneryData: StationJSONData[] = await (await axios.get(`${URLs.stacjownikAPI}/api/getSceneries`)).data;
await axios.get(`${URLs.stacjownikAPI}/api/getSceneries?timestamp=${Math.floor(Date.now() / 1800000)}`)
).data;
if (!sceneryData) { if (!sceneryData) {
this.dataStatuses.sceneries = DataStatus.Error; this.dataStatuses.sceneries = DataStatus.Error;
+22 -2
View File
@@ -46,13 +46,33 @@
} }
.region-badge { .region-badge {
padding: 0 0.5em; padding: 0.25em 0.5em;
border-radius: 0.5em; border-radius: 0.5em;
font-weight: bold; font-weight: bold;
color: white;
&.eu { &[aria-describedby='eu'] {
background-color: forestgreen; background-color: forestgreen;
} }
&[aria-describedby='cae'] {
background-color: lightcoral;
color: black;
}
&[aria-describedby='usw'] {
background-color: lightblue;
color: black;
}
&[aria-describedby='us'] {
background-color: lightblue;
color: black;
}
&[aria-describedby='ru'] {
background-color: lightslategray;
}
} }
.train-badge { .train-badge {
-9
View File
@@ -11,15 +11,6 @@
gap: 0.5em; gap: 0.5em;
} }
.filter-button .active-indicator {
width: 7px;
height: 7px;
background-color: lightgreen;
border-radius: 50%;
margin-left: 10px;
}
h1.option-title { h1.option-title {
position: relative; position: relative;
font-size: 1.1em; font-size: 1.1em;
+9
View File
@@ -138,6 +138,15 @@ input {
padding: 0.35em 0; padding: 0.35em 0;
} }
.active-indicator {
width: 7px;
height: 7px;
background-color: lightgreen;
border-radius: 50%;
margin-left: 10px;
}
a { a {
display: inline-block; display: inline-block;
+15
View File
@@ -1,3 +1,9 @@
.scenery-table-section {
position: relative;
height: 100%;
overflow-y: scroll;
}
table.scenery-history-table { table.scenery-history-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
@@ -29,3 +35,12 @@ table.scenery-history-table {
font-size: 1.2em; font-size: 1.2em;
color: #ccc; color: #ccc;
} }
.bottom-info {
display: flex;
justify-content: center;
button {
padding: 0.5em;
}
}
-56
View File
@@ -1,56 +0,0 @@
$free: #8a8a8a;
$ending: #e6c300;
$no-limit: #117fc9;
$unav: #ff3d5d;
$brb: #e6a100;
$no-space: #222;
$taken: #09a116;
$unknown: rgb(185, 60, 60);
.status-badge {
border-radius: 1rem;
font-weight: 500;
padding: 0.2em .55em;
background-color: $taken;
&.free {
background-color: $free;
font-size: 0.95em;
}
&.ending {
background-color: $ending;
color: black;
font-size: 0.9em;
}
&.no-limit {
background-color: $no-limit;
font-size: 0.85em;
}
&.not-signed,
&.unavailable {
background-color: $unav;
font-size: 0.85em;
}
&.brb {
background-color: $brb;
color: black;
font-size: 0.95em;
}
&.no-space {
background-color: $no-space;
color: white;
font-size: 0.85em;
}
&.unknown {
background-color: $unknown;
font-size: 0.95em;
}
}
+18 -44
View File
@@ -6,7 +6,7 @@
<JournalOptions <JournalOptions
@on-search-confirm="fetchHistoryData" @on-search-confirm="fetchHistoryData"
@on-options-reset="resetOptions" @on-options-reset="resetOptions"
@on-refresh-data="fetchHistoryData" @on-refresh-data="fetchHistoryData(true)"
:sorter-option-ids="['timestampFrom', 'duration']" :sorter-option-ids="['timestampFrom', 'duration']"
:data-status="dataStatus" :data-status="dataStatus"
:current-options-active="currentOptionsActive" :current-options-active="currentOptionsActive"
@@ -18,43 +18,13 @@
</div> </div>
<div class="list_wrapper" @scroll="handleScroll"> <div class="list_wrapper" @scroll="handleScroll">
<transition name="status-anim" mode="out-in"> <JournalDispatchersList
<div :key="dataStatus"> :dispatcherHistory="computedHistoryList"
<div class="journal_warning" v-if="store.isOffline"> :addHistoryData="addHistoryData"
{{ $t('app.offline') }} :dataStatus="dataStatus"
</div> :scrollDataLoaded="scrollDataLoaded"
:scrollNoMoreData="scrollNoMoreData"
<Loading v-else-if="dataStatus == DataStatus.Loading" /> />
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.error') }}
</div>
<div class="journal_warning" v-else-if="historyList.length == 0">
{{ $t('app.no-result') }}
</div>
<div v-else>
<JournalDispatchersList :dispatcherHistory="computedHistoryList" />
<button
class="btn btn--option btn--load-data"
v-if="!scrollNoMoreData && scrollDataLoaded && computedHistoryList.length > 15"
@click="addHistoryData"
>
{{ $t('journal.load-data') }}
</button>
</div>
</div>
</transition>
<div class="journal_warning" v-if="scrollNoMoreData">
{{ $t('journal.no-further-data') }}
</div>
<div class="journal_warning" v-else-if="!scrollDataLoaded">
{{ $t('journal.loading-further-data') }}
</div>
</div> </div>
</div> </div>
</section> </section>
@@ -194,8 +164,10 @@ export default defineComponent({
}, },
handleQueries(query: LocationQuery) { handleQueries(query: LocationQuery) {
if ('sceneryName' in query) this.searchersValues['search-station'] = `${query.sceneryName}`; const queryKeys = Object.keys(query);
if ('dispatcherName' in query) this.searchersValues['search-dispatcher'] = `${query.dispatcherName}`;
if (queryKeys.includes('sceneryName')) this.setSearchers('', `${query.sceneryName}`, '');
if (queryKeys.includes('dispatcherName')) this.setSearchers('', '', `${query.dispatcherName}`);
}, },
setSearchers(date: string, station: string, dispatcher: string) { setSearchers(date: string, station: string, dispatcher: string) {
@@ -214,10 +186,10 @@ export default defineComponent({
async addHistoryData() { async addHistoryData() {
this.scrollDataLoaded = false; this.scrollDataLoaded = false;
const countFrom = this.historyList.length; this.countFromIndex = this.historyList.length;
const responseData: DispatcherHistory[] = await ( const responseData: DispatcherHistory[] = await (
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}&countFrom=${countFrom}`) await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}&countFrom=${this.countFromIndex}`)
).data; ).data;
if (!responseData) return; if (!responseData) return;
@@ -231,7 +203,7 @@ export default defineComponent({
this.scrollDataLoaded = true; this.scrollDataLoaded = true;
}, },
async fetchHistoryData() { async fetchHistoryData(reset = false) {
const queries: string[] = []; const queries: string[] = [];
const dispatcher = this.searchersValues['search-dispatcher'].trim(); const dispatcher = this.searchersValues['search-dispatcher'].trim();
@@ -245,7 +217,7 @@ export default defineComponent({
if (station) queries.push(`stationName=${station}`); if (station) queries.push(`stationName=${station}`);
if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`); if (timestampFrom && timestampTo) queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`);
// Z API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance']; // API: const SORT_TYPES = ['allStopsCount', 'endDate', 'beginDate', 'routeDistance'];
if (this.sorterActive.id == 'timestampFrom') queries.push('sortBy=timestampFrom'); if (this.sorterActive.id == 'timestampFrom') queries.push('sortBy=timestampFrom');
else if (this.sorterActive.id == 'duration') queries.push('sortBy=currentDuration'); else if (this.sorterActive.id == 'duration') queries.push('sortBy=currentDuration');
else queries.push('sortBy=timestampFrom'); else queries.push('sortBy=timestampFrom');
@@ -258,6 +230,8 @@ export default defineComponent({
this.currentQueryArray = queries; this.currentQueryArray = queries;
try { try {
if (reset) this.dataStatus = DataStatus.Loading;
const responseData: DispatcherHistory[] = await ( const responseData: DispatcherHistory[] = await (
await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}`) await axios.get(`${DISPATCHERS_API_URL}?${this.currentQuery}`)
).data; ).data;
+12 -33
View File
@@ -21,38 +21,13 @@
</div> </div>
<div class="list_wrapper" @scroll="handleScroll"> <div class="list_wrapper" @scroll="handleScroll">
<transition name="status-anim" mode="out-in"> <JournalTimetablesList
<div :key="dataStatus"> :timetableHistory="timetableHistory"
<div class="journal_warning" v-if="store.isOffline"> :addHistoryData="addHistoryData"
{{ $t('app.offline') }} :dataStatus="dataStatus"
</div> :scrollDataLoaded="scrollDataLoaded"
:scrollNoMoreData="scrollNoMoreData"
<Loading v-else-if="dataStatus == DataStatus.Loading" /> />
<div v-else-if="dataStatus == DataStatus.Error" class="journal_warning error">
{{ $t('app.error') }}
</div>
<div v-else-if="timetableHistory.length == 0" class="journal_warning">
{{ $t('app.no-result') }}
</div>
<div v-else>
<JournalTimetablesList :timetableHistory="timetableHistory" />
<button
class="btn btn--option btn--load-data"
v-if="!scrollNoMoreData && scrollDataLoaded && timetableHistory.length >= 15"
@click="addHistoryData"
>
{{ $t('journal.load-data') }}
</button>
</div>
</div>
</transition>
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
<div class="journal_warning" v-else-if="!scrollDataLoaded">{{ $t('journal.loading-further-data') }}</div>
</div> </div>
</div> </div>
</section> </section>
@@ -190,7 +165,11 @@ export default defineComponent({
}, },
handleQueries(query: LocationQuery) { handleQueries(query: LocationQuery) {
if ('timetableId' in query) this.searchersValues['search-train'] = `#${query.timetableId}`; const queryKeys = Object.keys(query);
if (queryKeys.includes('timetableId')) this.setSearchers('', '', `#${query.timetableId}`, '', '');
if (queryKeys.includes('issuedFrom')) this.setSearchers('', '', '', '', `${query.issuedFrom}`);
if (queryKeys.includes('authorName')) this.setSearchers('', '', '', `${query.authorName}`, '');
}, },
setSearchers(date: string, driver: string, train: string, dispatcher: string, issuedFrom: string) { setSearchers(date: string, driver: string, train: string, dispatcher: string, issuedFrom: string) {
+3 -14
View File
@@ -33,12 +33,7 @@
</div> </div>
<keep-alive> <keep-alive>
<component <component :is="currentViewCompontent" :station="stationInfo" :key="currentViewCompontent"></component>
:is="currentViewCompontent"
:station="stationInfo"
:timetableOnly="timetableOnly"
:key="currentViewCompontent"
></component>
</keep-alive> </keep-alive>
</div> </div>
</div> </div>
@@ -172,12 +167,6 @@ button.back-btn {
} }
} }
.scenery-section {
position: relative;
height: 100%;
overflow-y: scroll;
}
.scenery-wrapper { .scenery-wrapper {
display: grid; display: grid;
grid-template-columns: 4fr 5fr; grid-template-columns: 4fr 5fr;
@@ -214,14 +203,14 @@ button.back-btn {
.scenery-right { .scenery-right {
background: #181818; background: #181818;
padding: 2em 0.5em; padding: 1em 0.5em;
height: 95vh; height: 95vh;
min-height: 550px; min-height: 550px;
max-height: 1000px; max-height: 1000px;
display: grid; display: grid;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr auto;
gap: 1em; gap: 1em;
} }
+1 -4
View File
@@ -14,7 +14,6 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import StorageManager from '../scripts/managers/storageManager';
import StationTable from '../components/StationsView/StationTable.vue'; import StationTable from '../components/StationsView/StationTable.vue';
import StationFilterCard from '../components/StationsView/StationFilterCard.vue'; import StationFilterCard from '../components/StationsView/StationFilterCard.vue';
import SelectBox from '../components/Global/SelectBox.vue'; import SelectBox from '../components/Global/SelectBox.vue';
@@ -44,9 +43,7 @@ export default defineComponent({
computed: { computed: {
computedStationList() { computedStationList() {
const list = this.filterStore.getFilteredStationList(this.store.stationList, this.store.region.id); return this.filterStore.getFilteredStationList(this.store.stationList, this.store.region.id);
return list;
}, },
}, },
+3 -1
View File
@@ -77,7 +77,9 @@ export default defineComponent({
watch([searchedTrain, searchedDriver, sorterActive, filterList], ([sT, sD, sA, fL]) => { watch([searchedTrain, searchedDriver, sorterActive, filterList], ([sT, sD, sA, fL]) => {
const areFiltersActive = fL.some((f, i) => f.isActive !== initTrainFilters[i].isActive); const areFiltersActive = fL.some((f, i) => f.isActive !== initTrainFilters[i].isActive);
currentOptionsActive.value = sT.length > 0 || sD.length > 0 || sA.id != 'distance' || areFiltersActive;
currentOptionsActive.value = sT.length > 0 || sD.length > 0 || sA.id != 'routeDistance' || areFiltersActive;
console.log(sA.id);
}); });
return { return {
+1 -1
View File
@@ -15,7 +15,7 @@ export default defineConfig({
globPatterns: ['**/*.{js,css,html,png,svg,jpg}'], globPatterns: ['**/*.{js,css,html,png,svg,jpg}'],
runtimeCaching: [ runtimeCaching: [
{ {
urlPattern: new RegExp('^https://spythere.pl/api/getSceneries', 'i'), urlPattern: new RegExp('^https://stacjownik.spythere.pl/api/getSceneries', 'i'),
handler: 'NetworkFirst', handler: 'NetworkFirst',
options: { options: {
cacheName: 'sceneries-cache', cacheName: 'sceneries-cache',