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
@@ -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>
@@ -0,0 +1,156 @@
<template>
<div class="flex justify-between gap-2">
<div class="flex gap-2">
<button
:class="`p-1 rounded-md ${
globalStore.viewMode == 'active'
? 'bg-green-600 hover:bg-green-500'
: 'bg-zinc-800 hover:bg-zinc-700'
}`"
@click="toggleViewMode('active')"
>
<WifiIcon class="size-6" />
</button>
<button
:class="`p-1 rounded-md ${
globalStore.viewMode == 'storage'
? 'bg-green-600 hover:bg-green-500'
: 'bg-zinc-800 hover:bg-zinc-700'
}`"
@click="toggleViewMode('storage')"
>
<ArchiveBoxArrowDownIcon class="size-6" />
</button>
<button
:class="`p-1 rounded-md ${
globalStore.viewMode == 'journal'
? 'bg-green-600 hover:bg-green-500'
: 'bg-zinc-800 hover:bg-zinc-700'
}`"
@click="toggleViewMode('journal')"
>
<CloudIcon class="size-6" />
</button>
</div>
<div class="flex gap-2">
<button class="bg-zinc-800 p-1 rounded-md hover:bg-zinc-700 self-end" @click="toggleDarkMode">
<MoonIcon v-if="globalStore.darkMode" class="text-white size-6" />
<SunIcon v-else class="text-white size-6" />
</button>
<button
class="bg-zinc-800 p-1 rounded-md hover:bg-zinc-700 disabled:opacity-60 disabled:hover:bg-zinc-800"
:disabled="globalStore.currentTimetableData == null"
@click="openPrintingWindow"
>
<PrinterIcon class="text-white size-6" />
</button>
<button
class="p-1 rounded-md disabled:opacity-60 disabled:hover:bg-zinc-800"
:disabled="globalStore.currentTimetableData == null"
:class="{
'bg-green-600 hover:bg-green-700': isTimetableSaved,
'bg-zinc-800 hover:bg-zinc-700': !isTimetableSaved
}"
@click="saveToStorage"
>
<ArrowDownTrayIcon class="text-white size-6" />
</button>
</div>
</div>
</template>
<script setup lang="ts">
import {
PrinterIcon,
MoonIcon,
SunIcon,
ArchiveBoxArrowDownIcon,
ArrowDownTrayIcon,
CloudIcon,
WifiIcon
} from '@heroicons/vue/16/solid';
import { computed, watch } from 'vue';
import { useGlobalStore } from '../../stores/global.store';
import type { ViewMode, TimetableData } from '../../types/common.types';
const globalStore = useGlobalStore();
// Computed
const isTimetableSaved = computed(() => {
if (!globalStore.currentTimetableData) return false;
return Object.keys(globalStore.storageTimetables).includes(
`${globalStore.currentTimetableData.timetableId}`
);
});
// Watchers
watch(
() => globalStore.selectedActiveTrain,
(curr) => {
if (curr != null) {
globalStore.generatedDate = new Date();
}
}
);
// Methods
function toggleViewMode(viewMode: ViewMode) {
globalStore.viewMode = viewMode;
}
function toggleDarkMode() {
globalStore.darkMode = !globalStore.darkMode;
window.localStorage.setItem('currentTheme', globalStore.darkMode ? 'dark' : 'light');
}
function saveToStorage() {
if (globalStore.currentTimetableData == null) return;
try {
const savedTimetablesStorage = localStorage.getItem('savedTimetables');
let savedTimetablesJSON: Record<number, TimetableData> = savedTimetablesStorage
? JSON.parse(savedTimetablesStorage)
: {};
if (savedTimetablesJSON[globalStore.currentTimetableData.timetableId] !== undefined) {
globalStore.selectedStorageTimetable =
savedTimetablesJSON[globalStore.currentTimetableData.timetableId];
globalStore.viewMode = 'storage';
return;
}
savedTimetablesJSON[globalStore.currentTimetableData.timetableId] = {
...globalStore.currentTimetableData,
savedTimestamp: Date.now()
};
localStorage.setItem('savedTimetables', JSON.stringify(savedTimetablesJSON));
globalStore.storageTimetables = savedTimetablesJSON;
} catch (error) {}
}
function openPrintingWindow() {
if (globalStore.selectedActiveTrain != null) {
const date = `${globalStore
.generatedDate!.toLocaleDateString('pl-PL')
.replace(/\./g, '-')}--${globalStore
.generatedDate!.toLocaleTimeString('pl-PL')
.replace(/:/g, '-')}`;
document.title = `${globalStore.selectedActiveTrain.driverName} ; ${
globalStore.selectedActiveTrain.timetable!.category
} ${globalStore.selectedActiveTrain.trainNo}
${globalStore.selectedActiveTrain.timetable?.route.replace('|', ' - ')} ; ${date}`;
}
window.print();
}
</script>