chore: added view mode menu and journal tab

This commit is contained in:
2025-02-10 14:51:01 +01:00
parent 56dcca3d5b
commit 0e888544c1
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> <template>
<div class="flex gap-2 mb-2"> <div class="flex gap-2 mb-2">
<button <div class="relative" @focusin="isMenuOpen = true" @keydown.esc="isMenuOpen = false">
class="p-1 rounded-md" <button
:class="{ class="p-1 rounded-md flex gap-2"
'bg-zinc-800 hover:bg-zinc-700': globalStore.viewMode == 'active', :class="{
'bg-green-600 hover:bg-green-500': globalStore.viewMode == 'storage', 'bg-zinc-800 hover:bg-zinc-700': isMenuOpen == false,
}" 'bg-green-600 hover:bg-green-500': isMenuOpen == true,
@click="toggleViewMode" }"
> @click="isMenuOpen = !isMenuOpen"
<ArchiveBoxArrowDownIcon class="size-6" /> >
</button> <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 <select
name="trains" name="trains"
@@ -31,11 +72,31 @@
<input <input
type="text" type="text"
v-if="globalStore.viewMode == 'storage'" 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" class="bg-zinc-800 p-1 rounded-md print:hidden w-full"
:placeholder="$t('train-search-placeholder')" :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"> <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" /> <MoonIcon v-if="globalStore.darkMode" class="text-white size-6" />
<SunIcon v-else class="text-white size-6" /> <SunIcon v-else class="text-white size-6" />
@@ -64,18 +125,31 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed, ref } from 'vue';
import { useApiStore } from '../../stores/api.store'; import { useApiStore } from '../../stores/api.store';
import { DataStatus } from '../../types/api.types'; import { DataStatus } from '../../types/api.types';
import { useGlobalStore } from '../../stores/global.store'; 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 { getRegionNameById } from '../../utils/trainUtils';
import type { TimetableData } from '../../types/common.types'; import type { TimetableData, ViewMode } from '../../types/common.types';
// Stores // Stores
const apiStore = useApiStore(); const apiStore = useApiStore();
const globalStore = useGlobalStore(); const globalStore = useGlobalStore();
const isMenuOpen = ref(false);
// Computed // Computed
const isTimetableSaved = computed(() => { const isTimetableSaved = computed(() => {
if (!globalStore.currentTimetableData) return false; if (!globalStore.currentTimetableData) return false;
@@ -94,8 +168,34 @@ function selectTrain() {
} }
} }
function toggleViewMode() { function fetchJournalTrain(e: Event) {
globalStore.viewMode = globalStore.viewMode == 'active' ? 'storage' : 'active'; 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() { function toggleDarkMode() {
@@ -1,12 +1,12 @@
<template> <template>
<div class="text-white"> <div class="text-white">
<div v-if="globalStore.selectedStorageTimetable == null && Object.keys(globalStore.storageTimetables).length == 0"> <div v-if="globalStore.selectedStorageTimetable == null && Object.keys(globalStore.storageTimetables).length == 0">
<div class="font-bold text-xl">{{ $t('storage-empty-header') }}</div> <div class="font-bold text-2xl p-1 bg-zinc-800">{{ $t('storage-empty-header') }}</div>
<div>{{ $t('storage-empty-info') }}</div> <div class="text-md mt-2 p-2 bg-zinc-900">{{ $t('storage-empty-info') }}</div>
</div> </div>
<div v-else> <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> <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"> <li v-for="timetable in filteredTimetables" class="flex gap-1 w-full my-2">
@@ -36,11 +36,11 @@ const i18n = useI18n();
const filteredTimetables = computed(() => { const filteredTimetables = computed(() => {
let timetables = Object.values(globalStore.storageTimetables); let timetables = Object.values(globalStore.storageTimetables);
if (globalStore.timetableSearch.length != 0) if (globalStore.storageTimetableSearch.length != 0)
timetables = timetables.filter((st) => timetables = timetables.filter((st) =>
`${st.timetableId} ${st.driverName} ${st.route} ${st.category} ${st.trainNo}` `${st.timetableId} ${st.driverName} ${st.route} ${st.category} ${st.trainNo}`
.toLocaleLowerCase() .toLocaleLowerCase()
.includes(globalStore.timetableSearch.toLocaleLowerCase()) .includes(globalStore.storageTimetableSearch.toLocaleLowerCase())
); );
timetables.sort((a, b) => { 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 class="overflow-auto text-center font-bold text-zinc-400 p-1 min-h-full" v-else>
<div v-if="globalStore.viewMode == 'active'"> <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>
<div v-else> <TimetableStorage v-else-if="globalStore.viewMode == 'storage'" />
<TimetableStorage /> <TimetableJournal v-else-if="globalStore.viewMode == 'journal'" />
</div>
</div> </div>
</template> </template>
@@ -37,6 +36,7 @@ import TimetableBody from './TimetableBody.vue';
import TimetableHeader from './TimetableHeader.vue'; import TimetableHeader from './TimetableHeader.vue';
import type { SceneryRoute, StopRow, TimetablePathData } from '../../types/common.types'; import type { SceneryRoute, StopRow, TimetablePathData } from '../../types/common.types';
import TimetableStorage from './TimetableStorage.vue'; import TimetableStorage from './TimetableStorage.vue';
import TimetableJournal from './TimetableJournal.vue';
const globalStore = useGlobalStore(); const globalStore = useGlobalStore();
const apiStore = useApiStore(); const apiStore = useApiStore();
+2 -1
View File
@@ -22,5 +22,6 @@
"storage-preview-empty": "No entries found for given parameters", "storage-preview-empty": "No entries found for given parameters",
"storage-preview-info": "Archived timetable {id} for user {driverName} from: {date}", "storage-preview-info": "Archived timetable {id} for user {driverName} from: {date}",
"storage-preview-button-text": "Return", "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-empty": "Nie znaleziono żadnych wpisów dla podanych parametrów",
"storage-preview-info": "Rozkład archiwalny {id} maszynisty {driverName} z dnia {date}", "storage-preview-info": "Rozkład archiwalny {id} maszynisty {driverName} z dnia {date}",
"storage-preview-button-text": "Powróć", "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 type { AxiosInstance } from 'axios';
import axios from 'axios'; import axios from 'axios';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { DataStatus, type ActiveDataResponse, type SceneriesDataResponse } from '../types/api.types'; import { DataStatus, type ActiveDataResponse, type JournalTimetableShortResponse, type SceneriesDataResponse } from '../types/api.types';
import type { ActiveData, SceneryData } from '../types/common.types'; import type { ActiveData, JournalTimetableShort, SceneryData } from '../types/common.types';
export const useApiStore = defineStore('api', { export const useApiStore = defineStore('api', {
state() { state() {
@@ -12,6 +12,8 @@ export const useApiStore = defineStore('api', {
activeData: null as ActiveData | null, activeData: null as ActiveData | null,
sceneryData: null as SceneryData[] | null, sceneryData: null as SceneryData[] | null,
journalTimetables: null as JournalTimetableShort[] | null,
outdatedTimerId: -1, outdatedTimerId: -1,
isActiveDataOutdated: false, isActiveDataOutdated: false,
@@ -43,7 +45,6 @@ export const useApiStore = defineStore('api', {
this.fetchSceneriesData(); this.fetchSceneriesData();
await this.fetchActiveData(); await this.fetchActiveData();
setInterval(() => { setInterval(() => {
this.fetchActiveData(); this.fetchActiveData();
}, 25000); }, 25000);
@@ -76,5 +77,25 @@ export const useApiStore = defineStore('api', {
console.error(error); 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, selectedTrainId: null as string | null,
selectedActiveTrain: null as ActiveTrain | null, selectedActiveTrain: null as ActiveTrain | null,
selectedStorageTimetable: null as TimetableData | null, selectedStorageTimetable: null as TimetableData | null,
selectedJournalTimetable: null,
storageTimetables: {} as Record<number, TimetableData>, storageTimetables: {} as Record<number, TimetableData>,
timetableWarnings: [] as string[], timetableWarnings: [] as string[],
@@ -18,7 +20,8 @@ export const useGlobalStore = defineStore('global', {
generatedDate: null as Date | null, generatedDate: null as Date | null,
generatedMs: 0, generatedMs: 0,
timetableSearch: '', storageTimetableSearch: '',
journalTimetableSearch: '',
showSettings: false, showSettings: false,
}), }),
@@ -52,7 +55,7 @@ export const useGlobalStore = defineStore('global', {
trainMaxSpeed: selectedTrain.timetable.trainMaxSpeed, trainMaxSpeed: selectedTrain.timetable.trainMaxSpeed,
timetableId: selectedTrain.timetable.timetableId, timetableId: selectedTrain.timetable.timetableId,
stopListString: selectedTrain.timetable.stopList 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( .map(
(stop) => (stop) =>
`${stop.arrivalLine ?? ''};${stop.arrivalTimestamp};${stop.stopNameRAW};${stop.stopTime ? stop.stopTime + '_' + stop.stopType : ''};${ `${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; return unitNameCorrections[unitName] ?? unitName;
}), }),
}; };
} else { } else if (this.viewMode == 'storage') {
const selectedStorageTimetable = this.selectedStorageTimetable; const selectedStorageTimetable = this.selectedStorageTimetable;
return selectedStorageTimetable; return selectedStorageTimetable;
} } else return null;
}, },
}, },
actions: {}, 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 ActiveDataResponse = ActiveData;
export type SceneriesDataResponse = SceneryData[]; export type SceneriesDataResponse = SceneryData[];
export type JournalTimetableShortResponse = JournalTimetableShort[];
export type JournalTimetableDetailedResponse = JournalTimetableDetailed[];
export enum DataStatus { export enum DataStatus {
'LOADING' = 0, 'LOADING' = 0,
'SUCCESS' = 1, 'SUCCESS' = 1,
+2 -2
View File
@@ -1,4 +1,4 @@
export type ViewMode = 'active' | 'storage'; export type ViewMode = 'active' | 'storage' | 'journal';
export interface ActiveData { export interface ActiveData {
trains: ActiveTrain[]; trains: ActiveTrain[];
@@ -240,7 +240,7 @@ export interface JournalTimetableDetailed extends JournalTimetableShort {
warningNotes: string; warningNotes: string;
hasDangerousCargo: boolean; hasDangerousCargo: boolean;
hasExtraDeliveries: boolean; hasExtraDeliveries: boolean;
stopListString: any; stopListString?: string;
} }
export interface TimetableData { export interface TimetableData {