From 23a8b9e8d469047a9dd4f2b7c5b29fcaa47ee51d Mon Sep 17 00:00:00 2001 From: Spythere Date: Mon, 2 Feb 2026 03:12:40 +0100 Subject: [PATCH 01/53] feature: player profile view --- src/router/index.ts | 5 + src/views/PlayerProfileView.vue | 161 ++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 src/views/PlayerProfileView.vue diff --git a/src/router/index.ts b/src/router/index.ts index 5d628e8..bf71128 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -61,6 +61,11 @@ const routes: Array = [ region: route.query.region }) }, + { + path: '/profile/:id', + name: 'PlayerProfileView', + component: () => import('../views/PlayerProfileView.vue') + }, { path: '/:catchAll(.*)', redirect: '/' diff --git a/src/views/PlayerProfileView.vue b/src/views/PlayerProfileView.vue new file mode 100644 index 0000000..1971d9b --- /dev/null +++ b/src/views/PlayerProfileView.vue @@ -0,0 +1,161 @@ + + + + + From cf51045343c39732f4135e9327731b7c5c247ca9 Mon Sep 17 00:00:00 2001 From: Spythere Date: Wed, 4 Feb 2026 01:02:57 +0100 Subject: [PATCH 02/53] chore(player profile): added typings for player info response object --- .../SceneryView/SceneryTimetablesHistory.vue | 6 +- src/typings/api.ts | 115 ++++++++++-------- src/typings/common.ts | 3 +- src/views/PlayerProfileView.vue | 36 +++++- 4 files changed, 104 insertions(+), 56 deletions(-) diff --git a/src/components/SceneryView/SceneryTimetablesHistory.vue b/src/components/SceneryView/SceneryTimetablesHistory.vue index 967663c..7c8984f 100644 --- a/src/components/SceneryView/SceneryTimetablesHistory.vue +++ b/src/components/SceneryView/SceneryTimetablesHistory.vue @@ -115,7 +115,7 @@ export default defineComponent({ data() { return { - historyList: [] as API.TimetableHistory.Response, + historyList: [] as API.TimetableHistory.ResponseShort, historyModeList, apiStore: useApiStore(), @@ -149,7 +149,7 @@ export default defineComponent({ requestFilters['returnType'] = 'short'; try { - const response: API.TimetableHistory.Response = await ( + const response: API.TimetableHistory.ResponseShort = await ( await this.apiStore.client!.get('api/getTimetables', { params: requestFilters }) @@ -178,7 +178,7 @@ export default defineComponent({ }); }, - parseCreatedDate(timetable: API.TimetableHistory.Data, locale: string) { + parseCreatedDate(timetable: API.TimetableHistory.DataShort, locale: string) { const createdDate = timetable.createdAt > timetable.beginDate ? new Date(timetable.beginDate) diff --git a/src/typings/api.ts b/src/typings/api.ts index 60ea024..29e15db 100644 --- a/src/typings/api.ts +++ b/src/typings/api.ts @@ -27,6 +27,15 @@ export namespace API { } } + export namespace PlayerActivity { + export interface Data { + dispatcher: API.ActiveSceneries.Data[]; + driver: API.ActiveTrains.Data; + } + + export type Response = Data; + } + export namespace DispatcherHistory { export type Response = Data[]; @@ -52,32 +61,25 @@ 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 { + 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 { @@ -102,12 +104,24 @@ export namespace API { routeDistance: number; } - export interface Response { + export interface Data { _sum: SumStats; _count: CountStats; _max: MaxStats; _avg: AvdStats; } + + export type Response = Data; + } + + export namespace PlayerInfo { + export interface Data { + currentActivity: PlayerActivity.Data; + dispatcherStats: DispatcherStats.Data; + dispatcherStatsLastMonth: DispatcherStats.Data; + driverStats: DriverStats.Data; + driverStatsLastMonth: DriverStats.Data; + } } export namespace ActiveSceneries { @@ -211,12 +225,40 @@ export namespace API { } export namespace TimetableHistory { - export interface Data { - id: number; - createdAt: string; + export interface Data extends DataShort { updatedAt: string; timetableId: number; + sceneriesString: string; + endDate: string; + + scheduledBeginDate: string; + scheduledEndDate: string; + + stockString?: string; + stockHistory: string[]; + + stockMass?: number; + stockLength?: number; + maxSpeed?: number; + + routeSceneries: string; + checkpointArrivals: string[]; + checkpointDepartures: string[]; + checkpointArrivalsScheduled: string[]; + checkpointDeparturesScheduled: string[]; + checkpointStopTypes: string[]; + checkpointComments: string[]; + visitedSceneries: string[]; + sceneryNames: string[]; + path: string; + warningNotes: string | null; + trainMaxSpeed?: number; + } + + export interface DataShort { + id: number; + createdAt: string; trainNo: number; trainCategoryCode: string; @@ -229,7 +271,6 @@ export namespace API { route: string; twr: number; skr: number; - sceneriesString: string; currentLocation: string[]; routeDistance: number; @@ -239,10 +280,6 @@ export namespace API { allStopsCount: number; beginDate: string; - endDate: string; - - scheduledBeginDate: string; - scheduledEndDate: string; terminated: boolean; fulfilled: boolean; @@ -250,32 +287,14 @@ export namespace API { authorName?: string; authorId?: number; - stockString?: string; - stockHistory: string[]; - - stockMass?: number; - stockLength?: number; - maxSpeed?: number; - currentSceneryName?: string; currentSceneryHash?: string; - routeSceneries: string; - checkpointArrivals: string[]; - checkpointDepartures: string[]; - checkpointArrivalsScheduled: string[]; - checkpointDeparturesScheduled: string[]; - checkpointStopTypes: string[]; - checkpointComments: string[]; - visitedSceneries: string[]; - sceneryNames: string[]; - path: string; - warningNotes: string | null; hasDangerousCargo: boolean; hasExtraDeliveries: boolean; - trainMaxSpeed?: number; } export type Response = Data[]; + export type ResponseShort = DataShort[]; } export namespace DailyStats { 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/views/PlayerProfileView.vue b/src/views/PlayerProfileView.vue index 1971d9b..27c2917 100644 --- a/src/views/PlayerProfileView.vue +++ b/src/views/PlayerProfileView.vue @@ -19,10 +19,13 @@

Stacjosponsor od 01.01.2024

-
+
-
245 / 250 (95.55%)
+
+ {{ playerInfo.driverStats._count.fulfilled }} / + {{ playerInfo.driverStats._count._all }} (95.55%) +
ROZKŁADÓW JAZDY
@@ -85,12 +88,37 @@ From ccca1c87520df016a15dd0d705b4fc5583441348 Mon Sep 17 00:00:00 2001 From: Spythere Date: Fri, 6 Feb 2026 01:16:32 +0100 Subject: [PATCH 03/53] chore(profile): added grid layout & cleaned up styles --- src/typings/api.ts | 8 ++ src/views/PlayerProfileView.vue | 239 +++++++++++++++++++++----------- 2 files changed, 167 insertions(+), 80 deletions(-) diff --git a/src/typings/api.ts b/src/typings/api.ts index 29e15db..51098d7 100644 --- a/src/typings/api.ts +++ b/src/typings/api.ts @@ -124,6 +124,14 @@ export namespace API { } } + export namespace PlayerJournal { + export interface Data { + timetables: TimetableHistory.DataShort[]; + issuedTimetables: TimetableHistory.DataShort[]; + duties: DispatcherHistory.Data[]; + } + } + export namespace ActiveSceneries { export interface Data { dispatcherId: number; diff --git a/src/views/PlayerProfileView.vue b/src/views/PlayerProfileView.vue index 27c2917..9be2ff7 100644 --- a/src/views/PlayerProfileView.vue +++ b/src/views/PlayerProfileView.vue @@ -2,85 +2,106 @@
-
+
player image -
-

Spythere

12 poziom maszynisty

12 poziom dyżurnego

-

Pierwsza aktywność: 02.02.2022

Ostatnia aktywność: 02.02.2026 (DR)

Stacjosponsor od 01.01.2024

-
-
-
-
- {{ playerInfo.driverStats._count.fulfilled }} / - {{ playerInfo.driverStats._count._all }} (95.55%) -
-
ROZKŁADÓW JAZDY
-
+
+
+ train icon +

STATYSTYKI MASZYNISTY

+
+
522 / 619 (95.39%) - wypełnione rozkłady jazdy
-
25
-
SŁUŻB JAKO DR
+ 16091 / 17149 (95.39%) - zatwierdzony kilometraż w RJ
-
-
14
-
WYSTAWIONYCH RJ
+ 2420 / 2537 (95.39%) - potwierdzonych stacji w RJ
+
237.13km - najdłuższy rozkład jazdy
+
60.39km - średnia długość wszystkich rozkładów
-
-
- train icon - Najdłuższy rozkład o długości 237.13km • średnio 60.39km -
+
+ clock icon +

STATYSTYKI DYŻURNEGO RUCHU

+
-
- spawn icon - 16091 zatwierdzonych kilometrów na 17149 razem (93.83%) -
- -
- clock icon - 2420 potwierdzonych odjazdów z 2537 łącznie (95.39%) -
- -
- clock icon - Najdłuższa służba o długości 6 godz. 13 min. • średnio 2 godz. 1 min. -
- -
- timetable icon - Wystawione łącznie 670.80km rozkładów • najdłuższy: 80.81km • - średnio: 35.31km -
+
25 - służby jako dyżurny ruchu
+
6 godz. 13 min. - najdłuższa służba
+
14 - wystawione RJ jako dyżurny ruchu
+
80.81km - najdłuższy wystawiony RJ
+
670.80km - suma długości wystawionych RJ
-
- +

STATYSTYKI AKTYWNOŚCI (30 OSTATNICH DNI)

+ +
+
+
+
train icon
+

55

+
+ ROZKŁADÓW
+ JAZDY +
+
+ +
+
train icon
+

5500

+
+ POKONANYCH
+ KILOMETRÓW +
+
+ +
+
train icon
+

15

+
+ SŁUŻB
+ DYŻURNEGO +
+
+ +
+
train icon
+

12

+
+ WYSTAWIONYCH
+ ROZKŁADÓW +
+
+
-
-
-
ROZKŁADY JAZDY
-
DYŻURNY RUCHU
-
WYSTAWIONE ROZKŁADY
-
+

OSTATNIA AKTYWNOŚĆ GRACZA

+ +
+ + + +
+ +
+
    +
  • +
@@ -88,7 +109,7 @@ From 40a0b4798417c7d606c552843fa33f4632fc5e54 Mon Sep 17 00:00:00 2001 From: Spythere Date: Fri, 6 Feb 2026 01:49:18 +0100 Subject: [PATCH 04/53] chore(profile): added combined journal with timetables and dispatchers; added journal filters --- src/typings/api.ts | 2 + src/views/PlayerProfileView.vue | 98 ++++++++++++++++++++++++++++++--- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/src/typings/api.ts b/src/typings/api.ts index 51098d7..75a1bfd 100644 --- a/src/typings/api.ts +++ b/src/typings/api.ts @@ -41,6 +41,8 @@ export namespace API { export interface Data { id: number; + createdAt: string; + updatedAt: string; currentDuration: number; dispatcherId: number; dispatcherName: string; diff --git a/src/views/PlayerProfileView.vue b/src/views/PlayerProfileView.vue index 9be2ff7..96dcaec 100644 --- a/src/views/PlayerProfileView.vue +++ b/src/views/PlayerProfileView.vue @@ -93,14 +93,18 @@

OSTATNIA AKTYWNOŚĆ GRACZA

- - - + + +
    -
  • +
  • {{ entry.type }} - {{ entry.date }}
@@ -109,22 +113,77 @@ From b8574f9ea131f5780433343614c757dfdfdb3b88 Mon Sep 17 00:00:00 2001 From: Spythere Date: Fri, 6 Feb 2026 17:15:08 +0100 Subject: [PATCH 09/53] chore(profile): translation setup --- src/locales/en.json | 10 ++++++---- src/locales/pl.json | 8 ++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/locales/en.json b/src/locales/en.json index 735f1c5..3b5701c 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -619,9 +619,11 @@ "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": { + "filters": { + "Timetable": "TIMETABLES", + "Dispatcher": "DISPATCHER DUTIES", + "IssuedTimetable": "ISSUED TIMETABLES" + } } } diff --git a/src/locales/pl.json b/src/locales/pl.json index 5d0f90e..33ede86 100644 --- a/src/locales/pl.json +++ b/src/locales/pl.json @@ -604,7 +604,11 @@ "desc-end": "Pociąg kończy bieg", "desc-terminated": "Pociąg zakończył bieg" }, - "history": { - "title": "DZIENNIK ROZKŁADÓW JAZDY" + "profile": { + "filters": { + "Timetable": "ROZKŁADY JAZDY", + "Dispatcher": "SŁUŻBY DYŻURNEGO", + "IssuedTimetable": "WYSTAWIONE RJ" + } } } From 1d49de1c6ba48129d9d41ba3e35e92b6ed9a9540 Mon Sep 17 00:00:00 2001 From: Spythere Date: Sat, 7 Feb 2026 01:18:28 +0100 Subject: [PATCH 10/53] chore(profile): organized fetching data; added link to profile in scenery view --- public/images/default-avatar.jpg | Bin 0 -> 2483 bytes .../SceneryInfo/SceneryInfoDispatcher.vue | 5 +- src/styles/_global.scss | 2 + src/typings/api.ts | 59 ++++++++++ src/views/PlayerProfileView.vue | 109 +++++++++++++----- 5 files changed, 142 insertions(+), 33 deletions(-) create mode 100644 public/images/default-avatar.jpg diff --git a/public/images/default-avatar.jpg b/public/images/default-avatar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9b35e48d8c1a52644102f11b2895b2096ad65abe GIT binary patch literal 2483 zcmbuAcTm&I8is!%fq(`im;{34geKsjNJkW;28bLaAV^W^9g*G+MF=gl13^K>16P`$ z5MvZV7eR_%t{@;tZ-NRKssxmh1GYPJ=l*f$o^R)!ot^#moA=qB-DM53CIQ|v26zJi z1OfqudktWX0DAkQzZLsM{#Jqg#jK|QoD*;XT)-evfDH};!$GW801E&h_HVU;?*U>1 zL)bZ>oLmPE?j=<50&E~Km<33`O7{R2mt@wvTtDj!3E#rVuL`y z5a>P^h%IzaU^s+bM1g}(#|-M|e^^v8mJ^{%E}%7Yi7A;cBb@?z4xq%9r;e`dqkSd& z-@u6f7uip+e{&53FfeHE@W60D3)p#Dm?8yyRb$)~3wSy~!t(YGY#H~qCC2(I=@0MT z6AxiJiisN^t*X6BpGXictSYI0>c*j#FZ;l1zFcc?qQw}Z%L3Z+Me}DwlZz`;P3Q?T z;P(nR*^+;e3od6{<(eZvjTz$LwSkl!kvOK#W+>eLl=TYk6?c|+JuSm;chP~C3LcFd zUw?O%x5>qOOA56$)m0!-aqe_eKcosh}5haAh0)m7p`i^TOp_JD=Z8W}mziY2ms z8VlQuYspQ^H6?vYWnc*;ldy5p_ojo#;1q{mgSO)aD=N_`aO5D31I+S-ug;nsjXY3Px|d>7`xX2ZwOir}%# z=(2TI(5ht9HwWytd^)u<(`wT z@UtE%%@YPk_0Pnp^pC!b>6TeTg}7lj2>=0r;+>s6@7fH{F;OeSLpC}{N=c<2yxEsc z#$P{XhM{jsz{~j8COhG)a0tK*hXB1}0f(anK41@5qLtHaP~%L|qrK}l3#=OZrI*nnM=-eq-v;Lon!%g-gfBMiE^L3KYtu+$u=gDt1nz?J#^Y+ z_AxGyCwz65FzBGYL!~CMfXjzCh7QU?+nEo;L#LOvj-1O<)|T^eO`d(6GfPkMlzo@> z?4bJ*NwU#XlRH>w&o^UV&Gm&zC9EVR;L+e#TLrP*Y1I0b#Yx!jR#W47Q4+Vxtmg?a zy>lBm)jiTt16jR6dC0o$ik_I!Ac-a)<=agR?&D{hnz5H@RdvF|lHZm)L?FKyph+D9 zm@=?#iqsvpeKkOtx!&;QR6G6>9dC-fEjYb>jp5-i%+;j>$*OH3nj)9_S3?;Qfu7iB z42c07`lZoL;uysdvb(7Vp{hF|(rFzzoya51m;h~I?4JlT9PmRyc5q|7hUHXclVybD zo1l_f!;#ra*(e}5^MsVM@U-^CF_Y$+m(l(0v=9T$2`b}!8l6X!^?$CP4_G$J<;X^cYE%4i6R!~U~hE5p<7kkkK<6#wsOR`s-uS*B6iN~HXO3x;v$<5~-w6%OZ%BFHp zk(_Gc7gNHrJZ`Rg%o09yjFyAVzxse8SFmkdlih5aMXeO6L?0!i`UuqO_UQbzcAuc@ z3X_R8D(VceNMc!9?}@>5`;3zV)%Q13&(#kE*Ds_Snm1;?2n-FFoeAu*2r=sMyC(>c zih$tmiK}#ek_2t}Mq+~|tRne=*9L#6SA2#dMwBzn%=iyM{`_^bTHQ;xZjMHAI>Z7|M%oFEsI$GYHfUX;6=h^2*slOKmEHhfc$dHP{YTi{!J z-p=1Uh28stEVeXE1PCGc2tTTD)MuDjoaaTKU`erg(*u#}%Z5ZVXkvr<-MPN9V6_F# z+vfhF37M)zTDQXK+VVXzohU_={E0ht4UXYtayD`lyJE#7B1pF`g?>!WR~&gqS-Ouq9d8*K&k~oYoZ#rv9>o$~e>q~ymKNbtNQb)N>@x4{$ z&aM@nbV15(`9SZoVV#I87hRyMSDdpnC;7k$qe{)x{B6rEnQfIf8=b{%5mVlDx2WOE z6n{6H7jCkyAxp6}+vSKAgSc6Xy`Nl5{sWb|)a2Za#s!Mbjf=ioWGMcobhaARha3&5 eAp7_UNtw5yulb9dWk-O%i2b-V|D}hrhW-LMZzVDS literal 0 HcmV?d00001 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' }} - +
-
+
player image -

Spythere

+

{{ playerInfo.playerName }}

+

12 poziom maszynisty

12 poziom dyżurnego

+

Ostatnia aktywność: 02.02.2026 (DR)

-

Stacjosponsor od 01.01.2024

+ +
@@ -159,12 +165,13 @@ - - 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 @@
@@ -50,15 +50,6 @@ 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 { dispatcherName?: string; stationName?: string; @@ -105,18 +96,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 @@ -158,15 +146,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(); } }, @@ -215,29 +194,6 @@ 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 }) { this.searchersValues['search-date-from'] = options['search-date-from'] ?? ''; this.searchersValues['search-date-to'] = options['search-date-to'] ?? ''; @@ -320,24 +276,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..1c414d9 100644 --- a/src/views/JournalTimetables.vue +++ b/src/views/JournalTimetables.vue @@ -14,7 +14,7 @@ optionsType="timetables" /> - +
@@ -170,35 +170,19 @@ 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, dataRefreshedAt: null as Date | null, scrollDataLoaded: true, scrollNoMoreData: false, - showReturnButton: false, - statsCardOpen: false, + chosenPlayerId: -1, + currentOptionsActive: false, timetableHistory: [] as API.TimetableHistory.Response, - dataStatus: Status.Data.Loading, - dataErrorMessage: '' + dataStatus: Status.Data.Loading }), setup() { @@ -248,15 +232,6 @@ 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(); } }, @@ -287,31 +262,6 @@ 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; - } - - try { - this.mainStore.driverStatsStatus = Status.Data.Loading; - - const statsData: API.DriverStats.Response = await ( - await this.apiStore.client!.get( - `api/getDriverInfo?name=${this.mainStore.driverStatsName}` - ) - ).data; - - 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! :/'); - } - }, - setOptions(options: { [key: string]: string }) { Object.keys(this.searchersValues).forEach((v) => { this.searchersValues[v as Journal.TimetableSearchKey] = options[v] ?? ''; @@ -464,26 +414,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; From 150b7749ae56e3ff19441878006760f118499c19 Mon Sep 17 00:00:00 2001 From: Spythere Date: Mon, 9 Feb 2026 00:52:11 +0100 Subject: [PATCH 14/53] chore(profile): added level badges for player summary --- src/composables/badge.ts | 8 +++ src/views/PlayerProfileView.vue | 118 ++++++++++++++++++++++++-------- 2 files changed, 99 insertions(+), 27 deletions(-) create mode 100644 src/composables/badge.ts 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/views/PlayerProfileView.vue b/src/views/PlayerProfileView.vue index 6691b8d..40aa69b 100644 --- a/src/views/PlayerProfileView.vue +++ b/src/views/PlayerProfileView.vue @@ -3,37 +3,73 @@
- player image +
+ player image -

{{ playerName }}

+ -

{{ playerTD2Info.levels.driver }} poziom maszynisty

-

{{ playerTD2Info.levels.dispatcher }} poziom dyżurnego

+
+

{{ playerName }}

-
-
- ONLINE JAKO DR: - {{ - playerInfo.currentActivity.dispatcher - .map((d) => `${d.stationName} (${d.stationHash})`) - .join(', ') - }} +
+
+ + {{ + playerInfo.driverStats.driverLevel > 1 + ? playerInfo.driverStats.driverLevel + : 'L' + }} + + MASZYNISTA +
+ +
+ + {{ + playerInfo.dispatcherStats.dispatcherLevel > 1 + ? playerInfo.dispatcherStats.dispatcherLevel + : 'L' + }} + + DYŻURNY RUCHU +
+
+
-
- ONLINE JAKO MASZYNISTA: - {{ playerInfo.currentActivity.driver }} -
+
+ ONLINE JAKO DR: + {{ + playerInfo.currentActivity.dispatcher + .map((d) => `${d.stationName} (${d.stationHash})`) + .join(', ') + }} +
+ +
+ ONLINE JAKO MASZYNISTA: + {{ playerInfo.currentActivity.driver.trainNo }}
@@ -168,7 +204,7 @@
spawn icon

- {{ playerInfo.driverStatsLastMonth.currentDistanceTotal || 0 }} + {{ playerInfo.driverStatsLastMonth.currentDistanceTotal?.toFixed(2) || 0 }}

@@ -295,6 +331,7 @@ import axios from 'axios'; import { getCountPercentage } from '../utils/calcUtils'; import { Status } from '../typings/common'; import Loading from '../components/Global/Loading.vue'; +import { calculateExpStyles } from '../composables/badge'; type JournalEntryType = 'Timetable' | 'Dispatcher' | 'IssuedTimetable'; @@ -375,6 +412,7 @@ const combinedJournal = computed(() => { async function fetchAllData() { const playerId = route.query.playerId?.toString(); + playerTD2Info.value = null; playerDataStatus.value = Status.Data.Loading; if (!playerId) { @@ -484,6 +522,7 @@ function toggleFilter(filterType: JournalEntryType) { diff --git a/src/views/PlayerProfileView.vue b/src/views/PlayerProfileView.vue index 261a0cc..4fe80ff 100644 --- a/src/views/PlayerProfileView.vue +++ b/src/views/PlayerProfileView.vue @@ -488,7 +488,7 @@ async function fetchPlayerJournal(playerId: string) { const response = await apiStore.client.get('api/getPlayerJournal', { params: { playerId: playerId, - countLimit: 30 + dateScope: '14d' } }); @@ -545,6 +545,8 @@ $tileColor: #181818; .profile-view { display: flex; justify-content: center; + height: 100vh; + min-height: 500px; } .no-data-found { @@ -573,8 +575,6 @@ $tileColor: #181818; max-width: var(--max-container-width); width: 100%; - // height: calc(100vh - 0.5em); - min-height: 900px; padding: 1rem 0; text-align: center; @@ -582,8 +582,6 @@ $tileColor: #181818; .view-container > div { position: relative; - - // border-radius: 0.5em; } .profile-sidebar { diff --git a/src/views/SceneryView.vue b/src/views/SceneryView.vue index c4cef30..1527176 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 { From 41e3d018e6e2c3beb5ed73755c95e0b116e5d757 Mon Sep 17 00:00:00 2001 From: Spythere Date: Fri, 13 Feb 2026 00:52:02 +0100 Subject: [PATCH 18/53] chore: profile --- src/views/PlayerProfileView.vue | 223 ++++++++++++++++++-------------- src/views/SceneryView.vue | 6 +- 2 files changed, 128 insertions(+), 101 deletions(-) diff --git a/src/views/PlayerProfileView.vue b/src/views/PlayerProfileView.vue index 4fe80ff..3aae801 100644 --- a/src/views/PlayerProfileView.vue +++ b/src/views/PlayerProfileView.vue @@ -1,7 +1,7 @@ - diff --git a/src/components/PlayerProfileView/ProfileRecentStats.vue b/src/components/PlayerProfileView/ProfileRecentStats.vue index 2da8ea1..d65c0f6 100644 --- a/src/components/PlayerProfileView/ProfileRecentStats.vue +++ b/src/components/PlayerProfileView/ProfileRecentStats.vue @@ -60,6 +60,8 @@ defineProps({ diff --git a/src/components/PlayerProfileView/ProfileSummary.vue b/src/components/PlayerProfileView/ProfileSummary.vue index 9282122..f97da32 100644 --- a/src/components/PlayerProfileView/ProfileSummary.vue +++ b/src/components/PlayerProfileView/ProfileSummary.vue @@ -67,7 +67,6 @@ class="player-activity" v-if="activeDispatches.length > 0 || activeTrains.length > 0" > -
{ grid-template-columns: repeat(auto-fit, minmax(450px, 1fr)); } } + +@include responsive.smallScreen { + .player-stats { + display: grid; + grid-template-columns: 1fr; + } +} diff --git a/src/views/PlayerProfileView.vue b/src/views/PlayerProfileView.vue index 36bd017..bad9a52 100644 --- a/src/views/PlayerProfileView.vue +++ b/src/views/PlayerProfileView.vue @@ -9,7 +9,7 @@
- +
@@ -66,6 +66,7 @@ onMounted(() => { async function fetchAllData() { const playerId = route.query.playerId?.toString(); + playerInfo.value = null; playerTD2Info.value = null; playerDataStatus.value = Status.Data.Loading; From 92c73b9ed9471b0c76005d3320f3109ba1d024dc Mon Sep 17 00:00:00 2001 From: Spythere Date: Wed, 18 Feb 2026 01:55:34 +0100 Subject: [PATCH 36/53] chore(profile): added missing translations --- .../PlayerProfileView/ProfileSummary.vue | 4 +- src/locales/en.json | 42 +++++++++++++++++++ src/locales/pl.json | 9 ++-- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/components/PlayerProfileView/ProfileSummary.vue b/src/components/PlayerProfileView/ProfileSummary.vue index f97da32..d9879b1 100644 --- a/src/components/PlayerProfileView/ProfileSummary.vue +++ b/src/components/PlayerProfileView/ProfileSummary.vue @@ -51,14 +51,14 @@ class="a-button btn--action" :to="`/journal/timetables?search-driver=${playerInfo.driverStats.driverName}`" > - DZIENNIK RJ + {{ t('profile.stats.timetables-journal') }} - DZIENNIK DR + {{ t('profile.stats.dispatchers-journal') }}
diff --git a/src/locales/en.json b/src/locales/en.json index 7b03507..8e6f7dd 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -197,6 +197,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", @@ -620,10 +621,51 @@ "desc-terminated": "The train has been terminated" }, "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", + + "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" } } } diff --git a/src/locales/pl.json b/src/locales/pl.json index 7d8efb6..a59bbdd 100644 --- a/src/locales/pl.json +++ b/src/locales/pl.json @@ -193,6 +193,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", @@ -616,13 +617,11 @@ }, "stats": { - "currently-online": "OBECNIE ONLINE", + "timetables-journal": "DZIENNIK RJ", + "dispatchers-journal": "DZIENNIK DR", "driver": "MASZYNISTA", "dispatcher": "DYŻURNY RUCHU", - "online-as-driver": "ONLINE JAKO MASZYNISTA", - "online-as-dispatcher": "ONLINE JAKO DR", - "on-scenery": "na scenerii", "header-driver": "STATYSTYKI MASZYNISTY", "fulfilled-timetables": "wypełnione rozkłady jazdy", @@ -638,7 +637,7 @@ "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": "Brak zapisanych wystawionych RJ" + "no-dispatcher-stats": "Ten użytkownik nie posiada statystyk dyżurnego zarejestrowanych przez Stacjownik!" }, "recent-stats": { From f2c11bf2cf8dba3511ee25a529dea2861c3bccce Mon Sep 17 00:00:00 2001 From: Spythere Date: Wed, 18 Feb 2026 01:55:59 +0100 Subject: [PATCH 37/53] chore(profile): date formatting from utils --- .../PlayerProfileView/ProfileHistoryList.vue | 24 ++++++++++--------- src/composables/time.ts | 6 +++++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/components/PlayerProfileView/ProfileHistoryList.vue b/src/components/PlayerProfileView/ProfileHistoryList.vue index 0047f14..1975539 100644 --- a/src/components/PlayerProfileView/ProfileHistoryList.vue +++ b/src/components/PlayerProfileView/ProfileHistoryList.vue @@ -48,18 +48,20 @@ : !entry.value.terminated && entry.type != 'IssuedTimetable' " > - {{ entry.date.toLocaleString('pl-PL', { dateStyle: 'long', timeStyle: 'short' }) }} - + {{ dateToLocaleString(entry.date, { dateStyle: 'long', timeStyle: 'short' }) }} + - - {{ - new Date(entry.value.timestampTo).toLocaleString('pl-PL', { - dateStyle: - new Date(entry.value.timestampTo).getDay() == entry.date.getDay() - ? undefined - : 'long', + {{ + dateToLocaleString(new Date(entry.value.timestampTo), { timeStyle: 'short' }) - }} + }} + {{ + dateToLocaleString(new Date(entry.value.timestampTo), { + dateStyle: 'long', + timeStyle: 'short' + }) + }}
@@ -97,8 +99,8 @@ @@ -265,7 +226,7 @@ ul.stats-list { gap: 0.5em; } -@include responsive.smallScreen{ +@include responsive.smallScreen { h3 { text-align: center; } diff --git a/src/store/apiStore.ts b/src/store/apiStore.ts index 3977102..f1f6479 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, @@ -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; + } } } }); From b7db3edd9b0c23b89e666c5e99df57454840bfd2 Mon Sep 17 00:00:00 2001 From: Spythere Date: Wed, 18 Feb 2026 23:56:32 +0100 Subject: [PATCH 44/53] chore(app): removed migration card rendering --- src/App.vue | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/App.vue b/src/App.vue index 5075757..42e818d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -7,11 +7,6 @@ - - @@ -52,7 +47,6 @@ import UpdateCard from './components/App/UpdateCard.vue'; import StorageManager from './managers/storageManager'; import AppFooter from './components/App/AppFooter.vue'; import AppWelcomeCard from './components/App/AppWelcomeCard.vue'; -import MigrateInfoCard from './components/App/MigrateInfoCard.vue'; const STORAGE_VERSION_KEY = 'app_version'; const WELCOME_CARD_SEEN_KEY = 'welcome_card_seen'; @@ -66,7 +60,6 @@ export default defineComponent({ AppFooter, UpdateCard, AppWelcomeCard, - MigrateInfoCard, Tooltip }, From 86fbaa25103ceafe0e96ff838ea7e8a2f2cf2ad3 Mon Sep 17 00:00:00 2001 From: Spythere Date: Fri, 20 Feb 2026 01:27:35 +0100 Subject: [PATCH 45/53] chore(profile): moved player avatar and its logic to separate component --- .../PlayerProfileView/ProfilePlayerAvatar.vue | 72 ++++++++++ .../PlayerProfileView/ProfileSummary.vue | 125 +++++++++++------- src/views/PlayerProfileView.vue | 34 +---- 3 files changed, 147 insertions(+), 84 deletions(-) create mode 100644 src/components/PlayerProfileView/ProfilePlayerAvatar.vue diff --git a/src/components/PlayerProfileView/ProfilePlayerAvatar.vue b/src/components/PlayerProfileView/ProfilePlayerAvatar.vue new file mode 100644 index 0000000..00d41d5 --- /dev/null +++ b/src/components/PlayerProfileView/ProfilePlayerAvatar.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/components/PlayerProfileView/ProfileSummary.vue b/src/components/PlayerProfileView/ProfileSummary.vue index 2b8cc78..28956d4 100644 --- a/src/components/PlayerProfileView/ProfileSummary.vue +++ b/src/components/PlayerProfileView/ProfileSummary.vue @@ -2,14 +2,7 @@
- player image - - +

@@ -228,7 +221,7 @@ diff --git a/src/components/StationsView/StationTable.vue b/src/components/StationsView/StationTable.vue index 055bc0e..a400a1c 100644 --- a/src/components/StationsView/StationTable.vue +++ b/src/components/StationsView/StationTable.vue @@ -445,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/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;