chore: fetching data

This commit is contained in:
2025-04-29 01:55:09 +02:00
parent 26e348b0be
commit b3ee8bd119
12 changed files with 270 additions and 162 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ define(['./workbox-99d8380f'], (function (workbox) { 'use strict';
*/ */
workbox.precacheAndRoute([{ workbox.precacheAndRoute([{
"url": "index.html", "url": "index.html",
"revision": "0.ah6c8rh4s38" "revision": "0.2ul174kd9vo"
}], {}); }], {});
workbox.cleanupOutdatedCaches(); workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
+2 -2
View File
@@ -1,13 +1,13 @@
<template> <template>
<main class="grid print:block p-3 mx-auto max-w-[800px] h-[calc(100vh-40px)] min-h-[300px] grid-rows-[auto_auto_1fr] gap-1"> <main class="grid print:block p-3 mx-auto max-w-[800px] h-[calc(100vh-40px)] min-h-[300px] grid-rows-[auto_auto_1fr] gap-1">
<TimetableSelect /> <SearchContainer />
<TimetableWarnings /> <TimetableWarnings />
<TrainTimetable /> <TrainTimetable />
</main> </main>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import TimetableSelect from '../Timetable/TimetableSelect.vue';
import TimetableWarnings from "../Timetable/TimetableWarnings.vue"; import TimetableWarnings from "../Timetable/TimetableWarnings.vue";
import TrainTimetable from '../Timetable/TrainTimetable.vue'; import TrainTimetable from '../Timetable/TrainTimetable.vue';
import SearchContainer from "../TimetableSearch/SearchContainer.vue";
</script> </script>
@@ -0,0 +1,41 @@
<template>
<select
v-model="globalStore.selectedTrainId"
@change="selectTrain"
name="trains"
id="trains-select"
class="bg-zinc-800 p-1 rounded-md print:hidden w-full"
:disabled="apiStore.activeDataStatus != DataStatus.SUCCESS"
>
<option :value="null" disabled>
{{
apiStore.activeDataStatus == DataStatus.LOADING
? $t('data-loading-text')
: $t('train-select-placeholder')
}}
</option>
<option :value="train.id" v-for="train in globalStore.activeTimetableTrains">
{{ train.driverName }} | {{ train.timetable?.category }} {{ train.trainNo }} [{{
getRegionNameById(train.region)
}}]
</option>
</select>
</template>
<script setup lang="ts">
import { useApiStore } from '../../stores/api.store';
import { useGlobalStore } from '../../stores/global.store';
import { DataStatus } from '../../types/api.types';
import { getRegionNameById } from '../../utils/trainUtils';
const apiStore = useApiStore();
const globalStore = useGlobalStore();
function selectTrain() {
if (!apiStore.activeData) return;
globalStore.selectedActiveTrain =
globalStore.activeTimetableTrains.find((train) => train.id == globalStore.selectedTrainId) ??
null;
}
</script>
@@ -0,0 +1,130 @@
<template>
<div class="flex gap-2 items-center">
<div class="flex gap-2 w-full">
<input
v-model="globalStore.journalTimetableSearch.driverName"
type="text"
@keydown.enter="fetchJournalTimetables"
:class="`bg-zinc-800 p-1 rounded-md print:hidden w-full ${
apiStore.connectionMode == 'offline' ? 'opacity-35' : ''
}`"
:disabled="
apiStore.journalDataStatus == DataStatus.LOADING || apiStore.connectionMode == 'offline'
"
:placeholder="$t('journal-driver-search-placeholder')"
/>
<input
v-model="globalStore.journalTimetableSearch.route"
type="text"
@keydown.enter="fetchJournalTimetables"
:class="`bg-zinc-800 p-1 rounded-md print:hidden w-full ${
apiStore.connectionMode == 'offline' ? 'opacity-35' : ''
}`"
:disabled="
apiStore.journalDataStatus == DataStatus.LOADING || apiStore.connectionMode == 'offline'
"
:placeholder="$t('journal-route-search-placeholder')"
/>
<input
v-model="globalStore.journalTimetableSearch.date"
type="date"
@keydown.enter="fetchJournalTimetables"
:class="`bg-zinc-800 p-1 rounded-md print:hidden w-full ${
apiStore.connectionMode == 'offline' ? 'opacity-35' : ''
}`"
:disabled="
apiStore.journalDataStatus == DataStatus.LOADING || apiStore.connectionMode == 'offline'
"
:placeholder="$t('journal-date-search-placeholder')"
/>
</div>
<button
class="bg-zinc-800 hover:bg-zinc-700 p-1 rounded-md"
v-if="globalStore.viewMode == 'journal'"
@click="clearSearch"
>
<TrashIcon class="size-6" />
</button>
<button
class="bg-zinc-800 hover:bg-zinc-700 p-1 rounded-md"
v-if="globalStore.viewMode == 'journal'"
@click="fetchJournalTimetables"
>
<ArrowRightCircleIcon class="size-6" />
</button>
</div>
</template>
<script setup lang="ts">
import { ArrowRightCircleIcon, TrashIcon } from '@heroicons/vue/16/solid';
import { useApiStore } from '../../stores/api.store';
import { useGlobalStore } from '../../stores/global.store';
import { DataStatus, type JournalTimetablesShortResponse } from '../../types/api.types';
const globalStore = useGlobalStore();
const apiStore = useApiStore();
async function fetchJournalTimetables() {
const searchValues = globalStore.journalTimetableSearch;
let fetchParams: Record<string, any> = {};
if (searchValues['driverName']) {
fetchParams['driverName'] = searchValues['driverName'].trim();
}
if (searchValues['date']) {
let dateFromStr = new Date(searchValues['date']).toISOString();
let dateTo = new Date(dateFromStr);
dateTo.setDate(dateTo.getDate() + 1);
fetchParams['dateFrom'] = dateFromStr;
fetchParams['dateTo'] = dateTo.toISOString();
}
if (searchValues['route']) {
const [routeFrom, routeTo] = searchValues['route'].split('-');
if (routeFrom) {
fetchParams['issuedFrom'] = routeFrom.trim();
}
if (routeTo) {
fetchParams['terminatingAt'] = routeTo.trim();
}
}
fetchParams['hasStopsDetails'] = 1;
fetchParams['returnType'] = 'short';
try {
apiStore.journalDataStatus = DataStatus.LOADING;
const response = (
await apiStore.client!.get<JournalTimetablesShortResponse>('/api/getTimetables', {
params: fetchParams
})
).data;
apiStore.journalDataStatus = DataStatus.SUCCESS;
apiStore.journalTimetablesData = response;
} catch (error) {
apiStore.journalDataStatus = DataStatus.ERROR;
apiStore.journalTimetablesData = null;
console.error(error);
}
}
function clearSearch() {
Object.keys(globalStore.journalTimetableSearch).forEach(
(k) => ((globalStore.journalTimetableSearch as any)[k] = '')
);
apiStore.journalTimetablesData = null;
}
</script>
@@ -0,0 +1,33 @@
<template>
<div class="flex gap-2">
<div class="w-full">
<input
v-model="globalStore.localTimetableSearch"
type="text"
class="bg-zinc-800 p-1 rounded-md print:hidden w-full"
:placeholder="$t('train-search-placeholder')"
/>
</div>
<div>
<button
class="bg-zinc-800 hover:bg-zinc-700 p-1 rounded-md"
v-if="globalStore.viewMode == 'storage'"
@click="clearSearch"
>
<TrashIcon class="size-6" />
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { TrashIcon } from '@heroicons/vue/16/solid';
import { useGlobalStore } from '../../stores/global.store';
const globalStore = useGlobalStore();
function clearSearch() {
globalStore.localTimetableSearch = '';
}
</script>
@@ -0,0 +1,26 @@
<template>
<div class="flex gap-2 flex-col mb-2 print:hidden">
<!-- Top Actions & Modes -->
<SearchModeActions />
<!-- Active Data Search -->
<ActiveSearchInput v-if="globalStore.viewMode == 'active'" />
<!-- Local Storage Search -->
<LocalSearchInput v-else-if="globalStore.viewMode == 'storage'" />
<!-- Journal Serach -->
<JournalSearchInput v-else />
</div>
</template>
<script setup lang="ts">
import { useGlobalStore } from '../../stores/global.store';
import ActiveSearchInput from './ActiveSearchInput.vue';
import JournalSearchInput from './JournalSearchInput.vue';
import LocalSearchInput from './LocalSearchInput.vue';
import SearchModeActions from './SearchModeActions.vue';
const globalStore = useGlobalStore();
</script>
@@ -1,5 +1,5 @@
<template> <template>
<div class="flex gap-2 justify-between flex-wrap mb-2 print:hidden"> <div class="flex justify-between gap-2">
<div class="flex gap-2"> <div class="flex gap-2">
<button <button
:class="`p-1 rounded-md ${ :class="`p-1 rounded-md ${
@@ -61,62 +61,10 @@
<ArrowDownTrayIcon class="text-white size-6" /> <ArrowDownTrayIcon class="text-white size-6" />
</button> </button>
</div> </div>
<!-- Active Data Search -->
<select
name="trains"
id="trains-select"
class="bg-zinc-800 p-1 rounded-md print:hidden w-full"
:disabled="apiStore.activeDataStatus != DataStatus.SUCCESS"
v-model="globalStore.selectedTrainId"
v-if="globalStore.viewMode == 'active'"
@change="selectTrain"
>
<option :value="null" disabled>
{{
apiStore.activeDataStatus == DataStatus.LOADING
? $t('data-loading-text')
: $t('train-select-placeholder')
}}
</option>
<option :value="train.id" v-for="train in globalStore.activeTimetableTrains">
{{ train.driverName }} | {{ train.timetable?.category }} {{ train.trainNo }} [{{
getRegionNameById(train.region)
}}]
</option>
</select>
<!-- Local Storage Search -->
<input
type="text"
v-if="globalStore.viewMode == 'storage'"
v-model="globalStore.localTimetableSearch"
class="bg-zinc-800 p-1 rounded-md print:hidden w-full"
:placeholder="$t('train-search-placeholder')"
/>
<!-- Journal Serach -->
<input
type="text"
v-else-if="globalStore.viewMode == 'journal'"
@change="fetchJournalTimetables"
v-model="globalStore.journalTimetableSearch"
:class="`bg-zinc-800 p-1 rounded-md print:hidden w-full ${
apiStore.connectionMode == 'offline' ? 'opacity-35' : ''
}`"
:disabled="
apiStore.journalDataStatus == DataStatus.LOADING || apiStore.connectionMode == 'offline'
"
:placeholder="$t('journal-search-placeholder')"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue';
import { useApiStore } from '../../stores/api.store';
import { DataStatus } from '../../types/api.types';
import { useGlobalStore } from '../../stores/global.store';
import { import {
PrinterIcon, PrinterIcon,
MoonIcon, MoonIcon,
@@ -126,12 +74,10 @@ import {
CloudIcon, CloudIcon,
WifiIcon WifiIcon
} from '@heroicons/vue/16/solid'; } from '@heroicons/vue/16/solid';
import { getRegionNameById } from '../../utils/trainUtils'; import { computed, watch } from 'vue';
import type { TimetableData, ViewMode } from '../../types/common.types'; import { useGlobalStore } from '../../stores/global.store';
import { watch } from 'vue'; import type { ViewMode, TimetableData } from '../../types/common.types';
// Stores
const apiStore = useApiStore();
const globalStore = useGlobalStore(); const globalStore = useGlobalStore();
// Computed // Computed
@@ -154,14 +100,6 @@ watch(
); );
// Methods // Methods
function selectTrain() {
if (!apiStore.activeData) return;
globalStore.selectedActiveTrain =
globalStore.activeTimetableTrains.find((train) => train.id == globalStore.selectedTrainId) ??
null;
}
function toggleViewMode(viewMode: ViewMode) { function toggleViewMode(viewMode: ViewMode) {
globalStore.viewMode = viewMode; globalStore.viewMode = viewMode;
} }
@@ -210,13 +148,9 @@ function openPrintingWindow() {
document.title = `${globalStore.selectedActiveTrain.driverName} ; ${ document.title = `${globalStore.selectedActiveTrain.driverName} ; ${
globalStore.selectedActiveTrain.timetable!.category globalStore.selectedActiveTrain.timetable!.category
} ${globalStore.selectedActiveTrain.trainNo} } ${globalStore.selectedActiveTrain.trainNo}
${globalStore.selectedActiveTrain.timetable?.route.replace('|', ' - ')} ; ${date}`; ${globalStore.selectedActiveTrain.timetable?.route.replace('|', ' - ')} ; ${date}`;
} }
window.print(); window.print();
} }
async function fetchJournalTimetables() {
apiStore.fetchJournalTimetables(globalStore.journalTimetableSearch);
}
</script> </script>
@@ -66,11 +66,29 @@
<script setup lang="ts"> <script setup lang="ts">
import { useApiStore } from '../../stores/api.store'; import { useApiStore } from '../../stores/api.store';
import { useGlobalStore } from '../../stores/global.store';
import { DataStatus } from '../../types/api.types'; import { DataStatus } from '../../types/api.types';
import type { JournalTimetableDetailed } from '../../types/common.types';
const apiStore = useApiStore(); const apiStore = useApiStore();
const globalStore = useGlobalStore();
function fetchTimetableDetails(id: number) { async function fetchTimetableDetails(id: number) {
apiStore.fetchJournalTimetableDetails(id); try {
const response = (
await apiStore.client!.get<JournalTimetableDetailed[]>('/api/getTimetables', {
params: {
timetableId: id,
hasStopsDetails: 1,
returnType: "detailed"
}
})
).data;
if (response.length > 0) globalStore.selectedJournalTimetable = response[0];
} catch (error) {
globalStore.selectedJournalTimetable = null;
console.error(error);
}
} }
</script> </script>
+3 -1
View File
@@ -36,7 +36,9 @@
"journal-preview-title": "DZIENNIK ROZKŁADÓW JAZDY", "journal-preview-title": "DZIENNIK ROZKŁADÓW JAZDY",
"journal-empty-info": "Wpisz dane rozkładu korzystając z pola tekstowego powyżej - mogą nimi być: <br> id (#numer); nickname (nick:Spythere); data (date:11.01.2025); punkt startowy (from:Krnów)<br>W przypadku wielu rozkładów jazdy wyświetli się maks. 15 najnowszych.", "journal-empty-info": "Wpisz dane rozkładu korzystając z pola tekstowego powyżej - mogą nimi być: <br> id (#numer); nickname (nick:Spythere); data (date:11.01.2025); punkt startowy (from:Krnów)<br>W przypadku wielu rozkładów jazdy wyświetli się maks. 15 najnowszych.",
"journal-search-placeholder": "nick:Spythere date:02.04.2025 from:Krnów to:Biała_Sudecka", "journal-driver-search-placeholder": "Maszynista / #ID",
"journal-date-search-placeholder": "Data",
"journal-route-search-placeholder": "Relacja",
"journal-preview-info": "Rozkład historyczny {id} maszynisty {driverName} z dnia {date}", "journal-preview-info": "Rozkład historyczny {id} maszynisty {driverName} z dnia {date}",
"journal-no-data": "Brak wyników dla obecnego wyszukiwania! Sprawdź czy wpisałeś poprawnie dane.", "journal-no-data": "Brak wyników dla obecnego wyszukiwania! Sprawdź czy wpisałeś poprawnie dane.",
+2 -77
View File
@@ -4,16 +4,9 @@ import { defineStore } from 'pinia';
import { import {
DataStatus, DataStatus,
type ActiveDataResponse, type ActiveDataResponse,
type SceneriesDataResponse, type SceneriesDataResponse
type JournalTimetablesShortResponse
} from '../types/api.types'; } from '../types/api.types';
import type { import type { ActiveData, JournalTimetableShort, SceneryData } from '../types/common.types';
ActiveData,
JournalTimetableDetailed,
JournalTimetableShort,
SceneryData
} from '../types/common.types';
import { useGlobalStore } from './global.store';
let activeDataInterval = -1; let activeDataInterval = -1;
@@ -93,74 +86,6 @@ export const useApiStore = defineStore('api', {
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
},
async fetchJournalTimetables(searchValue: string) {
// if (searchValue.trim().length == 0) {
// this.journalDataStatus = DataStatus.SUCCESS;
// this.journalTimetablesData = null;
// return;
// }
let searchObj: Record<string, any> = {};
const searchParams = searchValue.split(' ');
searchParams.forEach((param) => {
const [key, value] = param.split(':');
if (key == 'nick') searchObj['driverName'] = value;
else if (key == 'date') {
let dateFromStr = new Date(value).toISOString();
let dateTo = new Date(dateFromStr);
dateTo.setDate(dateTo.getDate() + 1);
searchObj['dateFrom'] = dateFromStr;
searchObj['dateTo'] = dateTo.toISOString();
} else if (key == 'from') searchObj['issuedFrom'] = value.replace(/_/g, ' ');
else if (key == 'to') searchObj['terminatingAt'] = value.replace(/_/g, ' ');
});
searchObj['hasStopsDetails'] = 1;
searchObj['returnType'] = 'short';
try {
this.journalDataStatus = DataStatus.LOADING;
const response = (
await this.client!.get<JournalTimetablesShortResponse>('/api/getTimetables', {
params: searchObj
})
).data;
this.journalDataStatus = DataStatus.SUCCESS;
this.journalTimetablesData = response;
} catch (error) {
this.journalDataStatus = DataStatus.ERROR;
this.journalTimetablesData = null;
console.error(error);
}
},
async fetchJournalTimetableDetails(id: number) {
const globalStore = useGlobalStore();
try {
const response = (
await this.client!.get<JournalTimetableDetailed[]>('/api/getTimetables', {
params: {
timetableId: id,
hasStopsDetails: 1
}
})
).data;
if (response.length > 0) globalStore.selectedJournalTimetable = response[0];
} catch (error) {
globalStore.selectedJournalTimetable = null;
console.error(error);
}
} }
} }
}); });
+6 -1
View File
@@ -26,7 +26,12 @@ export const useGlobalStore = defineStore('global', {
generatedMs: 0, generatedMs: 0,
localTimetableSearch: '', localTimetableSearch: '',
journalTimetableSearch: '',
journalTimetableSearch: {
driverName: '',
date: '',
route: ''
},
showSettings: false showSettings: false
}), }),
-6
View File
@@ -1,10 +1,5 @@
export type ViewMode = 'active' | 'storage' | 'journal'; export type ViewMode = 'active' | 'storage' | 'journal';
export enum StorageMode {
LOCAL = 'local',
API = 'api'
}
export interface ActiveData { export interface ActiveData {
trains: ActiveTrain[]; trains: ActiveTrain[];
activeSceneries: ActiveScenery[]; activeSceneries: ActiveScenery[];
@@ -228,7 +223,6 @@ export interface JournalTimetableDetailed extends JournalTimetableShort {
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
stockHistory: string[]; stockHistory: string[];
hidden: boolean;
routeSceneries: string; routeSceneries: string;
checkpointArrivals: any[]; checkpointArrivals: any[];
checkpointDepartures: any[]; checkpointDepartures: any[];