feat(scenery): added the scenery top records list mode

This commit is contained in:
2026-04-09 02:05:32 +02:00
parent e1f4a740ac
commit b999e84b15
5 changed files with 200 additions and 2 deletions
@@ -0,0 +1,183 @@
<template>
<div class="scenery-top-list">
<h2 class="header">{{ t('scenery.top-list.header') }}</h2>
<div class="top-actions">
<div class="actions-modes">
<button
v-for="mode in availableModes"
:class="`btn btn--option ${mode == currentListMode ? 'checked' : ''}`"
@click="selectListMode(mode)"
>
{{ t(`scenery.top-list.mode-${mode}`) }}
</button>
</div>
<div class="actions-scopes">
<button
v-for="scope in availableScopes"
:class="`btn btn--option ${scope == currentListScope ? 'checked' : ''}`"
@click="selectListScope(scope)"
>
{{ t(`scenery.top-list.scope-${scope}`) }}
</button>
</div>
</div>
<div class="rating-list-wrapper">
<Loading v-if="listState == Status.Data.Loading" />
<div v-else-if="listState == Status.Data.Error">Ups, coś poszło nie tak...</div>
<ul v-else-if="currentListMode == 'dispatchers'" v-for="value in topDispatchersList">
{{
value.dispatcherName
}}
-
{{
value.count
}}
</ul>
<ul v-else v-for="value in topLikesList">
{{
value.dispatcherName
}}
-
{{
value.sumRate
}}
</ul>
</div>
</div>
</template>
<script lang="ts" setup>
import { onActivated, PropType, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useApiStore } from '../../store/apiStore';
import { Station, ActiveScenery, Status } from '../../typings/common';
import Loading from '../Global/Loading.vue';
import { useMainStore } from '../../store/mainStore';
interface DispatcherTopCount {
dispatcherName: string;
count: number;
}
interface LikesTopCount {
dispatcherName: string;
sumRate: number;
}
const { t } = useI18n();
const apiStore = useApiStore();
const mainStore = useMainStore();
defineOptions({
name: 'SceneryTopList'
});
const props = defineProps({
station: {
type: Object as PropType<Station>
},
onlineScenery: {
type: Object as PropType<ActiveScenery>
}
});
const availableModes = ['likes', 'dispatchers'] as const;
const availableScopes = ['name', 'hash'] as const;
type ListMode = (typeof availableModes)[number];
type ListScope = (typeof availableScopes)[number];
const currentListMode = ref<ListMode>('likes');
const currentListScope = ref<ListScope>('name');
const listState = ref<Status.Data>(Status.Data.Loading);
const topLikesList = ref<LikesTopCount[]>([]);
const topDispatchersList = ref<DispatcherTopCount[]>([]);
onActivated(() => {
fetchTopDispatchersList();
});
function selectListMode(mode: ListMode) {
currentListMode.value = mode;
fetchTopDispatchersList();
}
function selectListScope(scope: ListScope) {
currentListScope.value = scope;
fetchTopDispatchersList();
}
async function fetchTopDispatchersList() {
const searchedStationValue =
currentListScope.value == 'name'
? props.station?.name
: apiStore.sceneryData.find((sc) => sc.name == props.station!.name)?.hash;
console.log(searchedStationValue);
topDispatchersList.value = [];
topLikesList.value = [];
if (!searchedStationValue) {
listState.value = Status.Data.Loaded;
return;
}
try {
listState.value = Status.Data.Loading;
const response = await apiStore.client.get(
`api/getSceneryTop${currentListMode.value}By${currentListScope.value}?${currentListScope.value}=${searchedStationValue}`
);
if (currentListMode.value == 'dispatchers') {
topDispatchersList.value = response as DispatcherTopCount[];
} else {
topLikesList.value = response as LikesTopCount[];
}
listState.value = Status.Data.Loaded;
} catch (error) {
listState.value = Status.Data.Error;
console.error(error);
}
}
</script>
<style lang="scss" scoped>
.scenery-top-list {
display: grid;
grid-template-rows: auto auto 1fr;
}
.header {
margin-bottom: 0.5em;
}
.top-actions {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 0.5em 1.5em;
}
.actions-modes,
.actions-scopes {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
font-weight: bold;
button {
font-weight: bold;
}
}
</style>
+1 -1
View File
@@ -15,7 +15,7 @@ export class HttpClient {
const data = await fetch(absoluteURL);
if (!data.ok) {
throw new Error(`Cannot fetch: ${absoluteURL}`);
throw new Error(`Cannot fetch ${absoluteURL}: ${data.statusText}`);
}
return data.json();
+1
View File
@@ -573,6 +573,7 @@
"option-active-timetables": "Active timetables",
"option-timetables-history": "Timetables history PL1",
"option-dispatchers-history": "Dispatchers history PL1",
"option-top-list": "Scenery records",
"btn-show-timetable-thumbnails": "Show rolling stock thumbnails",
"btn-hide-timetable-thumbnails": "Hide rolling stock thumbnails",
"timetable-includesScenery": "ALL TIMETABLES",
+10 -1
View File
@@ -559,6 +559,7 @@
"option-active-timetables": "Aktywne rozkłady jazdy",
"option-timetables-history": "Historia rozkładów PL1",
"option-dispatchers-history": "Historia dyżurów PL1",
"option-top-list": "Rekordy scenerii",
"btn-show-timetable-thumbnails": "Pokazuj podglądy składów",
"btn-hide-timetable-thumbnails": "Ukrywaj podglądy składów",
"timetable-includesScenery": "WSZYSTKIE RJ",
@@ -578,7 +579,15 @@
"tablice-link": "Tablica informacyjna zbiorcza <br> (autorstwa Thundo)",
"bottom-info": "Pokaż pełną historię w zakładce Dziennika",
"btn-show-internal-routes": "Pokazuj szlaki wewnętrzne",
"btn-hide-internal-routes": "Ukrywaj szlaki wewnętrzne"
"btn-hide-internal-routes": "Ukrywaj szlaki wewnętrzne",
"top-list": {
"header": "REKORDY NA SCENERII",
"mode-likes": "OCENA DR",
"mode-dispatchers": "LICZBA DYŻURÓW",
"scope-name": "OGÓLNIE",
"scope-hash": "OBECNY HASH"
}
},
"availability": {
"title": "Dostępność",
+5
View File
@@ -58,6 +58,7 @@ import SceneryDispatchersHistory from '../components/SceneryView/SceneryDispatch
import { useApiStore } from '../store/apiStore';
import { Status } from '../typings/common';
import SceneryTopList from '../components/SceneryView/SceneryTopList.vue';
const route = useRoute();
const router = useRouter();
@@ -89,6 +90,10 @@ const viewModes = [
{
id: 'scenery.option-dispatchers-history',
component: SceneryDispatchersHistory
},
{
id: 'scenery.option-top-list',
component: SceneryTopList
}
];