Compare commits

...

7 Commits

Author SHA1 Message Date
Spythere 91f4c6bc57 Merge do wersji 1.16.2 2023-07-01 01:25:10 +02:00
Spythere c133eb060b bump: 1.16.2 2023-07-01 01:20:04 +02:00
Spythere 7ffc169d8a hotfix 2023-07-01 01:19:50 +02:00
Spythere 1b85cc5f58 poprawki dziennika rj / dr 2023-06-30 20:50:03 +02:00
Spythere 72ff857fff dodatkowe info o postojach w dzienniku RJ 2023-06-27 02:52:41 +02:00
Spythere 96d64e77fc feature: nieskończona lista historii dr/rj scenerii 2023-06-24 13:46:37 +02:00
Spythere 6ceae3f161 revamp tabeli historii dyżurów 2023-06-23 14:19:28 +02:00
17 changed files with 380 additions and 249 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.16.1", "version": "1.16.2",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+1 -1
View File
@@ -49,7 +49,7 @@ let data = reactive({
{ {
name: 'driver', name: 'driver',
titlePath: 'journal.driver-stats-title', titlePath: 'journal.driver-stats-title',
inactive: true, // inactive: true,
}, },
] as { name: TStatTab; titlePath: string; inactive?: boolean }[], ] as { name: TStatTab; titlePath: string; inactive?: boolean }[],
}); });
@@ -1,10 +1,10 @@
<template> <template>
<transition-group class="journal-list" tag="ul" name="list-anim"> <transition-group class="journal-list" tag="ul" name="list-anim">
<li <li
v-for="{ timetable, sceneryList, stockHistoryComp, ...item } in computedTimetableHistory" v-for="{ timetable, stockHistoryComp, stops, showExtraInfo, ...item } in computedTimetableHistory"
class="journal_item" class="journal_item"
:key="timetable.id" :key="timetable.id"
@click="item.showExtra.value = !item.showExtra.value" @click="showExtraInfo.value = !showExtraInfo.value"
> >
<div class="journal_item-info"> <div class="journal_item-info">
<div class="info-general"> <div class="info-general">
@@ -43,7 +43,7 @@
<span class="general-time"> <span class="general-time">
<b class="info-date">{{ localeDay(timetable.beginDate, $i18n.locale) }}</b> <b class="info-date">{{ localeDay(timetable.beginDate, $i18n.locale) }}</b>
<b <b
class="info-status" class="info-badge"
:class="{ :class="{
fulfilled: timetable.fulfilled, fulfilled: timetable.fulfilled,
terminated: timetable.terminated && !timetable.fulfilled, terminated: timetable.terminated && !timetable.fulfilled,
@@ -67,45 +67,28 @@
<hr /> <hr />
<div class="scenery-list"> <!-- Spis postojów -->
<div class="stop-list">
<span <span
v-for="(scenery, i) in sceneryList.filter((_, i) => v-for="(stop, i) in stops.filter((_, i) => (!showExtraInfo.value ? i == 0 || i == stops.length - 1 : true))"
!item.showExtra.value ? i == 0 || i == sceneryList.length - 1 : true class="stop-list-item"
)" :key="stop.stopName"
:key="scenery.name" :data-confirmed="stop.confirmed"
:class="{ confirmed: scenery.confirmed }"
> >
<span v-if="i > 0"> <span v-if="i > 0">
&gt; &gt;
<span v-if="!item.showExtra.value && i == 1 && sceneryList.length > 2"> <span v-if="!showExtraInfo.value && i == 1 && stops.length > 2">
... (+{{ sceneryList.length - 2 }}) &gt; ... (+{{ stops.length - 2 }}) &gt;
</span> </span>
</span> </span>
{{ scenery.name }}
<!-- Data odjazdu ze stacji początkowej --> <span class="stop-name">{{ stop.stopName }}</span>
<span v-if="i == 0" v-html="scenery.beginDateHTML"></span> <span v-html="stop.html"></span>
<!-- Data przyjazdu do stacji końcowej -->
<span
v-else-if="i == sceneryList.length - 1 || (i == 1 && !item.showExtra.value)"
v-html="scenery.endDateHTML"
></span>
<!-- Data przyjazdu i odjazdu ze stacji pośredniej -->
<span v-if="item.showExtra.value && i > 0 && i < sceneryList.length - 1">
<span v-if="timetable.checkpointArrivals && i < timetable.checkpointArrivals.length">
&lpar;p. {{ localeTime(timetable.checkpointArrivals[i], $i18n.locale)
}}<span v-if="timetable.checkpointDepartures && i < timetable.checkpointDepartures.length">
/ o. {{ localeTime(timetable.checkpointDepartures[i], $i18n.locale) }}</span
>&rpar;
</span>
</span>
</span> </span>
</div> </div>
<!-- Status RJ --> <!-- Status RJ -->
<div style="margin: 0.5em 0"> <div class="info-status" style="margin: 0.5em 0">
<span> <span>
<b>{{ $t('journal.route-length') }}</b> <b>{{ $t('journal.route-length') }}</b>
{{ !timetable.fulfilled ? timetable.currentDistance + ' /' : '' }} {{ !timetable.fulfilled ? timetable.currentDistance + ' /' : '' }}
@@ -122,12 +105,24 @@
<b> <b>
{{ $t(`journal.${timetable.terminated ? 'last-seen-at' : 'currently-at'}`) }} {{ $t(`journal.${timetable.terminated ? 'last-seen-at' : 'currently-at'}`) }}
{{ timetable.currentSceneryName.replace(/.[a-zA-Z0-9]+.sc/, '') }} {{ 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> </b>
</span> </span>
</div> </div>
<!-- Nick dyżurnego --> <!-- Info o autorze RJ -->
<div v-if="timetable.authorName"> <div class="info-author" v-if="timetable.authorName">
<b class="text--grayed">{{ $t('journal.dispatcher-name') }}&nbsp;</b> <b class="text--grayed">{{ $t('journal.dispatcher-name') }}&nbsp;</b>
<router-link class="dispatcher-link" :to="`/journal/dispatchers?dispatcherName=${timetable.authorName}`"> <router-link class="dispatcher-link" :to="`/journal/dispatchers?dispatcherName=${timetable.authorName}`">
<b>{{ timetable.authorName }}</b> <b>{{ timetable.authorName }}</b>
@@ -144,11 +139,11 @@
<button class="btn--option btn--show"> <button class="btn--option btn--show">
{{ $t('journal.stock-info') }} {{ $t('journal.stock-info') }}
<img :src="getIcon(`arrow-${item.showExtra.value ? 'asc' : 'desc'}`)" alt="Arrow" /> <img :src="getIcon(`arrow-${showExtraInfo.value ? 'asc' : 'desc'}`)" alt="Arrow" />
</button> </button>
<!-- Dodatkowe informacje --> <!-- Dodatkowe informacje -->
<div class="info-extended" v-if="timetable.stockString && timetable.stockMass && item.showExtra.value"> <div class="info-extended" v-if="timetable.stockString && timetable.stockMass && showExtraInfo.value">
<hr /> <hr />
<div class="stock-specs"> <div class="stock-specs">
@@ -180,6 +175,7 @@
</span> </span>
</div> </div>
<!-- Historia zmian w składzie -->
<div class="stock-history" v-if="stockHistoryComp.length > 1"> <div class="stock-history" v-if="stockHistoryComp.length > 1">
<button <button
class="btn--action" class="btn--action"
@@ -235,7 +231,6 @@ export default defineComponent({
computedTimetableHistory() { computedTimetableHistory() {
return this.timetableHistory.map((timetable) => ({ return this.timetableHistory.map((timetable) => ({
timetable, timetable,
sceneryList: this.getSceneryList(timetable),
stockHistoryComp: timetable.stockHistory stockHistoryComp: timetable.stockHistory
.slice() .slice()
.reverse() .reverse()
@@ -252,38 +247,64 @@ export default defineComponent({
stockLength: Number(historyData[3]) || undefined, stockLength: Number(historyData[3]) || undefined,
}; };
}), }),
showExtra: ref(false),
showExtraInfo: ref(false),
stops: this.getTimetableStops(timetable),
currentHistoryIndex: ref(0), currentHistoryIndex: ref(0),
})); }));
}, },
}, },
methods: { methods: {
getSceneryList(timetable: TimetableHistory) { getTimetableStops(timetable: TimetableHistory) {
return timetable.sceneriesString.split('%').map((name, i) => { const stopNames = timetable.sceneriesString.split('%');
const beginDateHTML =
' (o. ' +
(timetable.beginDate != timetable.scheduledBeginDate
? `<s class='text--grayed'>${this.localeTime(timetable.beginDate, this.$i18n.locale)}</s> `
: '') +
`<span>${this.localeTime(timetable.scheduledBeginDate, this.$i18n.locale)}</span>)`;
const endDateHTML = const beginDateHTML = ` (o. ${
' (p. ' + timetable.beginDate != timetable.scheduledBeginDate
(timetable.endDate != timetable.scheduledEndDate && timetable.fulfilled ? `<s class="text--grayed">${this.localeTime(timetable.beginDate, this.$i18n.locale)}</s>`
? `<s class='text--grayed'>${this.localeTime( : ''
timetable.fulfilled ? timetable.endDate : timetable.scheduledEndDate, } <span>${this.localeTime(timetable.scheduledBeginDate, this.$i18n.locale)}</span>)`;
this.$i18n.locale
)}</s> `
: '') +
`<span>${this.localeTime(
timetable.fulfilled || (timetable.terminated && !timetable.fulfilled)
? timetable.scheduledEndDate
: timetable.endDate,
this.$i18n.locale
)}</span>)`;
return { name, confirmed: i < timetable.confirmedStopsCount, beginDateHTML, endDateHTML }; const endDateHTML = ` (p. ${
timetable.endDate != timetable.scheduledEndDate && timetable.fulfilled
? `<s class="text--grayed">${this.localeTime(timetable.endDate, this.$i18n.locale)}</s>`
: ''
} <span>${this.localeTime(timetable.scheduledEndDate, this.$i18n.locale)}</span>)`;
return stopNames.map((stopName, i) => {
const confirmed = i < timetable.confirmedStopsCount;
if (i == 0) return { stopName, html: beginDateHTML, confirmed };
if (i == stopNames.length - 1) return { stopName, html: endDateHTML, confirmed };
const departureDateScheduled = this.stringToDate(timetable.checkpointDeparturesScheduled?.at(i));
const departureDateReal = this.stringToDate(timetable.checkpointDepartures?.at(i));
const arrivalDateScheduled = this.stringToDate(timetable.checkpointArrivalsScheduled?.at(i));
const arrivalDateReal = this.stringToDate(timetable.checkpointArrivals?.at(i));
// const arrivalDelay =
// arrivalDateReal && arrivalDateScheduled ? arrivalDateReal.getTime() - arrivalDateScheduled.getTime() : 0;
// const departureDelay =
// departureDateReal && departureDateScheduled
// ? departureDateReal.getTime() - departureDateScheduled.getTime()
// : 0;
const arrivalHTML =
(arrivalDateReal && arrivalDateScheduled && arrivalDateReal?.getTime() != arrivalDateScheduled?.getTime()
? `<s class="text--grayed">${this.parseDateToTimeString(arrivalDateScheduled)}</s> `
: '') + this.parseDateToTimeString(arrivalDateReal || arrivalDateScheduled);
const departureHTML =
(departureDateReal &&
departureDateScheduled &&
departureDateReal?.getTime() != departureDateScheduled?.getTime()
? `<s class="text--grayed">${this.parseDateToTimeString(departureDateScheduled)}</s> `
: '') + this.parseDateToTimeString(departureDateReal || departureDateScheduled);
let html = `${arrivalHTML}${departureHTML ? ` / ${departureHTML}` : ''}`;
if (html) html = ` (${html})`;
return { stopName, html, confirmed };
}); });
}, },
@@ -322,7 +343,7 @@ hr {
margin-right: 0.5em; margin-right: 0.5em;
} }
&-status { &-badge {
padding: 0.05em 0.35em; padding: 0.05em 0.35em;
color: black; color: black;
@@ -418,10 +439,19 @@ ul.stock-list {
} }
} }
.scenery-list { .stop-list {
display: flex;
flex-wrap: wrap;
gap: 0.25em;
color: #adadad; color: #adadad;
span.confirmed {
&-item[data-confirmed='true'] {
color: #a3eba3; color: #a3eba3;
.stop-name {
font-weight: bold;
}
} }
} }
@@ -437,17 +467,10 @@ ul.stock-list {
} }
@include smallScreen { @include smallScreen {
.info-general { .journal_item-info {
flex-direction: column;
}
.info-extended {
text-align: center; text-align: center;
} }
.general-train {
justify-content: center;
}
.info-route { .info-route {
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -457,10 +480,9 @@ ul.stock-list {
margin: 1em auto 0 auto; margin: 1em auto 0 auto;
} }
.stock-specs { .info-general,
justify-content: center; .general-train,
} .stock-specs,
.stock-history { .stock-history {
justify-content: center; justify-content: center;
} }
@@ -2,37 +2,62 @@
<section class="scenery-dispatchers-history scenery-section"> <section class="scenery-dispatchers-history scenery-section">
<Loading v-if="dataStatus != 2" /> <Loading v-if="dataStatus != 2" />
<div class="list-warning" v-else-if="dispatcherHistoryList.length == 0">{{ $t('scenery.history-list-empty') }}</div> <table class="scenery-history-table" v-else-if="historyList.length">
<thead>
<!-- <th>{{ $t('scenery.timetables-history-id') }}</th>
<th>{{ $t('scenery.timetables-history-number') }}</th>
<th>{{ $t('scenery.timetables-history-route') }}</th>
<th>{{ $t('scenery.timetables-history-driver') }}</th>
<th>{{ $t('scenery.timetables-history-author') }}</th>
<th>{{ $t('scenery.timetables-history-date') }}</th> -->
<ul class="history-list" v-else> <th>Hash</th>
<li class="list-item" v-for="item in dispatcherHistoryList"> <th>Dyżurny</th>
<router-link class="item-general" :to="`/journal/dispatchers?dispatcherName=${item.dispatcherName}`"> <th>Poziom</th>
<span class="text--grayed">#{{ item.stationHash }}&nbsp;</span> <th>Ocena</th>
<b <th>Data</th>
v-if="item.dispatcherLevel !== null" </thead>
class="level-badge dispatcher"
:style="calculateExpStyle(item.dispatcherLevel, item.dispatcherIsSupporter)"
>
{{ item.dispatcherLevel >= 2 ? item.dispatcherLevel : 'L' }}
</b>
<b>{{ item.dispatcherName }}</b> <tbody>
</router-link> <tr v-for="historyItem in historyList">
<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 style="min-width: 300px">
<div v-if="historyItem.timestampTo">
<b>{{ $d(historyItem.timestampFrom) }}</b>
<div v-if="item.timestampTo"> {{ timestampToString(historyItem.timestampFrom) }}
<b>{{ $d(item.timestampFrom) }}</b> - {{ timestampToString(historyItem.timestampTo) }} ({{ calculateDuration(historyItem.currentDuration) }})
</div>
{{ timestampToString(item.timestampFrom) }} <div class="dispatcher-online" v-else>
- {{ timestampToString(item.timestampTo) }} ({{ calculateDuration(item.currentDuration) }}) {{ $t('journal.online-since') }}
</div> <b>{{ timestampToString(historyItem.timestampFrom) }}</b>
({{ calculateDuration(historyItem.currentDuration) }})
</div>
</td>
</tr>
</tbody>
</table>
<div class="dispatcher-online" v-else> <div class="no-history" v-else>{{ $t('scenery.history-list-empty') }}</div>
{{ $t('journal.online-since') }} <div ref="bottomDiv"></div>
<b>{{ timestampToString(item.timestampFrom) }}</b>
({{ calculateDuration(item.currentDuration) }})
</div>
</li>
</ul>
</section> </section>
</template> </template>
@@ -46,24 +71,35 @@ import Station from '../../scripts/interfaces/Station';
import { URLs } from '../../scripts/utils/apiURLs'; import { URLs } from '../../scripts/utils/apiURLs';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
import styleMixin from '../../mixins/styleMixin'; import styleMixin from '../../mixins/styleMixin';
import listObserverMixin from '../../mixins/listObserverMixin';
export default defineComponent({ export default defineComponent({
name: 'SceneryDispatchersHistory', name: 'SceneryDispatchersHistory',
mixins: [dateMixin, styleMixin], mixins: [dateMixin, styleMixin, listObserverMixin],
props: { props: {
station: { station: {
type: Object as PropType<Station>, type: Object as PropType<Station>,
required: true, required: true,
}, },
}, },
data() { data() {
return { return {
dispatcherHistoryList: [] as DispatcherHistory[], historyList: [] as DispatcherHistory[],
dataStatus: DataStatus.Loading, dataStatus: DataStatus.Loading,
}; };
}, },
mounted() {
this.mountObserver(this.fireObserverAction, this.$refs['bottomDiv'] as Element);
},
unmounted() {
this.unmountObserver();
},
activated() { activated() {
this.fetchAPIData(); if (this.historyList.length == 0) this.fetchAPIData();
}, },
methods: { methods: {
async fetchAPIData(countFrom = 0, countLimit = 30) { async fetchAPIData(countFrom = 0, countLimit = 30) {
@@ -71,12 +107,17 @@ export default defineComponent({
const requestString = `${URLs.stacjownikAPI}/api/getDispatchers?stationName=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`; const requestString = `${URLs.stacjownikAPI}/api/getDispatchers?stationName=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
const historyAPIData: DispatcherHistory[] = await (await axios.get(requestString)).data; const historyAPIData: DispatcherHistory[] = await (await axios.get(requestString)).data;
this.dispatcherHistoryList = historyAPIData; this.historyList.push(...historyAPIData);
this.dataStatus = DataStatus.Loaded; this.dataStatus = DataStatus.Loaded;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}, },
fireObserverAction() {
if (this.historyList.length > 0 && this.dataStatus == DataStatus.Loaded)
this.fetchAPIData(this.historyList.length);
},
}, },
components: { Loading }, components: { Loading },
}); });
@@ -84,30 +125,10 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @import '../../styles/responsive.scss';
@import '../../styles/SceneryView/styles.scss'; @import '../../styles/sceneryViewTables.scss';
.history-list { .level-badge {
padding: 0 0.5em; margin: 0 auto;
}
.list-item {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
text-align: left;
background-color: #353535;
padding: 0.5em;
margin: 0.5em 0;
line-height: 1.5em;
}
.item-general {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.25em;
} }
.dispatcher-online { .dispatcher-online {
@@ -2,7 +2,7 @@
<section class="scenery-timetables-history scenery-section"> <section class="scenery-timetables-history scenery-section">
<Loading v-if="dataStatus != 2" /> <Loading v-if="dataStatus != 2" />
<table v-else-if="sceneryHistoryList.length"> <table class="scenery-history-table" v-else-if="historyList.length">
<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>
@@ -13,7 +13,7 @@
</thead> </thead>
<tbody> <tbody>
<tr v-for="historyItem in sceneryHistoryList"> <tr v-for="historyItem in historyList">
<td> <td>
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">#{{ historyItem.id }}</router-link> <router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">#{{ historyItem.id }}</router-link>
</td> </td>
@@ -39,7 +39,8 @@
</tbody> </tbody>
</table> </table>
<div class="list-warning" v-else>{{ $t('scenery.history-list-empty') }}</div> <div class="no-history" v-else>{{ $t('scenery.history-list-empty') }}</div>
<div ref="bottomDiv"></div>
</section> </section>
</template> </template>
@@ -52,37 +53,54 @@ import { TimetableHistory, SceneryTimetableHistory } from '../../scripts/interfa
import Station from '../../scripts/interfaces/Station'; import Station from '../../scripts/interfaces/Station';
import { URLs } from '../../scripts/utils/apiURLs'; import { URLs } from '../../scripts/utils/apiURLs';
import Loading from '../Global/Loading.vue'; import Loading from '../Global/Loading.vue';
import listObserverMixin from '../../mixins/listObserverMixin';
export default defineComponent({ export default defineComponent({
name: 'SceneryTimetablesHistory', name: 'SceneryTimetablesHistory',
mixins: [dateMixin], mixins: [dateMixin, listObserverMixin],
props: { props: {
station: { station: {
type: Object as PropType<Station>, type: Object as PropType<Station>,
required: true, required: true,
}, },
}, },
data() { data() {
return { return {
sceneryHistoryList: [] as TimetableHistory[], historyList: [] as TimetableHistory[],
dataStatus: DataStatus.Loading, dataStatus: DataStatus.Loading,
}; };
}, },
activated() {
this.fetchAPIData(); mounted() {
this.mountObserver(this.fireObserverAction, this.$refs['bottomDiv'] as Element);
}, },
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) {
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.sceneryHistoryList = historyAPIData.timetables; this.historyList.push(...historyAPIData.timetables);
this.dataStatus = DataStatus.Loaded; this.dataStatus = DataStatus.Loaded;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}, },
fireObserverAction() {
if (this.historyList.length > 0 && this.dataStatus == DataStatus.Loaded)
this.fetchAPIData(this.historyList.length);
},
}, },
components: { Loading }, components: { Loading },
}); });
@@ -90,46 +108,5 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @import '../../styles/responsive.scss';
@import '../../styles/SceneryView/styles.scss'; @import '../../styles/sceneryViewTables.scss';
.list-warning {
padding: 1em 0.5em;
background-color: #444;
font-size: 1.2em;
}
.history-list {
padding: 0 0.5em;
}
table {
width: 100%;
border-collapse: collapse;
thead {
position: sticky;
top: 0;
background-color: #222222;
}
th {
padding: 0.5em;
}
tr {
background-color: #353535;
border: none;
}
td {
padding: 0.75em;
border-bottom: solid 5px #111;
}
}
@include smallScreen {
.list-item {
grid-template-columns: 1fr 1fr;
}
}
</style> </style>
+9 -3
View File
@@ -274,6 +274,7 @@
"title": "DISPATCHER HISTORY", "title": "DISPATCHER HISTORY",
"loading": "Loading dispatcher history data...", "loading": "Loading dispatcher history data...",
"no-history": "No dispatcher history found!", "no-history": "No dispatcher history found!",
"data-refreshed-at": "Data refreshed at",
"section-timetables": "TIMETABLES", "section-timetables": "TIMETABLES",
"section-dispatchers": "DISPATCHERS", "section-dispatchers": "DISPATCHERS",
@@ -291,8 +292,10 @@
"online-since": "ONLINE SINCE", "online-since": "ONLINE SINCE",
"duty-lasted": "The duty lasted", "duty-lasted": "The duty lasted",
"minutes": "{minutes} mins",
"hours": "{hours}h {minutes} mins", "hours": "{value} hour | {value} hours",
"minutes": "{value} min | {value} mins",
"seconds": "{value} s",
"stock-info": "EXTRA INFO", "stock-info": "EXTRA INFO",
"stock-length": "Length", "stock-length": "Length",
@@ -329,7 +332,10 @@
"driver-stats-info": "Enter a proper nickname into filters [F] to see user's driving statistics!", "driver-stats-info": "Enter a proper nickname into filters [F] to see user's driving statistics!",
"stats-loading": "Fetching statistics...", "stats-loading": "Fetching statistics...",
"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-route": "route:"
}, },
"scenery": { "scenery": {
"users": "PLAYERS ONLINE", "users": "PLAYERS ONLINE",
+8 -3
View File
@@ -279,6 +279,7 @@
"title": "HISTORIA DYŻURÓW", "title": "HISTORIA DYŻURÓW",
"loading": "Ładowanie historii dyżurów...", "loading": "Ładowanie historii dyżurów...",
"no-history": "Brak historii dyżurów dla tej scenerii!", "no-history": "Brak historii dyżurów dla tej scenerii!",
"data-refreshed-at": "Dane odświeżone o",
"section-timetables": "ROZKŁADY JAZDY", "section-timetables": "ROZKŁADY JAZDY",
"section-dispatchers": "DYŻURNI", "section-dispatchers": "DYŻURNI",
@@ -288,8 +289,9 @@
"online-since": "ONLINE OD", "online-since": "ONLINE OD",
"duty-lasted": "Dyżur trwał", "duty-lasted": "Dyżur trwał",
"minutes": "{minutes} min.", "hours": "{value} godz.",
"hours": "{hours} godz. {minutes} min.", "minutes": "{value} min.",
"seconds": "{value} sek.",
"route-length": "Kilometraż:", "route-length": "Kilometraż:",
"station-count": "Stacje:", "station-count": "Stacje:",
@@ -333,7 +335,10 @@
"driver-stats-info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki maszynisty!", "driver-stats-info": "Wpisz nazwę użytkownika w filtrach [F], aby zobaczyć jego statystyki maszynisty!",
"stats-loading": "Pobieranie statystyk...", "stats-loading": "Pobieranie statystyk...",
"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-route": "szlak:"
}, },
"scenery": { "scenery": {
"users": "GRACZE ONLINE", "users": "GRACZE ONLINE",
+70 -50
View File
@@ -1,50 +1,70 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
methods: { methods: {
localeDate(dateString: string, locale: string) { localeDate(dateString: string, locale: string) {
return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', { return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
weekday: 'long', weekday: 'long',
day: 'numeric', day: 'numeric',
month: '2-digit', month: '2-digit',
year: 'numeric', year: 'numeric',
hour: '2-digit', hour: '2-digit',
minute: '2-digit', minute: '2-digit',
}); });
}, },
localeDay(dateString: string, locale: string) { localeDay(dateString: string, locale: string) {
return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', { return new Date(dateString).toLocaleDateString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
day: 'numeric', day: 'numeric',
month: '2-digit', month: '2-digit',
year: 'numeric', year: 'numeric',
}); });
}, },
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',
minute: '2-digit', minute: '2-digit',
}); });
}, },
timestampToString(timestamp: number | null) { stringToDate(dateString?: string) {
return timestamp return dateString ? new Date(dateString) : null;
? new Date(timestamp).toLocaleTimeString('pl-PL', { },
hour: '2-digit',
minute: '2-digit', parseDateToTimeString(date: Date | null) {
}) return (
: ''; date?.toLocaleTimeString('pl-PL', {
}, hour: '2-digit',
minute: '2-digit',
calculateDuration(timestampMs: number) { }) || ''
const minsTotal = Math.round(timestampMs / 60000); );
const hoursTotal = Math.floor(minsTotal / 60); },
const minsInHour = minsTotal % 60;
timestampToString(timestamp: number | null) {
return minsTotal > 60 return timestamp
? this.$t('journal.hours', { hours: hoursTotal, minutes: minsInHour }) ? new Date(timestamp).toLocaleTimeString('pl-PL', {
: this.$t('journal.minutes', { minutes: minsTotal }); hour: '2-digit',
}, minute: '2-digit',
}, })
}); : '';
},
calculateDuration(timestampMs: number, showSeconds = false) {
const secondsTotal = Math.floor(timestampMs / 1000);
const minsTotal = Math.round(timestampMs / 60000);
const hoursTotal = Math.floor(minsTotal / 60);
const minsInHour = minsTotal % 60;
return minsTotal >= 60
? `${this.$t('journal.hours', { value: hoursTotal }, hoursTotal)} ${this.$t(
'journal.minutes',
{ value: minsInHour },
minsInHour
)}`
: showSeconds && secondsTotal <= 60
? this.$t('journal.seconds', { value: secondsTotal }, secondsTotal)
: this.$t('journal.minutes', { value: minsTotal }, minsTotal);
},
},
});
+24
View File
@@ -0,0 +1,24 @@
import { defineComponent } from 'vue';
export default defineComponent({
data: () => ({
observer: null as IntersectionObserver | null,
observerTarget: null as Element | null,
}),
methods: {
mountObserver(actionFunction: () => void, target: Element) {
this.observer = new IntersectionObserver((entries) => {
if (entries[0].intersectionRatio > 0) actionFunction();
});
this.observer.observe(target);
},
unmountObserver() {
if (!this.observerTarget) return;
this.observer?.unobserve(this.observerTarget);
},
},
});
@@ -16,6 +16,7 @@ export interface TimetableHistory {
twr: number; twr: number;
skr: number; skr: number;
sceneriesString: string; sceneriesString: string;
currentLocation: string[];
routeDistance: number; routeDistance: number;
currentDistance: number; currentDistance: number;
@@ -52,6 +53,11 @@ export interface TimetableHistory {
checkpointArrivals?: string[]; checkpointArrivals?: string[];
checkpointDepartures?: string[]; checkpointDepartures?: string[];
checkpointArrivalsScheduled?: string[];
checkpointDeparturesScheduled?: string[];
checkpointStopTypes?: string[];
} }
export interface SceneryTimetableHistory { export interface SceneryTimetableHistory {
+13
View File
@@ -23,6 +23,15 @@
padding: 1em 0; padding: 1em 0;
} }
.journal_refreshed-date {
background-color: #333;
color: #ddd;
text-align: end;
padding: 0.25em;
margin: 0.5em 0;
}
.journal_warning { .journal_warning {
text-align: center; text-align: center;
font-size: 1.3em; font-size: 1.3em;
@@ -71,6 +80,10 @@
justify-content: center; justify-content: center;
flex-wrap: wrap; flex-wrap: wrap;
} }
.journal_refreshed-date {
text-align: center;
}
} }
@media (orientation: landscape) { @media (orientation: landscape) {
-11
View File
@@ -1,11 +0,0 @@
.scenery-section {
position: relative;
height: 100%;
overflow-y: scroll;
}
.list-warning {
padding: 1em 0.5em;
background-color: #444;
font-size: 1.2em;
}
-1
View File
@@ -30,7 +30,6 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 0.9em;
&.driver { &.driver {
border-radius: 50%; border-radius: 50%;
+31
View File
@@ -0,0 +1,31 @@
table.scenery-history-table {
width: 100%;
border-collapse: collapse;
thead {
position: sticky;
top: 0;
background-color: #222222;
}
th {
padding: 0.5em;
}
tr {
background-color: #353535;
border: none;
}
td {
padding: 0.75em;
border-bottom: solid 5px #111;
}
}
.no-history {
padding: 1em 0.5em;
background-color: #444;
font-size: 1.2em;
color: #ccc;
}
+6
View File
@@ -13,6 +13,10 @@
optionsType="dispatchers" optionsType="dispatchers"
/> />
<div class="journal_refreshed-date" v-if="dataRefreshedAt">
{{ $t('journal.data-refreshed-at') }}: {{ dataRefreshedAt.toLocaleString($i18n.locale) }}
</div>
<div class="list_wrapper" @scroll="handleScroll"> <div class="list_wrapper" @scroll="handleScroll">
<transition name="status-anim" mode="out-in"> <transition name="status-anim" mode="out-in">
<div :key="dataStatus"> <div :key="dataStatus">
@@ -104,6 +108,7 @@ export default defineComponent({
data: () => ({ data: () => ({
currentQuery: '', currentQuery: '',
currentQueryArray: [] as string[], currentQueryArray: [] as string[],
dataRefreshedAt: null as Date | null,
scrollDataLoaded: true, scrollDataLoaded: true,
scrollNoMoreData: false, scrollNoMoreData: false,
@@ -273,6 +278,7 @@ export default defineComponent({
? this.historyList[0].dispatcherName ? this.historyList[0].dispatcherName
: ''; : '';
this.dataRefreshedAt = new Date();
this.dataStatus = DataStatus.Loaded; this.dataStatus = DataStatus.Loaded;
} catch (error) { } catch (error) {
this.dataStatus = DataStatus.Error; this.dataStatus = DataStatus.Error;
+6
View File
@@ -16,6 +16,10 @@
<JournalStats /> <JournalStats />
<div class="journal_refreshed-date" v-if="dataRefreshedAt">
{{ $t('journal.data-refreshed-at') }}: {{ dataRefreshedAt.toLocaleString($i18n.locale) }}
</div>
<div class="list_wrapper" @scroll="handleScroll"> <div class="list_wrapper" @scroll="handleScroll">
<transition name="status-anim" mode="out-in"> <transition name="status-anim" mode="out-in">
<div :key="dataStatus"> <div :key="dataStatus">
@@ -101,6 +105,7 @@ export default defineComponent({
data: () => ({ data: () => ({
currentQueryParams: {} as TimetablesQueryParams, currentQueryParams: {} as TimetablesQueryParams,
dataRefreshedAt: null as Date | null,
scrollDataLoaded: true, scrollDataLoaded: true,
scrollNoMoreData: false, scrollNoMoreData: false,
@@ -326,6 +331,7 @@ export default defineComponent({
: ''; : '';
this.dataStatus = DataStatus.Loaded; this.dataStatus = DataStatus.Loaded;
this.dataRefreshedAt = new Date();
} catch (error) { } catch (error) {
this.dataStatus = DataStatus.Error; this.dataStatus = DataStatus.Error;
this.dataErrorMessage = 'Ups! Coś poszło nie tak!'; this.dataErrorMessage = 'Ups! Coś poszło nie tak!';
+7 -1
View File
@@ -1,4 +1,4 @@
<template> <template>
<div class="scenery-view"> <div class="scenery-view">
<div class="scenery-offline" v-if="!stationInfo && isComponentVisible && store.dataStatuses.sceneries == 2"> <div class="scenery-offline" v-if="!stationInfo && isComponentVisible && store.dataStatuses.sceneries == 2">
<div>{{ $t('scenery.no-scenery') }}</div> <div>{{ $t('scenery.no-scenery') }}</div>
@@ -172,6 +172,12 @@ 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;