@use '../../styles/dropdown';
@use '../../styles/dropdown-filters';
+
+.filters-options > .dropdown_wrapper {
+ height: calc(100vh - 19em);
+ min-height: 500px;
+}
diff --git a/src/components/JournalView/JournalStats.vue b/src/components/JournalView/JournalStats.vue
index 6c71d0b..7458294 100644
--- a/src/components/JournalView/JournalStats.vue
+++ b/src/components/JournalView/JournalStats.vue
@@ -2,87 +2,70 @@
-
+
+
+
-
-
diff --git a/src/components/JournalView/JournalTimetables/JournalTimetableEntry.vue b/src/components/JournalView/JournalTimetables/JournalTimetableEntry.vue
index 584db44..8540c35 100644
--- a/src/components/JournalView/JournalTimetables/JournalTimetableEntry.vue
+++ b/src/components/JournalView/JournalTimetables/JournalTimetableEntry.vue
@@ -10,14 +10,14 @@
-
+
@@ -28,7 +28,6 @@
import { defineComponent, PropType } from 'vue';
import { API } from '../../../typings/api';
import { useApiStore } from '../../../store/apiStore';
-import { Journal } from '../typings';
import trainCategoryMixin from '../../../mixins/trainCategoryMixin';
import dateMixin from '../../../mixins/dateMixin';
@@ -41,7 +40,7 @@ import EntryDetails from './EntryDetails.vue';
export default defineComponent({
props: {
timetableEntry: {
- type: Object as PropType
,
+ type: Object as PropType,
required: true
},
showExtraInfo: {
@@ -60,74 +59,9 @@ export default defineComponent({
};
},
- computed: {
- timetablePathDetails() {
- if (!this.timetableEntry.path || this.timetableEntry.path == '') return null;
-
- return this.timetableEntry.path.split(';').map((pathEl, i) => {
- const [arrival, name, departure] = pathEl.split(',');
- const sceneryName = name.split(' ').slice(0, -1).join(' ');
- const sceneryHash = name.split(' ').pop()?.replace('.sc', '') ?? '';
-
- return {
- arrival,
- sceneryName,
- sceneryHash,
- departure,
- isVisited: this.timetableEntry.visitedSceneries?.includes(sceneryHash) ?? false
- };
- });
- },
-
- timetableStops(): Journal.TimetableStopDetails[] {
- const timetableEntry = this.timetableEntry;
-
- const stopNames = timetableEntry.sceneriesString.split('%');
-
- return stopNames.reduce((acc, stopName, i, arr) => {
- const arrivalDate =
- i == arr.length - 1
- ? (timetableEntry.checkpointArrivals.at(i) ?? timetableEntry.endDate)
- : timetableEntry.checkpointArrivals.at(i);
-
- const scheduledArrivalDate =
- i == arr.length - 1
- ? (timetableEntry.checkpointArrivalsScheduled.at(i) ?? timetableEntry.scheduledEndDate)
- : timetableEntry.checkpointArrivalsScheduled.at(i);
-
- const departureDate =
- i == 0
- ? (timetableEntry.checkpointDepartures.at(i) ?? timetableEntry.beginDate)
- : timetableEntry.checkpointDepartures.at(i);
-
- const scheduledDepartureDate =
- i == 0
- ? (timetableEntry.checkpointDeparturesScheduled.at(i) ??
- timetableEntry.scheduledBeginDate)
- : timetableEntry.checkpointDeparturesScheduled.at(i);
-
- const stopTime = Number(timetableEntry.checkpointStopTypes.at(i)?.split(',')[0]) || 0;
- const stopType = timetableEntry.checkpointStopTypes.at(i)?.split(',')[1] || '';
-
- acc.push({
- stopName,
- arrivalTimestamp: this.dateStringToTimestamp(arrivalDate),
- scheduledArrivalTimestamp: this.dateStringToTimestamp(scheduledArrivalDate),
- departureTimestamp: this.dateStringToTimestamp(departureDate),
- scheduledDepartureTimestamp: this.dateStringToTimestamp(scheduledDepartureDate),
- stopTime,
- stopType,
- isConfirmed: i < timetableEntry.confirmedStopsCount
- });
-
- return acc;
- }, []);
- }
- },
-
methods: {
- toggleExtraInfo() {
- this.$emit('toggleShowExtraInfo');
+ toggleExtraInfo(data: API.TimetableHistory.Data | null) {
+ this.$emit('toggleShowExtraInfo', data);
}
}
});
@@ -145,7 +79,7 @@ export default defineComponent({
display: flex;
}
-@include responsive.smallScreen{
+@include responsive.smallScreen {
.entry-route {
justify-content: center;
text-align: center;
diff --git a/src/components/JournalView/JournalTimetables/JournalTimetablesList.vue b/src/components/JournalView/JournalTimetables/JournalTimetablesList.vue
index 6cd0662..2bd36f6 100644
--- a/src/components/JournalView/JournalTimetables/JournalTimetablesList.vue
+++ b/src/components/JournalView/JournalTimetables/JournalTimetablesList.vue
@@ -20,7 +20,7 @@
v-for="(timetableEntry, i) in timetableHistory"
:key="timetableEntry.id"
:timetableEntry="timetableEntry"
- :onToggleShowExtraInfo="() => toggleExtraInfo(timetableEntry.id)"
+ :onToggleShowExtraInfo="toggleExtraInfo"
:showExtraInfo="extraInfoIndexes.includes(timetableEntry.id)"
/>
@@ -59,9 +59,11 @@ export default defineComponent({
JournalTimetableEntry
},
+ emits: ['toggleExtraInfo'],
+
props: {
timetableHistory: {
- type: Array as PropType,
+ type: Array as PropType,
required: true
},
scrollNoMoreData: {
@@ -75,32 +77,23 @@ export default defineComponent({
},
dataStatus: {
type: Number as PropType
+ },
+ extraInfoIndexes: {
+ type: Object as PropType,
+ required: true
}
},
data() {
return {
Status,
- store: useMainStore(),
- extraInfoIndexes: [] as number[]
+ store: useMainStore()
};
},
- watch: {
- '$route.query': {
- deep: true,
- handler() {
- this.extraInfoIndexes.length = 0;
- }
- }
- },
-
methods: {
- toggleExtraInfo(id: number) {
- const existingIdx = this.extraInfoIndexes.indexOf(id);
-
- if (existingIdx != -1) this.extraInfoIndexes.splice(existingIdx, 1);
- else this.extraInfoIndexes.push(id);
+ toggleExtraInfo(data: API.TimetableHistory.Data | null) {
+ this.$emit('toggleExtraInfo', data);
}
}
});
@@ -111,7 +104,7 @@ export default defineComponent({
@use '../../../styles/journal-section';
@use '../../../styles/responsive';
-@include responsive.smallScreen{
+@include responsive.smallScreen {
.journal_item-info {
text-align: center;
}
diff --git a/src/components/JournalView/typings.ts b/src/components/JournalView/typings.ts
index b7931a1..a2605cf 100644
--- a/src/components/JournalView/typings.ts
+++ b/src/components/JournalView/typings.ts
@@ -1,5 +1,6 @@
export namespace Journal {
export type DispatcherSearchKey =
+ | 'search-duty-id'
| 'search-dispatcher'
| 'search-station'
| 'search-date-from'
@@ -62,19 +63,6 @@ export namespace Journal {
default: boolean;
}
- export enum StatsTab {
- DRIVER_STATS = 'journal-driver-stats',
- DISPATCHER_STATS = 'journal-dispatcher-stats',
- DAILY_STATS = 'journal-daily-stats'
- }
-
- export interface StatsButton {
- tab: StatsTab;
- localeKey: string;
- iconName: string;
- disabled: boolean;
- }
-
export interface TimetableStopDetails {
stopName: string;
arrivalTimestamp: number;
diff --git a/src/components/PlayerProfileView/ProfileHistoryList.vue b/src/components/PlayerProfileView/ProfileHistoryList.vue
new file mode 100644
index 0000000..24bb03e
--- /dev/null
+++ b/src/components/PlayerProfileView/ProfileHistoryList.vue
@@ -0,0 +1,298 @@
+
+
+
+
+
+
+
+
+ {{ t('profile.list.no-recent-history') }}
+
+
+
+
+
+

+
+

+
+

+
+
+ {{ dateToLocaleString(entry.date, { dateStyle: 'long', timeStyle: 'short' }) }}
+
+ -
+ {{
+ dateToLocaleString(new Date(entry.value.timestampTo), {
+ timeStyle: 'short'
+ })
+ }}
+ {{
+ dateToLocaleString(new Date(entry.value.timestampTo), {
+ dateStyle: 'long',
+ timeStyle: 'short'
+ })
+ }}
+
+
+
+
+
+
+
+ {{ entry.value.trainCategoryCode }}
+
+ {{ ' ' }}
+ {{ entry.value.trainNo }}
+
+ {{ ' ' }} {{ t('profile.list.for') }}: {{ entry.value.driverName }}
+
+ {{ ' ' }}
+ {{ entry.value.route.replace('|', ' > ') }}
+ {{ ' ' }}
+ {{ entry.value.currentDistance }} km
+ / {{ entry.value.routeDistance }} km
+
+
+
+
+ {{ entry.value.stationName }}
+ {{ ' - ' }}
+
+ {{ t('profile.list.online-since') }}:
+ {{
+ humanizeDuration((entry.value.timestampTo || Date.now()) - entry.value.timestampFrom)
+ }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/PlayerProfileView/ProfilePlayerAvatar.vue b/src/components/PlayerProfileView/ProfilePlayerAvatar.vue
new file mode 100644
index 0000000..577305f
--- /dev/null
+++ b/src/components/PlayerProfileView/ProfilePlayerAvatar.vue
@@ -0,0 +1,80 @@
+
+
+
![player image]()
+
+

+
+
+
+
+
+
+
+
diff --git a/src/components/PlayerProfileView/ProfileRecentStats.vue b/src/components/PlayerProfileView/ProfileRecentStats.vue
new file mode 100644
index 0000000..21610f5
--- /dev/null
+++ b/src/components/PlayerProfileView/ProfileRecentStats.vue
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
{{ playerInfo.driverStatsLastMonth.countAll }}
+
+
{{ t('profile.recent-stats.timetables') }}
+
+
+
+
+
+
+ {{ playerInfo.driverStatsLastMonth.currentDistanceTotal?.toFixed(2) || 0 }}
+
+
+
{{ t('profile.recent-stats.distance') }}
+
+
+
+
+
+
+ {{ playerInfo.dispatcherStatsLastMonth.services?.count || 0 }}
+
+
+
{{ t('profile.recent-stats.duties') }}
+
+
+
+
+
+
+ {{ playerInfo.dispatcherStatsLastMonth.issuedTimetables?.count || 0 }}
+
+
+
{{ t('profile.recent-stats.created-timetables') }}
+
+
+
+
+
+
+
+
diff --git a/src/components/PlayerProfileView/ProfileSummary.vue b/src/components/PlayerProfileView/ProfileSummary.vue
new file mode 100644
index 0000000..0f9b629
--- /dev/null
+++ b/src/components/PlayerProfileView/ProfileSummary.vue
@@ -0,0 +1,392 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ playerInfo.driverStats.driverLevel > 1 ? playerInfo.driverStats.driverLevel : 'L'
+ }}
+
+ {{ t('profile.stats.driver') }}
+
+
+
+
+ {{
+ playerInfo.dispatcherStats.dispatcherLevel > 1
+ ? playerInfo.dispatcherStats.dispatcherLevel
+ : 'L'
+ }}
+
+ {{ t('profile.stats.dispatcher') }}
+
+
+
+
+
+
+
+
+
+
+ {{ d.stationName }} ({{ getRegionNameById(d.region) }})
+
+
+
+
+
+
+
+ {{ t.timetable.category }}
+ {{ t.trainNo }}
+ •
+ {{ t.currentStationName }} ({{ getRegionNameById(t.region) }})
+ •
+ {{ t.stockString.split(';')[0] }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ playerInfo.driverStats.countFulfilled }} /
+ {{ playerInfo.driverStats.countAll }} ({{
+ getCountPercentage(
+ playerInfo.driverStats.countFulfilled,
+ playerInfo.driverStats.countAll,
+ 2
+ )
+ }}%)
+
+ - {{ t('profile.stats.fulfilled-timetables') }}
+
+
+
+ {{ playerInfo.driverStats.currentDistanceTotal?.toFixed(2) }} /
+ {{ playerInfo.driverStats.routeDistanceTotal?.toFixed(2) }} ({{
+ getCountPercentage(
+ playerInfo.driverStats.currentDistanceTotal || 0,
+ playerInfo.driverStats.routeDistanceTotal || 0,
+ 2
+ )
+ }}%)
+
+ - {{ t('profile.stats.route-distance') }}
+
+
+
+ {{ playerInfo.driverStats.confirmedStopsTotal }} /
+ {{ playerInfo.driverStats.allStopsTotal }} ({{
+ getCountPercentage(
+ playerInfo.driverStats.confirmedStopsTotal || 0,
+ playerInfo.driverStats.allStopsTotal || 0,
+ 2
+ )
+ }}%)
+
+ - {{ t('profile.stats.confirmed-stops') }}
+
+
+ {{ playerInfo.driverStats.routeDistanceMax || 0 }}km -
+ {{ t('profile.stats.longest-timetable') }}
+
+
+
+ {{ playerInfo.driverStats.routeDistanceAvg?.toFixed(2) || 0 }}km
+
+ - {{ t('profile.stats.avg-timetable-length') }}
+
+
+
+
+ {{ t('profile.stats.no-timetable-stats') }}
+
+
+
+
+
+
+
+
+
+ {{ playerInfo.dispatcherStats.services.count }} -
+ {{ t('profile.stats.duties-count') }}
+
+
+ {{
+ humanizeDuration(playerInfo.dispatcherStats.services.durationMax)
+ }}
+ - {{ t('profile.stats.longest-duty') }}
+
+
+
+
+ {{ playerInfo.dispatcherStats.issuedTimetables.count }}
+ - {{ t('profile.stats.created-timetables-count') }}
+
+
+
+ {{ playerInfo.dispatcherStats.issuedTimetables.distanceMax }}km
+
+ - {{ t('profile.stats.longest-created-timetable') }}
+
+
+
+ {{ playerInfo.dispatcherStats.issuedTimetables.distanceSum.toFixed(2) }}km
+
+ - {{ t('profile.stats.created-timetables-length-sum') }}
+
+
+
+
+ {{ t('profile.stats.no-dispatcher-stats') }}
+
+
+
+
+
+
+
+
+
diff --git a/src/components/SceneryView/SceneryInfo/SceneryInfoDispatcher.vue b/src/components/SceneryView/SceneryInfo/SceneryInfoDispatcher.vue
index 57b20f4..4e1f221 100644
--- a/src/components/SceneryView/SceneryInfo/SceneryInfoDispatcher.vue
+++ b/src/components/SceneryView/SceneryInfo/SceneryInfoDispatcher.vue
@@ -8,10 +8,7 @@
{{ onlineScenery.dispatcherExp > 1 ? onlineScenery.dispatcherExp : 'L' }}
-
+
timetable.beginDate
? new Date(timetable.beginDate)
diff --git a/src/components/StationsView/StationTable.vue b/src/components/StationsView/StationTable.vue
index 6f20a2f..a400a1c 100644
--- a/src/components/StationsView/StationTable.vue
+++ b/src/components/StationsView/StationTable.vue
@@ -132,7 +132,6 @@
@@ -446,7 +445,7 @@ export default defineComponent({
$rowCol: #424242;
.station_table {
- height: calc(100vh - 11em);
+ height: calc(100vh - 17em);
max-height: 2000px;
min-height: 500px;
overflow: auto;
diff --git a/src/components/TrainsView/TrainTable.vue b/src/components/TrainsView/TrainTable.vue
index 6413b3c..28f3cfb 100644
--- a/src/components/TrainsView/TrainTable.vue
+++ b/src/components/TrainsView/TrainTable.vue
@@ -97,7 +97,7 @@ export default defineComponent({
@use '../../styles/animations';
.train-table {
- height: calc(100vh - 11em);
+ height: calc(100vh - 17em);
min-height: 500px;
position: relative;
diff --git a/src/composables/badge.ts b/src/composables/badge.ts
new file mode 100644
index 0000000..43a7e27
--- /dev/null
+++ b/src/composables/badge.ts
@@ -0,0 +1,8 @@
+export function calculateExpStyles(exp: number, isSupporter = false) {
+ const bgColor = exp > -1 ? (exp < 2 ? '#26B0D9' : `hsl(${-exp * 5 + 100}, 85%, 50%)`) : '#666';
+
+ const fontColor = exp > 14 || exp == -1 ? 'white' : 'black';
+ const boxShadow = isSupporter ? `0 0 6px 2px ${bgColor};` : '';
+
+ return { 'background-color': bgColor, color: fontColor, 'box-shadow': boxShadow };
+}
diff --git a/src/composables/time.ts b/src/composables/time.ts
new file mode 100644
index 0000000..edb1eba
--- /dev/null
+++ b/src/composables/time.ts
@@ -0,0 +1,37 @@
+import { useI18n } from 'vue-i18n';
+
+export function calculateDuration(timestampMs: number) {
+ const secondsTotal = Math.floor(timestampMs / 1000);
+ const minsTotal = Math.round(timestampMs / 60000);
+ const hoursTotal = Math.floor(minsTotal / 60);
+ const minsInHour = minsTotal % 60;
+
+ return {
+ secondsTotal,
+ minsTotal,
+ hoursTotal,
+ minsInHour
+ };
+}
+
+export function humanizeDuration(timestampMs: number, showSeconds = false) {
+ const { t } = useI18n();
+
+ const duration = calculateDuration(timestampMs);
+
+ return duration.minsTotal >= 60
+ ? `${t('journal.hours', { value: duration.hoursTotal }, duration.hoursTotal)} ${t(
+ 'journal.minutes',
+ { value: duration.minsInHour },
+ duration.minsInHour
+ )}`
+ : showSeconds && duration.secondsTotal <= 60
+ ? t('journal.seconds', { value: duration.secondsTotal }, duration.secondsTotal)
+ : t('journal.minutes', { value: duration.minsTotal }, duration.minsTotal);
+}
+
+export function dateToLocaleString(date: Date, dateOptions: Intl.DateTimeFormatOptions) {
+ const { locale } = useI18n();
+
+ return date.toLocaleString(locale.value == 'pl' ? 'pl-PL' : 'en-GB', dateOptions);
+}
diff --git a/src/locales/en.json b/src/locales/en.json
index 735f1c5..65e8495 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -87,10 +87,8 @@
"tooltip-scenery-offline": "Scenery is offline",
"pojazdownik-link-content": "POJAZDOWNIK",
"language-tooltip-content": "JĘZYK / LANGUAGE",
- "gnr-link-content": "TRAIN ORDERS
GENERATOR"
- },
- "footer": {
- "discord": "Stacjownik Discord server"
+ "gnr-link-content": "TRAIN ORDERS
GENERATOR",
+ "discord-link-content": "STACJOWNIK
DISCORD SERVER"
},
"categories": {
"EI": "domestic express",
@@ -197,6 +195,7 @@
"search-train": "Train no. / #",
"select-driver": "Choose a driver...",
"search-driver": "Driver name",
+ "search-duty-id": "Duty ID",
"search-dispatcher": "Dispatcher name",
"search-station": "Scenery name / #",
"search-author": "Timetable author name",
@@ -428,7 +427,7 @@
"last-seen-ago": "since {minutes} minutes",
"scenery-offline": "Offline ride",
"timeout": "An error occured while trying to refresh SWDR timetable data!",
- "driver-journal-link": "DRIVER JOURNAL",
+ "driver-profile-link": "PLAYER'S PROFILE",
"driver-srjp-link": "SRJP",
"driver-return-link": "RETURN",
"driver-not-found-header": "Train not found! :/",
@@ -619,9 +618,54 @@
"desc-end": "The train terminates here",
"desc-terminated": "The train has been terminated"
},
- "history": {
- "title": "TIMETABLE JOURNAL",
- "search-train": "Train no.",
- "search-driver": "Driver name"
+ "profile": {
+ "journal-button": "PLAYER'S PROFILE",
+ "no-player-found": "Player not found! :/",
+ "return-to-main": "Return to the main site",
+
+ "filters": {
+ "Timetable": "TIMETABLES",
+ "Dispatcher": "DISPATCHER DUTIES",
+ "IssuedTimetable": "ISSUED TIMETABLES"
+ },
+
+ "stats": {
+ "timetables-journal": "TIMETABLE JOURNAL",
+ "dispatchers-journal": "DISPATCHER JOURNAL",
+ "forum-profile": "FORUM PROFILE",
+
+ "driver": "DRIVER",
+ "dispatcher": "DISPATCHER",
+
+ "header-driver": "DRIVER'S STATS",
+ "fulfilled-timetables": "fulfilled timetables",
+ "route-distance": "confirmed timetables distance",
+ "confirmed-stops": "confirmed stations in timetables",
+ "longest-timetable": "longest timetable",
+ "avg-timetable-length": "average distance of all timetables",
+ "no-timetable-stats": "This player does not have any registered timetables in Stacjownik!",
+
+ "header-dispatcher": "DISPATCHER'S STATS",
+ "duties-count": "duties as dispatcher",
+ "longest-duty": "longest duty",
+ "created-timetables-count": "issued timetables as dispatcher",
+ "longest-created-timetable": "longest issued timetable",
+ "created-timetables-length-sum": "distance sum of issued timetables",
+ "no-dispatcher-stats": "No registered dispatcher duties in Stacjownik!"
+ },
+
+ "recent-stats": {
+ "header": "ACTIVITY STATISTICS (30 LAST DAYS)",
+ "timetables": "TIMETABLES",
+ "distance": "MADE KILOMETERS",
+ "duties": "DISPATCHER DUTIES",
+ "created-timetables": "ISSUED TIMETABLES"
+ },
+
+ "list": {
+ "for": "for",
+ "online-since": "online since",
+ "no-recent-history": "No recent activity in the simulator :("
+ }
}
}
diff --git a/src/locales/pl.json b/src/locales/pl.json
index 5d0f90e..adce89b 100644
--- a/src/locales/pl.json
+++ b/src/locales/pl.json
@@ -83,10 +83,8 @@
"tooltip-scenery-offline": "Sceneria offline",
"pojazdownik-link-content": "POJAZDOWNIK",
"language-tooltip-content": "JĘZYK / LANGUAGE",
- "gnr-link-content": "GENERATOR
ROZKAZÓW PISEMNYCH"
- },
- "footer": {
- "discord": "Serwer Discord Stacjownika"
+ "gnr-link-content": "GENERATOR
ROZKAZÓW PISEMNYCH",
+ "discord-link-content": "SERWER DISCORD
STACJOWNIKA"
},
"categories": {
"EI": "ekspres krajowy",
@@ -193,6 +191,7 @@
"search-train": "Nr pociągu / #",
"search-driver": "Nick maszynisty",
"select-driver": "Wybierz maszynistę...",
+ "search-duty-id": "ID służby",
"search-dispatcher": "Nick dyżurnego",
"search-station": "Nazwa scenerii / #",
"search-author": "Nick autora rozkładu jazdy",
@@ -414,7 +413,7 @@
"last-seen-ago": "od {minutes} minut",
"scenery-offline": "Przejazd offline",
"timeout": "Wystąpił problem z aktualizacją rozkładów jazdy z SWDR",
- "driver-journal-link": "DZIENNIK MASZYNISTY",
+ "driver-profile-link": "PROFIL GRACZA",
"driver-srjp-link": "SRJP",
"driver-return-link": "POWRÓT",
"driver-not-found-header": "Nie znaleziono pociągu! :/",
@@ -604,7 +603,54 @@
"desc-end": "Pociąg kończy bieg",
"desc-terminated": "Pociąg zakończył bieg"
},
- "history": {
- "title": "DZIENNIK ROZKŁADÓW JAZDY"
+ "profile": {
+ "journal-button": "PROFIL GRACZA",
+ "no-player-found": "Nie znaleziono gracza! :/",
+ "return-to-main": "Powrót do strony głównej",
+
+ "filters": {
+ "Timetable": "ROZKŁADY JAZDY",
+ "Dispatcher": "SŁUŻBY DYŻURNEGO",
+ "IssuedTimetable": "WYSTAWIONE RJ"
+ },
+
+ "stats": {
+ "timetables-journal": "DZIENNIK RJ",
+ "dispatchers-journal": "DZIENNIK DR",
+ "forum-profile": "PROFIL FORUM",
+
+ "driver": "MASZYNISTA",
+ "dispatcher": "DYŻURNY RUCHU",
+
+ "header-driver": "STATYSTYKI MASZYNISTY",
+ "fulfilled-timetables": "wypełnione rozkłady jazdy",
+ "route-distance": "zatwierdzony kilometraż w RJ",
+ "confirmed-stops": "potwierdzonych stacji w RJ",
+ "longest-timetable": "najdłuższy rozkład jazdy",
+ "avg-timetable-length": "średnia długość wszystkich rozkładów",
+ "no-timetable-stats": "Ten użytkownik nie posiada statystyk maszynisty zarejestrowanych przez Stacjownik!",
+
+ "header-dispatcher": "STATYSTYKI DYŻURNEGO RUCHU",
+ "duties-count": "służby jako dyżurny ruchu",
+ "longest-duty": "najdłuższa służba",
+ "created-timetables-count": "wystawione RJ jako dyżurny ruchu",
+ "longest-created-timetable": "najdłuższy wystawiony RJ",
+ "created-timetables-length-sum": "suma długości wystawionych RJ",
+ "no-dispatcher-stats": "Ten użytkownik nie posiada statystyk dyżurnego zarejestrowanych przez Stacjownik!"
+ },
+
+ "recent-stats": {
+ "header": "STATYSTYKI AKTYWNOŚCI (30 DNI)",
+ "timetables": "ROZKŁADÓW JAZDY",
+ "distance": "POKONANYCH KILOMETRÓW",
+ "duties": "SŁUŻB DYŻURNEGO",
+ "created-timetables": "WYSTAWIONYCH ROZKŁADÓW"
+ },
+
+ "list": {
+ "for": "dla",
+ "online-since": "online od",
+ "no-recent-history": "Brak ostatniej aktywności w symulatorze :("
+ }
}
}
diff --git a/src/router/index.ts b/src/router/index.ts
index 5d628e8..03fff48 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -61,6 +61,11 @@ const routes: Array = [
region: route.query.region
})
},
+ {
+ path: '/profile',
+ name: 'PlayerProfileView',
+ component: () => import('../views/PlayerProfileView.vue')
+ },
{
path: '/:catchAll(.*)',
redirect: '/'
@@ -70,12 +75,12 @@ const routes: Array = [
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
if (
- (to.name == 'SceneryView' || to.name == 'DriverView') &&
+ (to.name == 'SceneryView' || to.name == 'DriverView' || to.name == 'PlayerProfileView') &&
from.name !== to.name &&
from.query['view'] === undefined &&
!savedPosition
)
- return { el: `.app_main`, behavior: 'instant', top: -13 };
+ return { el: `.app_main`, behavior: 'smooth', top: 0 };
if (savedPosition) return savedPosition;
},
diff --git a/src/store/apiStore.ts b/src/store/apiStore.ts
index 3977102..0404bdd 100644
--- a/src/store/apiStore.ts
+++ b/src/store/apiStore.ts
@@ -9,7 +9,8 @@ export const useApiStore = defineStore('apiStore', {
dataStatuses: {
connection: Status.Data.Loading,
sceneries: Status.Data.Loading,
- vehicles: Status.Data.Loading
+ vehicles: Status.Data.Loading,
+ dailyStatsData: Status.Data.Loading
},
activeData: undefined as API.ActiveData.Response | undefined,
@@ -18,6 +19,8 @@ export const useApiStore = defineStore('apiStore', {
donatorsData: [] as API.Donators.Response,
sceneryData: [] as StationJSONData[],
+ dailyStatsData: null as API.DailyStats.Response | null,
+
nextUpdateTime: 0,
nextDataCheckTime: 0,
@@ -65,7 +68,7 @@ export const useApiStore = defineStore('apiStore', {
// Active data fefresh
if (t >= this.nextUpdateTime) {
this.fetchActiveData();
- this.nextUpdateTime = t + 20000;
+ this.nextUpdateTime = t + 31000;
}
window.requestAnimationFrame(this.updateTick);
@@ -119,6 +122,21 @@ export const useApiStore = defineStore('apiStore', {
this.dataStatuses.vehicles = Status.Data.Error;
console.error('Ups! Wystąpił błąd podczas pobierania informacji o pojazdach:', error);
}
+ },
+
+ async fetchDailyStats() {
+ try {
+ const res: API.DailyStats.Response = await (
+ await this.client!.get('api/getDailyStats')
+ ).data;
+
+ this.dailyStatsData = res;
+
+ this.dataStatuses.dailyStatsData = Status.Data.Loaded;
+ } catch (error) {
+ console.error('Ups! Wystąpił błąd podczas pobierania statystyk rozkładów jazdy...');
+ this.dataStatuses.dailyStatsData = Status.Data.Error;
+ }
}
}
});
diff --git a/src/store/mainStore.ts b/src/store/mainStore.ts
index 357b671..d863e9c 100644
--- a/src/store/mainStore.ts
+++ b/src/store/mainStore.ts
@@ -26,20 +26,12 @@ export const useMainStore = defineStore('mainStore', {
isOffline: false,
appUpdate: null,
- dispatcherStatsName: '',
- dispatcherStatsStatus: Status.Data.Initialized,
-
- driverStatsName: '',
- driverStatsData: undefined,
- driverStatsStatus: Status.Data.Initialized,
-
chosenModalTrainId: undefined,
modalLastClickedTarget: null,
currentLocale: 'pl',
- isMigrateInfoCardOpen: false,
- pinnedStationNames: []
+ isMigrateInfoCardOpen: false
}) as MainStoreState,
actions: {
diff --git a/src/store/typings.ts b/src/store/typings.ts
index 2b6f9a2..e7e9305 100644
--- a/src/store/typings.ts
+++ b/src/store/typings.ts
@@ -5,11 +5,6 @@ export interface MainStoreState {
region: { id: string; value: string; name: string };
isOffline: boolean;
appUpdate: { version: string; changelog: string; releaseURL: string } | null;
- dispatcherStatsName: string;
- dispatcherStatsData?: API.DispatcherStats.Response;
- driverStatsName: string;
- driverStatsData?: API.DriverStats.Response;
- driverStatsStatus: Status.Data;
chosenModalTrainId?: string;
modalLastClickedTarget: EventTarget | null;
currentLocale: string;
diff --git a/src/styles/_badge.scss b/src/styles/_badge.scss
index 1aeb6eb..f919df3 100644
--- a/src/styles/_badge.scss
+++ b/src/styles/_badge.scss
@@ -135,3 +135,20 @@
color: black;
}
}
+
+.timetable-status-badge {
+ padding: 0.05em 0.35em;
+ color: black;
+
+ &.terminated {
+ background-color: salmon;
+ }
+
+ &.fulfilled {
+ background-color: lightgreen;
+ }
+
+ &.active {
+ background-color: lightblue;
+ }
+}
\ No newline at end of file
diff --git a/src/styles/_global.scss b/src/styles/_global.scss
index b24ed4d..9103bec 100644
--- a/src/styles/_global.scss
+++ b/src/styles/_global.scss
@@ -9,6 +9,9 @@
--clr-bg2: #1b1b1b;
--clr-bg3: #1d1d1d;
--clr-view-bg: #1a1a1a;
+ --clr-bg-light: #2b2b2b;
+
+ --clr-tile: #181818;
--clr-accent: #1085b3;
--clr-accent2: #ff3d5d;
@@ -23,6 +26,8 @@
--clr-donator: #f7a4ff;
+ --clr-success: springgreen;
+
--no-scroll-padding: 17px;
--max-container-width: 1700px;
@@ -30,9 +35,8 @@
}
::-webkit-scrollbar {
- width: var(--no-scroll-padding);
- height: var(--no-scroll-padding);
- background-color: transparent;
+ // width: var(--no-scroll-padding);
+ // height: var(--no-scroll-padding);
&-track {
background-color: #333;
@@ -49,6 +53,7 @@
body {
background: var(--clr-bg);
+ color-scheme: dark;
margin: 0;
padding: 0;
@@ -331,19 +336,6 @@ a.a-button {
}
@include responsive.smallScreen {
- ::-webkit-scrollbar {
- width: 0.5em;
- height: 0.5em;
-
- &-track {
- background-color: #222;
- }
-
- &-thumb {
- background-color: #777;
- }
- }
-
[data-tooltip]:hover::after,
[data-tooltip]:focus::after {
transform: translate(-50%, 2em);
diff --git a/src/styles/_journal-section.scss b/src/styles/_journal-section.scss
index cffb516..f809b29 100644
--- a/src/styles/_journal-section.scss
+++ b/src/styles/_journal-section.scss
@@ -11,8 +11,8 @@
.list_wrapper {
overflow-y: auto;
- height: calc(100vh - 12.5em);
- min-height: 700px;
+ height: calc(100vh - 21em);
+ min-height: 500px;
margin-top: 0.5em;
position: relative;
diff --git a/src/typings/api.ts b/src/typings/api.ts
index 60ea024..606f1b8 100644
--- a/src/typings/api.ts
+++ b/src/typings/api.ts
@@ -1,3 +1,4 @@
+import { Journal } from '../components/JournalView/typings';
import { Status, Vehicle, VehicleGroup } from './common';
export enum APIDataStatus {
@@ -27,11 +28,22 @@ export namespace API {
}
}
+ export namespace PlayerActivity {
+ export interface Data {
+ dispatcher: API.ActiveSceneries.Data[];
+ driver: API.ActiveTrains.Data | null;
+ }
+
+ export type Response = Data;
+ }
+
export namespace DispatcherHistory {
export type Response = Data[];
export interface Data {
id: number;
+ createdAt: string;
+ updatedAt: string;
currentDuration: number;
dispatcherId: number;
dispatcherName: string;
@@ -52,61 +64,64 @@ export namespace API {
}
export namespace DispatcherStats {
- export interface DistanceStat {
- routeDistance: number | null;
+ export interface Services {
+ count: number;
+ durationMax: number;
+ durationAvg: number;
}
- export interface DurationStat {
- currentDuration: number | null;
+ export interface IssuedTimetables {
+ count: number;
+ distanceMax: number;
+ distanceAvg: number;
+ distanceSum: number;
}
- export interface Count {
- _all: number;
+ export interface Data {
+ dispatcherId: number | null;
+ dispatcherName: string | null;
+ dispatcherLevel: number | null;
+ services: Services | null;
+ issuedTimetables: IssuedTimetables | null;
}
- export interface Response {
- services: {
- count: number;
- durationMax: number;
- durationAvg: number;
- } | null;
-
- issuedTimetables: {
- count: number;
- distanceMax: number;
- distanceAvg: number;
- distanceSum: number;
- } | null;
- }
+ export type Response = Data;
}
export namespace DriverStats {
- export interface SumStats {
- routeDistance: number;
- confirmedStopsCount: number;
- allStopsCount: number;
- currentDistance: number;
+ export interface Data {
+ driverName: string | null;
+ driverId: number | null;
+ driverLevel: number | null;
+ countAll: number;
+ countTerminated: number;
+ countFulfilled: number;
+ routeDistanceTotal: number | null;
+ routeDistanceAvg: number | null;
+ routeDistanceMax: number | null;
+ currentDistanceTotal: number | null;
+ confirmedStopsTotal: number | null;
+ allStopsTotal: number | null;
}
- export interface CountStats {
- fulfilled: number;
- terminated: number;
- _all: number;
- }
+ export type Response = Data;
+ }
- export interface MaxStats {
- routeDistance: number;
+ export namespace PlayerInfo {
+ export interface Data {
+ currentActivity: PlayerActivity.Data;
+ dispatcherStats: DispatcherStats.Data;
+ dispatcherStatsLastMonth: DispatcherStats.Data;
+ driverStats: DriverStats.Data;
+ driverStatsLastMonth: DriverStats.Data;
}
+ }
- export interface AvdStats {
- routeDistance: number;
- }
-
- export interface Response {
- _sum: SumStats;
- _count: CountStats;
- _max: MaxStats;
- _avg: AvdStats;
+ export namespace PlayerJournal {
+ export interface Data {
+ timetables: TimetableHistory.DataShort[];
+ issuedTimetables: TimetableHistory.DataShort[];
+ duties: DispatcherHistory.Data[];
}
}
@@ -211,14 +226,48 @@ export namespace API {
}
export namespace TimetableHistory {
- export interface Data {
+ export interface QueryParams {
+ driverName?: string;
+ trainNo?: string;
+ timetableId?: string;
+ categoryCode?: string;
+
+ authorName?: string;
+
+ dateFrom?: string;
+ dateTo?: string;
+
+ issuedFrom?: string;
+ terminatingAt?: string;
+ via?: string;
+ includesScenery?: string;
+
+ countFrom?: number;
+ countLimit?: number;
+
+ fulfilled?: number;
+ terminated?: number;
+
+ twr?: number;
+ skr?: number;
+ pn?: number;
+ tn?: number;
+
+ returnType?: 'all' | 'short' | 'detailed';
+
+ sortBy?: Journal.TimetableSorter['id'];
+ }
+
+ export interface Data extends DataShort, DataDetailsOnly {
+ updatedAt: string;
+ }
+
+ export interface DataShort {
id: number;
createdAt: string;
- updatedAt: string;
-
- timetableId: number;
trainNo: number;
trainCategoryCode: string;
+ timetableId: number;
driverId: number;
driverName: string;
@@ -229,7 +278,6 @@ export namespace API {
route: string;
twr: number;
skr: number;
- sceneriesString: string;
currentLocation: string[];
routeDistance: number;
@@ -240,7 +288,6 @@ export namespace API {
beginDate: string;
endDate: string;
-
scheduledBeginDate: string;
scheduledEndDate: string;
@@ -250,15 +297,25 @@ export namespace API {
authorName?: string;
authorId?: number;
+ currentSceneryName?: string;
+ currentSceneryHash?: string;
+ hasDangerousCargo: boolean;
+ hasExtraDeliveries: boolean;
+ }
+
+ export interface DataDetailsOnly {
+ id: number;
+ timetableId: number;
+
+ sceneriesString: string;
stockString?: string;
stockHistory: string[];
stockMass?: number;
stockLength?: number;
maxSpeed?: number;
+ trainMaxSpeed?: number;
- currentSceneryName?: string;
- currentSceneryHash?: string;
routeSceneries: string;
checkpointArrivals: string[];
checkpointDepartures: string[];
@@ -268,14 +325,20 @@ export namespace API {
checkpointComments: string[];
visitedSceneries: string[];
sceneryNames: string[];
+
path: string;
warningNotes: string | null;
- hasDangerousCargo: boolean;
- hasExtraDeliveries: boolean;
- trainMaxSpeed?: number;
+
+ authorId?: number;
+ authorName?: string;
+ driverId: number;
+ driverName: string;
+ driverLanguageId: number | null;
}
export type Response = Data[];
+ export type ResponseShort = DataShort[];
+ export type ResponseDetailsOnly = DataDetailsOnly[];
}
export namespace DailyStats {
@@ -427,6 +490,62 @@ export namespace GithubAPI {
}
}
+export namespace Td2API {
+ export namespace UsersInfoByName {
+ export interface UserStat {
+ variable: string;
+ value: number;
+ position: number;
+ server_total: number;
+ server_max: number;
+ server_min: number;
+ server_avg: number;
+ }
+
+ export interface Levels {
+ driver: number;
+ dispatcher: number;
+ }
+
+ export interface UserGroup {
+ id_group: number;
+ group_name: string;
+ description: string;
+ online_color: string;
+ min_posts: number;
+ max_messages: number;
+ stars: string;
+ group_type: number;
+ hidden: number;
+ id_parent: number;
+ }
+
+ export interface UserInfo {
+ id_member: number;
+ id_group: number;
+ additional_groups: string;
+ member_name: string;
+ karma_bad: number;
+ karma_good: number;
+ date_registered: number;
+ last_login: number;
+ avatar: number;
+ lngfile: string;
+ user_stats: UserStat[];
+ levels: Levels;
+ user_groups: UserGroup[];
+ }
+
+ export type Message = UserInfo[];
+
+ export interface Response {
+ success: boolean;
+ respCode: number;
+ message: Message;
+ }
+ }
+}
+
export namespace Websocket {
export interface Payload {
activeSceneries: API.ActiveSceneries.Response;
diff --git a/src/typings/common.ts b/src/typings/common.ts
index 79af5d1..684c030 100644
--- a/src/typings/common.ts
+++ b/src/typings/common.ts
@@ -220,6 +220,7 @@ export interface CheckpointTrain {
export type Vehicle = API.VehiclesData.VehicleObject;
export type VehicleGroup = API.VehiclesData.VehicleGroupObject;
+// Train Tooltip Info
export interface TooltipUserTrain {
driverName: string;
trainNo: number;
@@ -240,4 +241,4 @@ export interface TooltipTrainInfo {
headVehicleName: string;
stockCount: number;
trainTimetableCategory?: string;
-}
+}
\ No newline at end of file
diff --git a/src/utils/calcUtils.ts b/src/utils/calcUtils.ts
new file mode 100644
index 0000000..7541073
--- /dev/null
+++ b/src/utils/calcUtils.ts
@@ -0,0 +1,5 @@
+export function getCountPercentage(partCount: number, allCount: number, fixedDigits: number) {
+ if (allCount == 0) return 0;
+
+ return ((partCount / allCount) * 100).toFixed(fixedDigits);
+}
diff --git a/src/utils/regionUtils.ts b/src/utils/regionUtils.ts
new file mode 100644
index 0000000..2807b58
--- /dev/null
+++ b/src/utils/regionUtils.ts
@@ -0,0 +1,19 @@
+export enum ServerRegion {
+ 'eu' = 'PL1',
+ 'cae' = 'PL2',
+ 'usw' = 'DE',
+ 'us' = 'CZE',
+ 'ru' = 'ENG'
+}
+
+export const regions: Record = {
+ eu: 'PL1',
+ cae: 'PL2',
+ usw: 'DE',
+ us: 'CZE',
+ ru: 'ENG'
+};
+
+export function getRegionNameById(id: string) {
+ return regions[id] ?? 'PL1';
+}
diff --git a/src/views/DriverView.vue b/src/views/DriverView.vue
index 5739d8f..80bf7e9 100644
--- a/src/views/DriverView.vue
+++ b/src/views/DriverView.vue
@@ -47,6 +47,6 @@ const chosenTrain = computed(() =>
margin: 0 auto;
padding: 1em 0;
max-width: var(--max-container-width);
- min-height: calc(100vh - 7em);
+ min-height: 100vh;
}
diff --git a/src/views/JournalDispatchers.vue b/src/views/JournalDispatchers.vue
index 8123de6..47e4d04 100644
--- a/src/views/JournalDispatchers.vue
+++ b/src/views/JournalDispatchers.vue
@@ -14,7 +14,7 @@
optionsType="dispatchers"
/>
-
+
@@ -50,16 +50,8 @@ import JournalHeader from '../components/JournalView/JournalHeader.vue';
import JournalStats from '../components/JournalView/JournalStats.vue';
import { useApiStore } from '../store/apiStore';
-const statsButtons: Journal.StatsButton[] = [
- {
- tab: Journal.StatsTab.DISPATCHER_STATS,
- localeKey: 'journal.dispatcher-stats.button',
- iconName: 'user',
- disabled: true
- }
-];
-
interface DispatchersQueryParams {
+ dutyId?: number;
dispatcherName?: string;
stationName?: string;
stationHash?: string;
@@ -105,18 +97,15 @@ export default defineComponent({
},
data: () => ({
- statsButtons,
-
dataRefreshedAt: null as Date | null,
currentQueryParams: {} as DispatchersQueryParams,
scrollDataLoaded: true,
scrollNoMoreData: false,
- showReturnButton: false,
- statsCardOpen: false,
- currentOptionsActive: false,
+ chosenPlayerId: -1,
+ currentOptionsActive: false,
dataStatus: Status.Data.Loading,
historyList: [] as API.DispatcherHistory.Response
@@ -126,12 +115,13 @@ export default defineComponent({
const sorterActive: Journal.DispatcherSorter = reactive({ id: 'timestampFrom', dir: -1 });
const journalFilterActive = ref({});
- const searchersValues = reactive({
+ const searchersValues = reactive>({
+ 'search-duty-id': '',
'search-dispatcher': '',
'search-station': '',
'search-date-from': '',
'search-date-to': ''
- } as Journal.DispatcherSearchType);
+ });
provide('sorterActive', sorterActive);
provide('journalFilterActive', journalFilterActive);
@@ -158,15 +148,6 @@ export default defineComponent({
queryParams[k as keyof DispatchersQueryParams] !=
defaultQueryParams[k as keyof DispatchersQueryParams]
);
- },
-
- 'mainStore.dispatcherStatsData'(stats) {
- this.statsButtons.find((sb) => sb.tab == Journal.StatsTab.DISPATCHER_STATS)!.disabled =
- stats === undefined;
- },
-
- async 'mainStore.dispatcherStatsName'() {
- this.fetchDispatcherStats();
}
},
@@ -192,6 +173,7 @@ export default defineComponent({
handleRouteParams() {
this.$router.push({
query: {
+ 'search-duty-id': this.searchersValues['search-duty-id'] || undefined,
'search-date-from': this.searchersValues['search-date-from'] || undefined,
'search-date-to': this.searchersValues['search-date-to'] || undefined,
'search-station': this.searchersValues['search-station'] || undefined,
@@ -215,30 +197,8 @@ export default defineComponent({
this.setOptions(query as any);
},
- async fetchDispatcherStats() {
- if (!this.mainStore.dispatcherStatsName) {
- this.mainStore.dispatcherStatsData = undefined;
- return;
- }
-
- try {
- const statsData: API.DispatcherStats.Response = await (
- await this.apiStore.client!.get('api/getDispatcherStats', {
- params: {
- name: this.mainStore.dispatcherStatsName
- }
- })
- ).data;
-
- this.mainStore.dispatcherStatsData = statsData;
- } catch (error) {
- this.mainStore.dispatcherStatsData = undefined;
-
- console.error('Ups! Wystąpił błąd przy próbie pobrania statystyk dyżurnego! :/');
- }
- },
-
- setOptions(options: { [key: string]: string }) {
+ setOptions(options: Record) {
+ this.searchersValues['search-duty-id'] = options['search-duty-id'] ?? '';
this.searchersValues['search-date-from'] = options['search-date-from'] ?? '';
this.searchersValues['search-date-to'] = options['search-date-to'] ?? '';
this.searchersValues['search-station'] = options['search-station'] ?? '';
@@ -275,6 +235,7 @@ export default defineComponent({
async fetchHistoryData() {
const queryParams: DispatchersQueryParams = {};
+ const dutyId = this.searchersValues['search-duty-id'].trim() || undefined;
const dispatcherName = this.searchersValues['search-dispatcher'].trim() || undefined;
const stationName = this.searchersValues['search-station'].trim() || undefined;
const dateFromString = this.searchersValues['search-date-from'].trim() || undefined;
@@ -295,6 +256,7 @@ export default defineComponent({
dateToISO = dateTo.toISOString();
}
+ queryParams['dutyId'] = Number(dutyId) || undefined;
queryParams['dispatcherName'] = dispatcherName;
queryParams['dateFrom'] = dateFromISO;
@@ -320,24 +282,24 @@ export default defineComponent({
if (!responseData) {
this.dataStatus = Status.Data.Error;
+ this.chosenPlayerId = -1;
+
return;
}
- if (!responseData) return;
-
// Response data exists
this.historyList = responseData;
- // Stats display
- this.mainStore.dispatcherStatsName =
- this.historyList.length > 0 && this.searchersValues['search-dispatcher'].trim()
- ? this.historyList[0].dispatcherName
- : '';
+ this.chosenPlayerId =
+ this.historyList.length > 0 && this.searchersValues['search-dispatcher'].trim() != ''
+ ? this.historyList[0].dispatcherId
+ : -1;
this.dataRefreshedAt = new Date();
this.dataStatus = Status.Data.Loaded;
} catch (error) {
this.dataStatus = Status.Data.Error;
+ this.chosenPlayerId = -1;
}
this.scrollNoMoreData = false;
diff --git a/src/views/JournalTimetables.vue b/src/views/JournalTimetables.vue
index 978d0bd..1e7986d 100644
--- a/src/views/JournalTimetables.vue
+++ b/src/views/JournalTimetables.vue
@@ -14,7 +14,7 @@
optionsType="timetables"
/>
-
+
@@ -29,6 +29,8 @@
:dataStatus="dataStatus"
:scrollDataLoaded="scrollDataLoaded"
:scrollNoMoreData="scrollNoMoreData"
+ :extraInfoIndexes="extraInfoIndexes"
+ @toggleExtraInfo="toggleExtraInfo"
/>
@@ -118,36 +120,6 @@ export const journalTimetableFilters: Journal.TimetableFilter[] = [
}
];
-interface TimetablesQueryParams {
- driverName?: string;
- trainNo?: string;
- timetableId?: string;
- categoryCode?: string;
-
- authorName?: string;
-
- dateFrom?: string;
- dateTo?: string;
-
- issuedFrom?: string;
- terminatingAt?: string;
- via?: string;
- includesScenery?: string;
-
- countFrom?: number;
- countLimit?: number;
-
- fulfilled?: number;
- terminated?: number;
-
- twr?: number;
- skr?: number;
- pn?: number;
- tn?: number;
-
- sortBy?: Journal.TimetableSorter['id'];
-}
-
export default defineComponent({
components: {
JournalOptions,
@@ -170,35 +142,18 @@ export default defineComponent({
mainStore: useMainStore(),
apiStore: useApiStore(),
- statsButtons: [
- {
- tab: Journal.StatsTab.DAILY_STATS,
- localeKey: 'journal.daily-stats.button',
- iconName: 'stats',
- disabled: false
- },
- {
- tab: Journal.StatsTab.DRIVER_STATS,
- localeKey: 'journal.driver-stats.button',
- iconName: 'train',
- disabled: true
- }
- ],
-
- currentQueryParams: {} as TimetablesQueryParams,
+ currentQueryParams: {} as API.TimetableHistory.QueryParams,
dataRefreshedAt: null as Date | null,
scrollDataLoaded: true,
scrollNoMoreData: false,
+ extraInfoIndexes: [] as number[],
- showReturnButton: false,
- statsCardOpen: false,
- currentOptionsActive: false,
+ chosenPlayerId: -1,
- timetableHistory: [] as API.TimetableHistory.Response,
+ timetableHistory: [] as API.TimetableHistory.ResponseShort,
- dataStatus: Status.Data.Loading,
- dataErrorMessage: ''
+ dataStatus: Status.Data.Loading
}),
setup() {
@@ -245,18 +200,11 @@ export default defineComponent({
};
},
- watch: {
- currentQueryParams(q: TimetablesQueryParams) {
- this.currentOptionsActive = Object.values(q).some((v) => v !== undefined);
- },
-
- 'mainStore.driverStatsData'(driverStats) {
- this.statsButtons.find((sb) => sb.tab == Journal.StatsTab.DRIVER_STATS)!.disabled =
- driverStats === undefined;
- },
-
- async 'mainStore.driverStatsName'() {
- this.fetchDriverStats();
+ computed: {
+ currentOptionsActive() {
+ return Object.keys(this.currentQueryParams)
+ .filter((k) => k != 'countFrom' && k != 'returnType')
+ .some((k) => (this.currentQueryParams as any)[k] !== undefined);
}
},
@@ -287,28 +235,21 @@ export default defineComponent({
this.setOptions(query as any);
},
- async fetchDriverStats() {
- if (!this.mainStore.driverStatsName) {
- this.mainStore.driverStatsData = undefined;
- this.mainStore.driverStatsStatus = Status.Data.Initialized;
- return;
- }
+ async toggleExtraInfo(timetableDetails: API.TimetableHistory.Data | null) {
+ if (!timetableDetails) return;
- try {
- this.mainStore.driverStatsStatus = Status.Data.Loading;
+ const existingIdx = this.extraInfoIndexes.indexOf(timetableDetails.id);
- const statsData: API.DriverStats.Response = await (
- await this.apiStore.client!.get(
- `api/getDriverInfo?name=${this.mainStore.driverStatsName}`
- )
- ).data;
+ if (existingIdx == -1) {
+ this.extraInfoIndexes.push(timetableDetails.id);
- this.mainStore.driverStatsData = statsData;
- this.mainStore.driverStatsStatus = Status.Data.Loaded;
- } catch (error) {
- this.mainStore.driverStatsData = undefined;
- this.mainStore.driverStatsStatus = Status.Data.Error;
- console.error('Ups! Wystąpił błąd przy próbie pobrania statystyk maszynisty! :/');
+ const synchedTimetable = this.timetableHistory.find((t) => t.id == timetableDetails.id);
+
+ if (synchedTimetable) {
+ Object.assign(synchedTimetable, timetableDetails);
+ }
+ } else {
+ this.extraInfoIndexes.splice(existingIdx, 1);
}
},
@@ -354,6 +295,8 @@ export default defineComponent({
},
async fetchHistoryData() {
+ this.extraInfoIndexes.length = 0;
+
const driverName = this.searchersValues['search-driver'].trim() || undefined;
const trainNo = this.searchersValues['search-train'].trim() || undefined;
const authorName = this.searchersValues['search-dispatcher'].trim() || undefined;
@@ -378,7 +321,7 @@ export default defineComponent({
dateToISO = dateTo.toISOString();
}
- const queryParams: TimetablesQueryParams = {};
+ const queryParams: API.TimetableHistory.QueryParams = {};
this.filterList
.filter((f) => f.isActive)
@@ -445,6 +388,7 @@ export default defineComponent({
queryParams['terminatingAt'] = terminatingAt;
queryParams['via'] = via;
queryParams['categoryCode'] = categoryCode;
+ queryParams['returnType'] = 'short';
queryParams['issuedFrom'] = issuedFrom;
queryParams['sortBy'] =
@@ -456,7 +400,7 @@ export default defineComponent({
this.currentQueryParams = queryParams;
try {
- const responseData: API.TimetableHistory.Response = await (
+ const responseData: API.TimetableHistory.ResponseShort = await (
await this.apiStore.client!.get('api/getTimetables', {
params: this.currentQueryParams
})
@@ -464,26 +408,23 @@ export default defineComponent({
if (!responseData) {
this.dataStatus = Status.Data.Error;
- this.dataErrorMessage = 'Brak danych!';
+ this.chosenPlayerId = -1;
return;
}
- if (!responseData) return;
-
// Response data exists
this.timetableHistory = responseData;
- // Stats display
- this.mainStore.driverStatsName =
- this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim()
- ? this.timetableHistory[0].driverName
- : '';
+ this.chosenPlayerId =
+ this.timetableHistory.length > 0 && this.searchersValues['search-driver'].trim() != ''
+ ? this.timetableHistory[0].driverId
+ : -1;
this.dataStatus = Status.Data.Loaded;
this.dataRefreshedAt = new Date();
} catch (error) {
this.dataStatus = Status.Data.Error;
- this.dataErrorMessage = 'Ups! Coś poszło nie tak!';
+ this.chosenPlayerId = -1;
}
this.scrollNoMoreData = false;
diff --git a/src/views/PlayerProfileView.vue b/src/views/PlayerProfileView.vue
new file mode 100644
index 0000000..5266a22
--- /dev/null
+++ b/src/views/PlayerProfileView.vue
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
+
+
{{ t('profile.no-player-found') }}
+ {{ t('profile.return-to-main') }}
+
+
+
+
+
+
+
+
diff --git a/src/views/SceneryView.vue b/src/views/SceneryView.vue
index c4cef30..767e293 100644
--- a/src/views/SceneryView.vue
+++ b/src/views/SceneryView.vue
@@ -135,6 +135,10 @@ function setViewMode(componentName: string) {
&-view {
display: flex;
justify-content: center;
+
+ height: 100vh;
+ min-height: 500px;
+ max-height: 2000px;
}
&-offline {
@@ -181,10 +185,6 @@ function setViewMode(componentName: string) {
background-color: #181818;
border-radius: 0.5em;
padding: 1em 0.5em;
-
- height: calc(100vh - 0.5em);
- min-height: 500px;
- max-height: 2000px;
}
.scenery-left {
@@ -236,6 +236,10 @@ function setViewMode(componentName: string) {
}
@include responsive.midScreen {
+ .scenery-view {
+ height: auto;
+ }
+
.scenery-wrapper {
grid-template-columns: 1fr;
gap: 0;
diff --git a/src/views/StationsView.vue b/src/views/StationsView.vue
index 834f049..0f8717f 100644
--- a/src/views/StationsView.vue
+++ b/src/views/StationsView.vue
@@ -32,6 +32,16 @@
+
+
+
+