1 Commits

Author SHA1 Message Date
Spythere 0e888544c1 chore: added view mode menu and journal tab 2025-02-10 14:51:01 +01:00
10 changed files with 222 additions and 37 deletions
@@ -0,0 +1,56 @@
<template>
<div class="text-white">
<!-- <div v-if="apiStore.journalTimetables == null">
<div class="font-bold text-xl">{{ $t('storage-empty-header') }}</div>
<div>{{ $t('storage-empty-info') }}</div>
</div> -->
<div class="font-bold text-2xl p-2 bg-zinc-800">DZIENNIK SRJP</div>
<div v-if="apiStore.journalTimetables == null" class="text-md mt-2 p-2 bg-zinc-900">
Wyszukaj gracza w polu powyżej, aby pokazać jego najnowsze SRJP.
<!-- <div class="text-sm text-red-400 mt-2 flex flex-wrap md:flex-nowrap justify-center items-center gap-1">
<ExclamationCircleIcon class="size-8 min-w-8" />
<span>
Rozkłady z danymi do wygenerowania SRJP dostępne tylko dla wspierających twórczość
<a class="underline hover:text-red-200 transition-colors" href="https://td2.info.pl/profile/?u=20777" target="_blank">@Spythere</a> dla
symulatora TD2!
</span>
</div> -->
</div>
<!-- <div class="font-bold text-xl p-2 bg-zinc-700 mb-3">{{ $t('storage-preview-title') }}</div> -->
<!-- <div class="font-bold p-2 bg-zinc-800 mb-3" v-if="filteredTimetables.length == 0">{{ $t('storage-preview-empty') }}</div> -->
<li v-for="timetable in apiStore.journalTimetables" class="flex gap-1 w-full my-2">
<button class="bg-zinc-900 p-2 w-full cursor-pointer hover:bg-zinc-800 text-left" @click="selectTimetable(timetable.id)">
<div class="text-zinc-300">#{{ timetable.id }} &bull; {{ new Date(timetable.createdAt).toLocaleString() }}</div>
<b>{{ timetable.driverName }} | {{ timetable.trainCategoryCode }} {{ timetable.trainNo }}</b> {{ timetable.route.replace('|', ' > ') }}
</button>
<!-- <button class="bg-zinc-900 p-2 hover:bg-zinc-800" @click="removeTimetable(timetable.timetableId)">
<TrashIcon class="size-5 text-white" />
</button> -->
</li>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { useGlobalStore } from '../../stores/global.store';
import { useApiStore } from '../../stores/api.store';
import { onMounted } from 'vue';
import { ExclamationCircleIcon } from '@heroicons/vue/16/solid';
const globalStore = useGlobalStore();
const apiStore = useApiStore();
const i18n = useI18n();
onMounted(() => {
// apiStore.fetchTimetableHistoryList();
});
function selectTimetable(timetableId: number) {}
</script>
<style scoped></style>
+116 -16
View File
@@ -1,15 +1,56 @@
<template>
<div class="flex gap-2 mb-2">
<button
class="p-1 rounded-md"
:class="{
'bg-zinc-800 hover:bg-zinc-700': globalStore.viewMode == 'active',
'bg-green-600 hover:bg-green-500': globalStore.viewMode == 'storage',
}"
@click="toggleViewMode"
>
<ArchiveBoxArrowDownIcon class="size-6" />
</button>
<div class="relative" @focusin="isMenuOpen = true" @keydown.esc="isMenuOpen = false">
<button
class="p-1 rounded-md flex gap-2"
:class="{
'bg-zinc-800 hover:bg-zinc-700': isMenuOpen == false,
'bg-green-600 hover:bg-green-500': isMenuOpen == true,
}"
@click="isMenuOpen = !isMenuOpen"
>
<Bars3Icon class="size-6" />
</button>
<div class="fixed z-20 left-0 top-0 w-screen h-screen" v-if="isMenuOpen" @click="isMenuOpen = false"></div>
<div class="absolute z-30 top-full left-0 w-36 p-1 mt-2 flex flex-col gap-1 bg-zinc-600 rounded-md" v-if="isMenuOpen">
<button
class="p-1 rounded-md flex gap-2"
:class="{
'bg-zinc-950 hover:bg-zinc-800': globalStore.viewMode != 'active',
'bg-green-600 hover:bg-green-500': globalStore.viewMode == 'active',
}"
@click="toggleViewMode('active')"
>
<WifiIcon class="size-6" /> <span> Aktywne RJ</span>
</button>
<button
class="p-1 rounded-md flex gap-2"
:class="{
'bg-zinc-950 hover:bg-zinc-800': globalStore.viewMode != 'storage',
'bg-green-600 hover:bg-green-500': globalStore.viewMode == 'storage',
}"
@click="toggleViewMode('storage')"
>
<ArchiveBoxArrowDownIcon class="size-6" /> Zapisane RJ
</button>
<button
class="p-1 rounded-md flex gap-2"
:class="{
'bg-zinc-950 hover:bg-zinc-800': globalStore.viewMode != 'journal',
'bg-green-600 hover:bg-green-500': globalStore.viewMode == 'journal',
}"
@click="toggleViewMode('journal')"
>
<CloudArrowDownIcon class="size-6" /> Dziennik RJ
</button>
<!-- <button class="m-0 p-0" @focus="isMenuOpen = false"></button> -->
</div>
</div>
<select
name="trains"
@@ -31,11 +72,31 @@
<input
type="text"
v-if="globalStore.viewMode == 'storage'"
v-model="globalStore.timetableSearch"
v-model="globalStore.storageTimetableSearch"
class="bg-zinc-800 p-1 rounded-md print:hidden w-full"
:placeholder="$t('train-search-placeholder')"
/>
<div v-if="globalStore.viewMode == 'journal'" class="w-full relative">
<input
type="text"
v-model="globalStore.journalTimetableSearch"
class="bg-zinc-800 p-1 rounded-md print:hidden w-full"
:placeholder="$t('journal-search-placeholder')"
@keydown.enter="fetchJournalTrain"
/>
<div class="absolute top-0 right-0">
<button class="bg-zinc-800 p-1 rounded-md hover:bg-zinc-700">
<MagnifyingGlassIcon class="text-white size-6" />
</button>
<button class="bg-zinc-800 p-1 rounded-md hover:bg-zinc-700">
<XMarkIcon class="text-white size-6" />
</button>
</div>
</div>
<button class="bg-zinc-800 p-1 rounded-md hover:bg-zinc-700" @click="toggleDarkMode">
<MoonIcon v-if="globalStore.darkMode" class="text-white size-6" />
<SunIcon v-else class="text-white size-6" />
@@ -64,18 +125,31 @@
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { computed, ref } from 'vue';
import { useApiStore } from '../../stores/api.store';
import { DataStatus } from '../../types/api.types';
import { useGlobalStore } from '../../stores/global.store';
import { PrinterIcon, MoonIcon, SunIcon, ArchiveBoxArrowDownIcon, ArrowDownTrayIcon } from '@heroicons/vue/16/solid';
import {
PrinterIcon,
MoonIcon,
SunIcon,
ArchiveBoxArrowDownIcon,
ArrowDownTrayIcon,
CloudArrowDownIcon,
WifiIcon,
XMarkIcon,
MagnifyingGlassIcon,
Bars3Icon,
} from '@heroicons/vue/16/solid';
import { getRegionNameById } from '../../utils/trainUtils';
import type { TimetableData } from '../../types/common.types';
import type { TimetableData, ViewMode } from '../../types/common.types';
// Stores
const apiStore = useApiStore();
const globalStore = useGlobalStore();
const isMenuOpen = ref(false);
// Computed
const isTimetableSaved = computed(() => {
if (!globalStore.currentTimetableData) return false;
@@ -94,8 +168,34 @@ function selectTrain() {
}
}
function toggleViewMode() {
globalStore.viewMode = globalStore.viewMode == 'active' ? 'storage' : 'active';
function fetchJournalTrain(e: Event) {
e.preventDefault();
console.log(globalStore.journalTimetableSearch);
}
function toggleViewMode(viewMode: ViewMode) {
if (viewMode == globalStore.viewMode) {
switch (viewMode) {
case 'active':
globalStore.selectedActiveTrain = null;
break;
case 'storage':
globalStore.selectedStorageTimetable = null;
break;
case 'journal':
globalStore.selectedJournalTimetable = null;
break;
default:
break;
}
}
isMenuOpen.value = false;
globalStore.viewMode = viewMode;
}
function toggleDarkMode() {
@@ -1,12 +1,12 @@
<template>
<div class="text-white">
<div v-if="globalStore.selectedStorageTimetable == null && Object.keys(globalStore.storageTimetables).length == 0">
<div class="font-bold text-xl">{{ $t('storage-empty-header') }}</div>
<div>{{ $t('storage-empty-info') }}</div>
<div class="font-bold text-2xl p-1 bg-zinc-800">{{ $t('storage-empty-header') }}</div>
<div class="text-md mt-2 p-2 bg-zinc-900">{{ $t('storage-empty-info') }}</div>
</div>
<div v-else>
<div class="font-bold text-xl p-2 bg-zinc-700 mb-3">{{ $t('storage-preview-title') }}</div>
<div class="font-bold text-2xl p-2 bg-zinc-800 mb-3">{{ $t('storage-preview-title') }}</div>
<div class="font-bold p-2 bg-zinc-800 mb-3" v-if="filteredTimetables.length == 0">{{ $t('storage-preview-empty') }}</div>
<li v-for="timetable in filteredTimetables" class="flex gap-1 w-full my-2">
@@ -36,11 +36,11 @@ const i18n = useI18n();
const filteredTimetables = computed(() => {
let timetables = Object.values(globalStore.storageTimetables);
if (globalStore.timetableSearch.length != 0)
if (globalStore.storageTimetableSearch.length != 0)
timetables = timetables.filter((st) =>
`${st.timetableId} ${st.driverName} ${st.route} ${st.category} ${st.trainNo}`
.toLocaleLowerCase()
.includes(globalStore.timetableSearch.toLocaleLowerCase())
.includes(globalStore.storageTimetableSearch.toLocaleLowerCase())
);
timetables.sort((a, b) => {
+4 -4
View File
@@ -19,12 +19,11 @@
<div class="overflow-auto text-center font-bold text-zinc-400 p-1 min-h-full" v-else>
<div v-if="globalStore.viewMode == 'active'">
<div>{{ $t('train-select-info') }}</div>
<div class="text-xl p-2 bg-zinc-900">{{ $t('train-select-info') }}</div>
</div>
<div v-else>
<TimetableStorage />
</div>
<TimetableStorage v-else-if="globalStore.viewMode == 'storage'" />
<TimetableJournal v-else-if="globalStore.viewMode == 'journal'" />
</div>
</template>
@@ -37,6 +36,7 @@ import TimetableBody from './TimetableBody.vue';
import TimetableHeader from './TimetableHeader.vue';
import type { SceneryRoute, StopRow, TimetablePathData } from '../../types/common.types';
import TimetableStorage from './TimetableStorage.vue';
import TimetableJournal from './TimetableJournal.vue';
const globalStore = useGlobalStore();
const apiStore = useApiStore();
+2 -1
View File
@@ -22,5 +22,6 @@
"storage-preview-empty": "No entries found for given parameters",
"storage-preview-info": "Archived timetable {id} for user {driverName} from: {date}",
"storage-preview-button-text": "Return",
"delete-timetable-confirm": "Are you sure that you want to delete this timetable?"
"delete-timetable-confirm": "Are you sure that you want to delete this timetable?",
"journal-search-placeholder": "Driver nickname"
}
+2 -1
View File
@@ -22,5 +22,6 @@
"storage-preview-empty": "Nie znaleziono żadnych wpisów dla podanych parametrów",
"storage-preview-info": "Rozkład archiwalny {id} maszynisty {driverName} z dnia {date}",
"storage-preview-button-text": "Powróć",
"delete-timetable-confirm": "Czy na pewno chcesz usunąć ten rozkład jazdy z archiwum?"
"delete-timetable-confirm": "Czy na pewno chcesz usunąć ten rozkład jazdy z archiwum?",
"journal-search-placeholder": "Nick maszynisty"
}
+24 -3
View File
@@ -1,8 +1,8 @@
import type { AxiosInstance } from 'axios';
import axios from 'axios';
import { defineStore } from 'pinia';
import { DataStatus, type ActiveDataResponse, type SceneriesDataResponse } from '../types/api.types';
import type { ActiveData, SceneryData } from '../types/common.types';
import { DataStatus, type ActiveDataResponse, type JournalTimetableShortResponse, type SceneriesDataResponse } from '../types/api.types';
import type { ActiveData, JournalTimetableShort, SceneryData } from '../types/common.types';
export const useApiStore = defineStore('api', {
state() {
@@ -12,6 +12,8 @@ export const useApiStore = defineStore('api', {
activeData: null as ActiveData | null,
sceneryData: null as SceneryData[] | null,
journalTimetables: null as JournalTimetableShort[] | null,
outdatedTimerId: -1,
isActiveDataOutdated: false,
@@ -43,7 +45,6 @@ export const useApiStore = defineStore('api', {
this.fetchSceneriesData();
await this.fetchActiveData();
setInterval(() => {
this.fetchActiveData();
}, 25000);
@@ -76,5 +77,25 @@ export const useApiStore = defineStore('api', {
console.error(error);
}
},
async fetchTimetableHistoryList() {
try {
const response = (
await this.client!.get<JournalTimetableShortResponse>('/api/getTimetables', {
params: {
driverName: 'Spythere',
returnType: 'short',
hasStopsDetails: 1
},
})
).data;
this.journalTimetables = response;
// this.sceneryData = response;
} catch (error) {
console.error(error);
}
},
},
});
+7 -4
View File
@@ -11,6 +11,8 @@ export const useGlobalStore = defineStore('global', {
selectedTrainId: null as string | null,
selectedActiveTrain: null as ActiveTrain | null,
selectedStorageTimetable: null as TimetableData | null,
selectedJournalTimetable: null,
storageTimetables: {} as Record<number, TimetableData>,
timetableWarnings: [] as string[],
@@ -18,7 +20,8 @@ export const useGlobalStore = defineStore('global', {
generatedDate: null as Date | null,
generatedMs: 0,
timetableSearch: '',
storageTimetableSearch: '',
journalTimetableSearch: '',
showSettings: false,
}),
@@ -52,7 +55,7 @@ export const useGlobalStore = defineStore('global', {
trainMaxSpeed: selectedTrain.timetable.trainMaxSpeed,
timetableId: selectedTrain.timetable.timetableId,
stopListString: selectedTrain.timetable.stopList
.filter((stop) => stop.mainStop || (/^podg|po|pe$/.test(stop.stopNameRAW)))
.filter((stop) => stop.mainStop || /^podg|po|pe$/.test(stop.stopNameRAW))
.map(
(stop) =>
`${stop.arrivalLine ?? ''};${stop.arrivalTimestamp};${stop.stopNameRAW};${stop.stopTime ? stop.stopTime + '_' + stop.stopType : ''};${
@@ -70,10 +73,10 @@ export const useGlobalStore = defineStore('global', {
return unitNameCorrections[unitName] ?? unitName;
}),
};
} else {
} else if (this.viewMode == 'storage') {
const selectedStorageTimetable = this.selectedStorageTimetable;
return selectedStorageTimetable;
}
} else return null;
},
},
actions: {},
+4 -1
View File
@@ -1,9 +1,12 @@
import type { ActiveData, SceneryData } from './common.types';
import type { ActiveData, JournalTimetableDetailed, JournalTimetableShort, SceneryData } from './common.types';
export type ActiveDataResponse = ActiveData;
export type SceneriesDataResponse = SceneryData[];
export type JournalTimetableShortResponse = JournalTimetableShort[];
export type JournalTimetableDetailedResponse = JournalTimetableDetailed[];
export enum DataStatus {
'LOADING' = 0,
'SUCCESS' = 1,
+2 -2
View File
@@ -1,4 +1,4 @@
export type ViewMode = 'active' | 'storage';
export type ViewMode = 'active' | 'storage' | 'journal';
export interface ActiveData {
trains: ActiveTrain[];
@@ -240,7 +240,7 @@ export interface JournalTimetableDetailed extends JournalTimetableShort {
warningNotes: string;
hasDangerousCargo: boolean;
hasExtraDeliveries: boolean;
stopListString: any;
stopListString?: string;
}
export interface TimetableData {