mirror of
https://github.com/Spythere/srjp-td2.git
synced 2026-05-02 21:18:12 +00:00
feat: journal timetable view mode
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "none"
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Logger Function
|
||||||
|
log() {
|
||||||
|
local message="$1"
|
||||||
|
local type="$2"
|
||||||
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
local color
|
||||||
|
local endcolor="\033[0m"
|
||||||
|
|
||||||
|
case "$type" in
|
||||||
|
"info") color="\033[38;5;79m" ;;
|
||||||
|
"success") color="\033[1;32m" ;;
|
||||||
|
"error") color="\033[1;31m" ;;
|
||||||
|
*) color="\033[1;34m" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo -e "${color}${timestamp} - ${message}${endcolor}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Error handler function
|
||||||
|
handle_error() {
|
||||||
|
local exit_code=$1
|
||||||
|
local error_message="$2"
|
||||||
|
log "Error: $error_message (Exit Code: $exit_code)" "error"
|
||||||
|
exit $exit_code
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check for command availability
|
||||||
|
command_exists() {
|
||||||
|
command -v "$1" &> /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
check_os() {
|
||||||
|
if ! [ -f "/etc/debian_version" ]; then
|
||||||
|
echo "Error: This script is only supported on Debian-based systems."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to Install the script pre-requisites
|
||||||
|
install_pre_reqs() {
|
||||||
|
log "Installing pre-requisites" "info"
|
||||||
|
|
||||||
|
# Run 'apt-get update'
|
||||||
|
if ! apt-get update -y; then
|
||||||
|
handle_error "$?" "Failed to run 'apt-get update'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run 'apt-get install'
|
||||||
|
if ! apt-get install -y apt-transport-https ca-certificates curl gnupg; then
|
||||||
|
handle_error "$?" "Failed to install packages"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! mkdir -p /usr/share/keyrings; then
|
||||||
|
handle_error "$?" "Makes sure the path /usr/share/keyrings exist or run ' mkdir -p /usr/share/keyrings' with sudo"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f /usr/share/keyrings/nodesource.gpg || true
|
||||||
|
rm -f /etc/apt/sources.list.d/nodesource.list || true
|
||||||
|
|
||||||
|
# Run 'curl' and 'gpg' to download and import the NodeSource signing key
|
||||||
|
if ! curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /usr/share/keyrings/nodesource.gpg; then
|
||||||
|
handle_error "$?" "Failed to download and import the NodeSource signing key"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Explicitly set the permissions to ensure the file is readable by all
|
||||||
|
if ! chmod 644 /usr/share/keyrings/nodesource.gpg; then
|
||||||
|
handle_error "$?" "Failed to set correct permissions on /usr/share/keyrings/nodesource.gpg"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to configure the Repo
|
||||||
|
configure_repo() {
|
||||||
|
local node_version=$1
|
||||||
|
|
||||||
|
arch=$(dpkg --print-architecture)
|
||||||
|
if [ "$arch" != "amd64" ] && [ "$arch" != "arm64" ] && [ "$arch" != "armhf" ]; then
|
||||||
|
handle_error "1" "Unsupported architecture: $arch. Only amd64, arm64, and armhf are supported."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "deb [arch=$arch signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$node_version nodistro main" | tee /etc/apt/sources.list.d/nodesource.list > /dev/null
|
||||||
|
|
||||||
|
# N|solid Config
|
||||||
|
echo "Package: nsolid" | tee /etc/apt/preferences.d/nsolid > /dev/null
|
||||||
|
echo "Pin: origin deb.nodesource.com" | tee -a /etc/apt/preferences.d/nsolid > /dev/null
|
||||||
|
echo "Pin-Priority: 600" | tee -a /etc/apt/preferences.d/nsolid > /dev/null
|
||||||
|
|
||||||
|
# Nodejs Config
|
||||||
|
echo "Package: nodejs" | tee /etc/apt/preferences.d/nodejs > /dev/null
|
||||||
|
echo "Pin: origin deb.nodesource.com" | tee -a /etc/apt/preferences.d/nodejs > /dev/null
|
||||||
|
echo "Pin-Priority: 600" | tee -a /etc/apt/preferences.d/nodejs > /dev/null
|
||||||
|
|
||||||
|
# Run 'apt-get update'
|
||||||
|
if ! apt-get update -y; then
|
||||||
|
handle_error "$?" "Failed to run 'apt-get update'"
|
||||||
|
else
|
||||||
|
log "Repository configured successfully."
|
||||||
|
log "To install Node.js, run: apt-get install nodejs -y" "info"
|
||||||
|
log "You can use N|solid Runtime as a node.js alternative" "info"
|
||||||
|
log "To install N|solid Runtime, run: apt-get install nsolid -y \n" "success"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Define Node.js version
|
||||||
|
NODE_VERSION="23.x"
|
||||||
|
|
||||||
|
# Check OS
|
||||||
|
check_os
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
install_pre_reqs || handle_error $? "Failed installing pre-requisites"
|
||||||
|
configure_repo "$NODE_VERSION" || handle_error $? "Failed configuring repository"
|
||||||
@@ -1,16 +1,68 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex gap-2 mb-2 print:hidden">
|
<div class="flex gap-2 justify-between flex-wrap mb-2 print:hidden">
|
||||||
<button
|
<div class="flex gap-2">
|
||||||
class="p-1 rounded-md"
|
<button
|
||||||
:class="{
|
:class="`p-1 rounded-md ${
|
||||||
'bg-zinc-800 hover:bg-zinc-700': globalStore.viewMode == 'active',
|
globalStore.viewMode == 'active'
|
||||||
'bg-green-600 hover:bg-green-500': globalStore.viewMode == 'storage',
|
? 'bg-green-600 hover:bg-green-500'
|
||||||
}"
|
: 'bg-zinc-800 hover:bg-zinc-700'
|
||||||
@click="toggleViewMode"
|
}`"
|
||||||
>
|
@click="toggleViewMode('active')"
|
||||||
<ArchiveBoxArrowDownIcon class="size-6" />
|
>
|
||||||
</button>
|
<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>
|
||||||
|
|
||||||
|
<!-- Active Data Search -->
|
||||||
<select
|
<select
|
||||||
name="trains"
|
name="trains"
|
||||||
id="trains-select"
|
id="trains-select"
|
||||||
@@ -21,45 +73,37 @@
|
|||||||
@change="selectTrain"
|
@change="selectTrain"
|
||||||
>
|
>
|
||||||
<option :value="null" disabled>
|
<option :value="null" disabled>
|
||||||
{{ apiStore.activeDataStatus == DataStatus.LOADING ? $t('data-loading-text') : $t('train-select-placeholder') }}
|
{{
|
||||||
|
apiStore.activeDataStatus == DataStatus.LOADING
|
||||||
|
? $t('data-loading-text')
|
||||||
|
: $t('train-select-placeholder')
|
||||||
|
}}
|
||||||
</option>
|
</option>
|
||||||
<option :value="train.id" v-for="train in globalStore.activeTimetableTrains">
|
<option :value="train.id" v-for="train in globalStore.activeTimetableTrains">
|
||||||
{{ train.driverName }} | {{ train.timetable?.category }} {{ train.trainNo }} [{{ getRegionNameById(train.region) }}]
|
{{ train.driverName }} | {{ train.timetable?.category }} {{ train.trainNo }} [{{
|
||||||
|
getRegionNameById(train.region)
|
||||||
|
}}]
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- Local Storage Search -->
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
v-if="globalStore.viewMode == 'storage'"
|
v-if="globalStore.viewMode == 'storage'"
|
||||||
v-model="globalStore.timetableSearch"
|
v-model="globalStore.localTimetableSearch"
|
||||||
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')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button class="bg-zinc-800 p-1 rounded-md hover:bg-zinc-700" @click="toggleDarkMode">
|
<!-- Journal Serach -->
|
||||||
<MoonIcon v-if="globalStore.darkMode" class="text-white size-6" />
|
<input
|
||||||
<SunIcon v-else class="text-white size-6" />
|
type="text"
|
||||||
</button>
|
v-else-if="globalStore.viewMode == 'journal'"
|
||||||
|
v-model="globalStore.journalTimetableSearch"
|
||||||
<button
|
@change="fetchJournalTimetables"
|
||||||
class="bg-zinc-800 p-1 rounded-md hover:bg-zinc-700 disabled:opacity-60 disabled:hover:bg-zinc-800"
|
class="bg-zinc-800 p-1 rounded-md print:hidden w-full"
|
||||||
:disabled="globalStore.currentTimetableData == null"
|
:placeholder="$t('journal-search-placeholder')"
|
||||||
@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>
|
</template>
|
||||||
|
|
||||||
@@ -68,9 +112,17 @@ import { computed } 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,
|
||||||
|
CloudIcon,
|
||||||
|
WifiIcon
|
||||||
|
} 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();
|
||||||
@@ -80,22 +132,26 @@ const globalStore = useGlobalStore();
|
|||||||
const isTimetableSaved = computed(() => {
|
const isTimetableSaved = computed(() => {
|
||||||
if (!globalStore.currentTimetableData) return false;
|
if (!globalStore.currentTimetableData) return false;
|
||||||
|
|
||||||
return Object.keys(globalStore.storageTimetables).includes(`${globalStore.currentTimetableData.timetableId}`);
|
return Object.keys(globalStore.storageTimetables).includes(
|
||||||
|
`${globalStore.currentTimetableData.timetableId}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
function selectTrain() {
|
function selectTrain() {
|
||||||
if (!apiStore.activeData) return;
|
if (!apiStore.activeData) return;
|
||||||
|
|
||||||
globalStore.selectedActiveTrain = globalStore.activeTimetableTrains.find((train) => train.id == globalStore.selectedTrainId) ?? null;
|
globalStore.selectedActiveTrain =
|
||||||
|
globalStore.activeTimetableTrains.find((train) => train.id == globalStore.selectedTrainId) ??
|
||||||
|
null;
|
||||||
|
|
||||||
if (globalStore.selectedActiveTrain != null) {
|
if (globalStore.selectedActiveTrain != null) {
|
||||||
globalStore.generatedDate = new Date();
|
globalStore.generatedDate = new Date();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleViewMode() {
|
function toggleViewMode(viewMode: ViewMode) {
|
||||||
globalStore.viewMode = globalStore.viewMode == 'active' ? 'storage' : 'active';
|
globalStore.viewMode = viewMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleDarkMode() {
|
function toggleDarkMode() {
|
||||||
@@ -109,16 +165,22 @@ function saveToStorage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const savedTimetablesStorage = localStorage.getItem('savedTimetables');
|
const savedTimetablesStorage = localStorage.getItem('savedTimetables');
|
||||||
let savedTimetablesJSON: Record<number, TimetableData> = savedTimetablesStorage ? JSON.parse(savedTimetablesStorage) : {};
|
let savedTimetablesJSON: Record<number, TimetableData> = savedTimetablesStorage
|
||||||
|
? JSON.parse(savedTimetablesStorage)
|
||||||
|
: {};
|
||||||
|
|
||||||
if (savedTimetablesJSON[globalStore.currentTimetableData.timetableId] !== undefined) {
|
if (savedTimetablesJSON[globalStore.currentTimetableData.timetableId] !== undefined) {
|
||||||
globalStore.selectedStorageTimetable = savedTimetablesJSON[globalStore.currentTimetableData.timetableId];
|
globalStore.selectedStorageTimetable =
|
||||||
|
savedTimetablesJSON[globalStore.currentTimetableData.timetableId];
|
||||||
globalStore.viewMode = 'storage';
|
globalStore.viewMode = 'storage';
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
savedTimetablesJSON[globalStore.currentTimetableData.timetableId] = { ...globalStore.currentTimetableData, savedTimestamp: Date.now() };
|
savedTimetablesJSON[globalStore.currentTimetableData.timetableId] = {
|
||||||
|
...globalStore.currentTimetableData,
|
||||||
|
savedTimestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
localStorage.setItem('savedTimetables', JSON.stringify(savedTimetablesJSON));
|
localStorage.setItem('savedTimetables', JSON.stringify(savedTimetablesJSON));
|
||||||
globalStore.storageTimetables = savedTimetablesJSON;
|
globalStore.storageTimetables = savedTimetablesJSON;
|
||||||
@@ -127,21 +189,22 @@ function saveToStorage() {
|
|||||||
|
|
||||||
function openPrintingWindow() {
|
function openPrintingWindow() {
|
||||||
if (globalStore.selectedActiveTrain != null) {
|
if (globalStore.selectedActiveTrain != null) {
|
||||||
const date = `${globalStore.generatedDate!.toLocaleDateString('pl-PL').replace(/\./g, '-')}--${globalStore
|
const date = `${globalStore
|
||||||
|
.generatedDate!.toLocaleDateString('pl-PL')
|
||||||
|
.replace(/\./g, '-')}--${globalStore
|
||||||
.generatedDate!.toLocaleTimeString('pl-PL')
|
.generatedDate!.toLocaleTimeString('pl-PL')
|
||||||
.replace(/:/g, '-')}`;
|
.replace(/:/g, '-')}`;
|
||||||
|
|
||||||
document.title = `${globalStore.selectedActiveTrain.driverName} ; ${globalStore.selectedActiveTrain.timetable!.category} ${
|
document.title = `${globalStore.selectedActiveTrain.driverName} ; ${
|
||||||
globalStore.selectedActiveTrain.trainNo
|
globalStore.selectedActiveTrain.timetable!.category
|
||||||
}
|
} ${globalStore.selectedActiveTrain.trainNo}
|
||||||
${globalStore.selectedActiveTrain.timetable?.route.replace('|', ' - ')} ; ${date}`;
|
${globalStore.selectedActiveTrain.timetable?.route.replace('|', ' - ')} ; ${date}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.print();
|
window.print();
|
||||||
}
|
}
|
||||||
|
|
||||||
// function refreshData() {
|
async function fetchJournalTimetables() {
|
||||||
// apiStore.fetchActiveData();
|
apiStore.fetchJournalTimetables(globalStore.journalTimetableSearch);
|
||||||
// selectTrain();
|
}
|
||||||
// }
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<!-- Local -->
|
||||||
<div class="my-2 print:hidden" v-if="globalStore.currentTimetableData?.savedTimestamp">
|
<div class="my-2 print:hidden" v-if="globalStore.currentTimetableData?.savedTimestamp">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<div class="flex items-center gap-2 bg-zinc-900 p-1 w-full">
|
<div class="flex items-center gap-2 bg-zinc-900 p-1 w-full">
|
||||||
@@ -18,11 +19,49 @@
|
|||||||
</i18n-t>
|
</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="font-bold bg-zinc-900 p-1 hover:bg-zinc-800" @click="removeTimetable(globalStore.currentTimetableData.timetableId)">
|
<button
|
||||||
|
class="font-bold bg-zinc-900 p-1 hover:bg-zinc-800"
|
||||||
|
@click="removeTimetable(globalStore.currentTimetableData.timetableId)"
|
||||||
|
>
|
||||||
<TrashIcon class="text-white size-6" />
|
<TrashIcon class="text-white size-6" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="font-bold bg-zinc-900 p-1 hover:bg-zinc-800" @click="globalStore.selectedStorageTimetable = null">
|
<button
|
||||||
|
class="font-bold bg-zinc-900 p-1 hover:bg-zinc-800"
|
||||||
|
@click="globalStore.selectedStorageTimetable = null"
|
||||||
|
>
|
||||||
|
<ArrowUturnLeftIcon class="text-white size-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Journal -->
|
||||||
|
<div class="my-2 print:hidden" v-if="globalStore.currentTimetableData?.journalCreatedAt">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<div class="flex items-center gap-2 bg-zinc-900 p-1 w-full">
|
||||||
|
<div>
|
||||||
|
<InformationCircleIcon class="size-5" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<i18n-t keypath="journal-preview-info" tag="span">
|
||||||
|
<template v-slot:id>
|
||||||
|
<b>#{{ globalStore.currentTimetableData.timetableId }}</b>
|
||||||
|
</template>
|
||||||
|
<template v-slot:driverName>
|
||||||
|
<b>{{ globalStore.currentTimetableData.driverName }}</b>
|
||||||
|
</template>
|
||||||
|
<template v-slot:date>
|
||||||
|
<b>{{
|
||||||
|
new Date(globalStore.currentTimetableData.journalCreatedAt).toLocaleString()
|
||||||
|
}}</b>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="font-bold bg-zinc-900 p-1 hover:bg-zinc-800"
|
||||||
|
@click="globalStore.selectedJournalTimetable = null"
|
||||||
|
>
|
||||||
<ArrowUturnLeftIcon class="text-white size-6" />
|
<ArrowUturnLeftIcon class="text-white size-6" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,12 +84,14 @@ function removeTimetable(timetableId: number) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const savedTimetablesStorage = localStorage.getItem('savedTimetables');
|
const savedTimetablesStorage = localStorage.getItem('savedTimetables');
|
||||||
let savedTimetablesJSON: Record<number, TimetableData> = savedTimetablesStorage ? JSON.parse(savedTimetablesStorage) : {};
|
let savedTimetablesJSON: Record<number, TimetableData> = savedTimetablesStorage
|
||||||
|
? JSON.parse(savedTimetablesStorage)
|
||||||
|
: {};
|
||||||
delete savedTimetablesJSON[timetableId];
|
delete savedTimetablesJSON[timetableId];
|
||||||
|
|
||||||
localStorage.setItem('savedTimetables', JSON.stringify(savedTimetablesJSON));
|
localStorage.setItem('savedTimetables', JSON.stringify(savedTimetablesJSON));
|
||||||
globalStore.storageTimetables = savedTimetablesJSON;
|
globalStore.storageTimetables = savedTimetablesJSON;
|
||||||
|
|
||||||
globalStore.selectedStorageTimetable = null;
|
globalStore.selectedStorageTimetable = null;
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<!-- Timetable render based on current view mode -->
|
||||||
:class="{ dark: globalStore.darkMode }"
|
<div :class="{ dark: globalStore.darkMode }" v-if="globalStore.currentTimetableData != null"
|
||||||
v-if="globalStore.currentTimetableData != null"
|
class="overflow-auto p-1 bg-white print:bg-white dark:bg-zinc-950 print:text-black text-black dark:text-white min-h-full">
|
||||||
class="overflow-auto p-1 bg-white print:bg-white dark:bg-zinc-950 print:text-black text-black dark:text-white min-h-full"
|
|
||||||
>
|
|
||||||
<div>
|
<div>
|
||||||
<div class="p-1 font-bold w-max">
|
<div class="p-1 font-bold w-max">
|
||||||
{{ globalStore.currentTimetableData.category }} {{ globalStore.currentTimetableData.trainNo }} {{ $t('headers.relation') }}
|
{{ globalStore.currentTimetableData.category }} {{ globalStore.currentTimetableData.trainNo }} {{
|
||||||
|
$t('headers.relation') }}
|
||||||
{{ globalStore.currentTimetableData.route.replace('|', ' - ') }}
|
{{ globalStore.currentTimetableData.route.replace('|', ' - ') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -22,9 +21,8 @@
|
|||||||
<div>{{ $t('train-select-info') }}</div>
|
<div>{{ $t('train-select-info') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else>
|
<LocalStorageView v-else-if="globalStore.viewMode == 'storage'" />
|
||||||
<StorageView />
|
<JournalStorageView v-else />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -35,8 +33,9 @@ import { useApiStore } from '../../stores/api.store';
|
|||||||
import { useGlobalStore } from '../../stores/global.store';
|
import { useGlobalStore } from '../../stores/global.store';
|
||||||
import TimetableBody from './TimetableBody.vue';
|
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, type StopRow, type TimetablePathData } from '../../types/common.types';
|
||||||
import StorageView from '../TimetableStorage/StorageView.vue';
|
import LocalStorageView from '../TimetableViews/LocalStorageView.vue';
|
||||||
|
import JournalStorageView from '../TimetableViews/JournalStorageView.vue';
|
||||||
|
|
||||||
const globalStore = useGlobalStore();
|
const globalStore = useGlobalStore();
|
||||||
const apiStore = useApiStore();
|
const apiStore = useApiStore();
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>API STORAGE</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
<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>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
<div class="font-bold p-2 bg-zinc-700 mb-3">
|
|
||||||
<div class="text-2xl">{{ $t('storage-preview-title') }}</div>
|
|
||||||
<div class="flex gap-2 justify-center">
|
|
||||||
<template v-for="(mode, i) in storageModeList">
|
|
||||||
<span v-if="i != 0">•</span>
|
|
||||||
<button class="hover:text-green-300" :class="{ 'underline text-green-300': currentStorageModeIndex == i }" @click="selectStorageMode(i)">
|
|
||||||
{{ $t(`storage-mode.${mode.key}`) }}
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Current storage mode component -->
|
|
||||||
<component :is="storageModeList[currentStorageModeIndex].component" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
|
||||||
import { StorageMode } from '../../types/common.types';
|
|
||||||
import { ref, type Component, type Ref } from 'vue';
|
|
||||||
import LocalStorage from '../TimetableStorage/LocalStorage.vue';
|
|
||||||
import ApiStorage from '../TimetableStorage/ApiStorage.vue';
|
|
||||||
|
|
||||||
interface CurrentStorageMode {
|
|
||||||
key: StorageMode;
|
|
||||||
component: Component;
|
|
||||||
}
|
|
||||||
|
|
||||||
const globalStore = useGlobalStore();
|
|
||||||
const currentStorageModeIndex: Ref<number> = ref(0);
|
|
||||||
|
|
||||||
const storageModeList: CurrentStorageMode[] = [
|
|
||||||
{
|
|
||||||
key: StorageMode.LOCAL,
|
|
||||||
component: LocalStorage,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: StorageMode.API,
|
|
||||||
component: ApiStorage,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function selectStorageMode(index: number) {
|
|
||||||
currentStorageModeIndex.value = index;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<div class="text-white">
|
||||||
|
<h2 class="font-bold p-2 bg-zinc-700 mb-3 text-2xl">
|
||||||
|
{{ $t('journal-preview-title') }}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div v-if="apiStore.journalDataStatus == DataStatus.LOADING" class="bg-zinc-900 p-2">
|
||||||
|
{{ $t('data-loading-text') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="apiStore.journalDataStatus == DataStatus.ERROR" class="bg-red-500 p-2">
|
||||||
|
{{ $t('data-loading-error-text') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else-if="!apiStore.journalTimetablesData"
|
||||||
|
class="text-zinc-400 mt-2"
|
||||||
|
v-html="$t('journal-empty-info')"
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div v-else-if="apiStore.journalTimetablesData.length == 0">
|
||||||
|
<p class="text-zinc-300 mb-2">
|
||||||
|
{{ $t('journal-no-data') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<b class="text-red-300">
|
||||||
|
{{ $t('journal-reminder-text') }}
|
||||||
|
</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="timetable in apiStore.journalTimetablesData"
|
||||||
|
class="flex gap-1 w-full my-2"
|
||||||
|
@click="fetchTimetableDetails(timetable.id)"
|
||||||
|
>
|
||||||
|
<button class="bg-zinc-900 p-2 w-full cursor-pointer hover:bg-zinc-800 text-left">
|
||||||
|
<div class="text-zinc-300">
|
||||||
|
#{{ timetable.id }} •
|
||||||
|
{{ new Date(timetable.createdAt!).toLocaleString() }}
|
||||||
|
</div>
|
||||||
|
<b>
|
||||||
|
{{ timetable.driverName }} | {{ timetable.trainCategoryCode }}
|
||||||
|
{{ timetable.trainNo }}
|
||||||
|
</b>
|
||||||
|
{{ timetable.route.replace('|', ' > ') }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div v-if="apiStore.journalTimetablesData.length > 0">
|
||||||
|
<hr class="border-t-2 border-t-gray-500" />
|
||||||
|
|
||||||
|
<p class="text-zinc-400 text-sm">
|
||||||
|
{{ $t('journal-footer-text') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useApiStore } from '../../stores/api.store';
|
||||||
|
import { DataStatus } from '../../types/api.types';
|
||||||
|
|
||||||
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
|
function fetchTimetableDetails(id: number) {
|
||||||
|
apiStore.fetchJournalTimetableDetails(id);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
+45
-14
@@ -1,16 +1,42 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="text-white">
|
||||||
<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-700 mb-3">
|
||||||
|
<div class="text-2xl">{{ $t('storage-preview-title') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul>
|
<div
|
||||||
|
v-if="
|
||||||
|
globalStore.selectedStorageTimetable == null &&
|
||||||
|
Object.keys(globalStore.storageTimetables).length == 0
|
||||||
|
"
|
||||||
|
class="text-zinc-400"
|
||||||
|
>
|
||||||
|
{{ $t('storage-empty-info') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="font-bold p-2 bg-zinc-800 mb-3" v-else-if="filteredTimetables.length == 0">
|
||||||
|
{{ $t('storage-preview-empty') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul v-else>
|
||||||
<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">
|
||||||
<button class="bg-zinc-900 p-2 w-full cursor-pointer hover:bg-zinc-800 text-left" @click="selectTimetable(timetable)">
|
<button
|
||||||
<div class="text-zinc-300">#{{ timetable.timetableId }} • {{ new Date(timetable.savedTimestamp!).toLocaleString() }}</div>
|
class="bg-zinc-900 p-2 w-full cursor-pointer hover:bg-zinc-800 text-left"
|
||||||
<b>{{ timetable.driverName }} | {{ timetable.category }} {{ timetable.trainNo }}</b> {{ timetable.route.replace('|', ' > ') }}
|
@click="selectTimetable(timetable)"
|
||||||
|
>
|
||||||
|
<div class="text-zinc-300">
|
||||||
|
#{{ timetable.timetableId }} •
|
||||||
|
{{ new Date(timetable.savedTimestamp!).toLocaleString() }}
|
||||||
|
</div>
|
||||||
|
<b>{{ timetable.driverName }} | {{ timetable.category }} {{ timetable.trainNo }}</b>
|
||||||
|
{{ timetable.route.replace('|', ' > ') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="bg-zinc-900 p-2 hover:bg-zinc-800" @click="removeTimetable(timetable.timetableId)">
|
<button
|
||||||
<TrashIcon class="size-5 text-white" />
|
class="bg-zinc-900 p-2 hover:bg-zinc-800"
|
||||||
|
@click="removeTimetable(timetable.timetableId)"
|
||||||
|
>
|
||||||
|
<TrashIcon class="size-6 text-white" />
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -18,11 +44,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useGlobalStore } from '../../stores/global.store';
|
import { useGlobalStore } from '../../stores/global.store';
|
||||||
import { computed } from 'vue';
|
|
||||||
import { TrashIcon } from '@heroicons/vue/16/solid';
|
|
||||||
import type { TimetableData } from '../../types/common.types';
|
import type { TimetableData } from '../../types/common.types';
|
||||||
|
import { TrashIcon } from '@heroicons/vue/16/solid';
|
||||||
|
|
||||||
const globalStore = useGlobalStore();
|
const globalStore = useGlobalStore();
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
@@ -30,11 +56,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.localTimetableSearch.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.localTimetableSearch.toLocaleLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
timetables.sort((a, b) => {
|
timetables.sort((a, b) => {
|
||||||
@@ -55,11 +81,16 @@ function removeTimetable(timetableId: number) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const savedTimetablesStorage = localStorage.getItem('savedTimetables');
|
const savedTimetablesStorage = localStorage.getItem('savedTimetables');
|
||||||
let savedTimetablesJSON: Record<number, TimetableData> = savedTimetablesStorage ? JSON.parse(savedTimetablesStorage) : {};
|
let savedTimetablesJSON: Record<number, TimetableData> = savedTimetablesStorage
|
||||||
|
? JSON.parse(savedTimetablesStorage)
|
||||||
|
: {};
|
||||||
delete savedTimetablesJSON[timetableId];
|
delete savedTimetablesJSON[timetableId];
|
||||||
|
|
||||||
localStorage.setItem('savedTimetables', JSON.stringify(savedTimetablesJSON));
|
localStorage.setItem('savedTimetables', JSON.stringify(savedTimetablesJSON));
|
||||||
|
|
||||||
globalStore.storageTimetables = savedTimetablesJSON;
|
globalStore.storageTimetables = savedTimetablesJSON;
|
||||||
} catch (error) {}
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
+35
-29
@@ -1,30 +1,36 @@
|
|||||||
{
|
{
|
||||||
"data-loading-text": "Loading data...",
|
"data-loading-text": "Loading data...",
|
||||||
"train-select-placeholder": "Choose active train from the list",
|
"data-loading-error-text": "Oops! An error occurent while loading data from the server!",
|
||||||
"train-select-info": "Choose active train to generate SRJP timetable",
|
"train-select-placeholder": "Choose active train from the list",
|
||||||
"train-search-placeholder": "Enter TT details (number, route, user)",
|
"train-select-info": "Choose active train to generate SRJP timetable",
|
||||||
"headers": {
|
"train-search-placeholder": "Enter TT details (number, route, user)",
|
||||||
"line_no": "Line\nno.",
|
"headers": {
|
||||||
"line_km": "Km",
|
"line_no": "Line\nno.",
|
||||||
"station": "Station",
|
"line_km": "Km",
|
||||||
"time": "Time",
|
"station": "Station",
|
||||||
"loco_1": "Loco I",
|
"time": "Time",
|
||||||
"loco_2": "Loco II",
|
"loco_1": "Loco I",
|
||||||
"loco_3": "Loco III",
|
"loco_2": "Loco II",
|
||||||
"mass": "Loco load",
|
"loco_3": "Loco III",
|
||||||
"length": "Train len.",
|
"mass": "Loco load",
|
||||||
"vmax": "Vmax",
|
"length": "Train len.",
|
||||||
"relation": "Route"
|
"vmax": "Vmax",
|
||||||
},
|
"relation": "Route"
|
||||||
"storage-empty-header": "ARCHIVED TIMETABLES SEARCH MODE",
|
},
|
||||||
"storage-empty-info": "Timetables will be shown here after their archiving.",
|
"storage-empty-header": "ARCHIVED TIMETABLES SEARCH MODE",
|
||||||
"storage-preview-title": "ARCHIVED TIMETABLES",
|
"storage-empty-info": "Timetables will be shown here after their archiving.",
|
||||||
"storage-preview-empty": "No entries found for given parameters",
|
"storage-preview-title": "ARCHIVED TIMETABLES",
|
||||||
"storage-preview-info": "Archived timetable {id} for user {driverName} from: {date}",
|
"storage-preview-empty": "No entries found for given parameters",
|
||||||
"storage-preview-button-text": "Return",
|
"storage-preview-info": "Archived timetable {id} for user {driverName} from: {date}",
|
||||||
"delete-timetable-confirm": "Are you sure that you want to delete this timetable?",
|
"storage-preview-button-text": "Return",
|
||||||
"storage-mode": {
|
"delete-timetable-confirm": "Are you sure that you want to delete this timetable?",
|
||||||
"local": "LOCALLY",
|
|
||||||
"api": "STACJOWNIK"
|
"journal-preview-title": "TIMETABLES JOURNAL",
|
||||||
}
|
"journal-empty-info": "Enter timetable details in the text field above - it may be: <br> id (#number); nickname (nick:Spythere); date (date:11.01.2025); starting point (from:Krnów)<br>Up to 15 newest timetables will be shown.",
|
||||||
}
|
"journal-search-placeholder": "nick:Spythere date:02.04.2025 from:Krnów to:Biała_Sudecka",
|
||||||
|
"journal-preview-info": "Historical timetable {id} for user {driverName} from: {date}",
|
||||||
|
|
||||||
|
"journal-no-data": "No data for the current search! Check if the data you entered is correct.",
|
||||||
|
"journal-reminder-text": "Warning: detailed timetables data for SRJP purpose are collected since 1st February 2025 and only for users who support Stacjownik project!",
|
||||||
|
"journal-footer-text": "Detailed timetables data for SRJP purpose are collected since 1st February 2025 and only for users who support Stacjownik project!"
|
||||||
|
}
|
||||||
|
|||||||
+35
-29
@@ -1,30 +1,36 @@
|
|||||||
{
|
{
|
||||||
"data-loading-text": "Ładowanie danych...",
|
"data-loading-text": "Ładowanie danych...",
|
||||||
"train-select-placeholder": "Wybierz pociąg z listy",
|
"data-loading-error-text": "Ups! Wystąpił błąd podczas pobierania danych z serwera!",
|
||||||
"train-select-info": "Wybierz aktywny pociąg, aby wygenerować SRJP",
|
"train-select-placeholder": "Wybierz pociąg z listy",
|
||||||
"train-search-placeholder": "Wpisz szczegóły RJ (nr, relacja, gracz)",
|
"train-select-info": "Wybierz aktywny pociąg, aby wygenerować SRJP",
|
||||||
"headers": {
|
"train-search-placeholder": "Wpisz szczegóły RJ (nr, relacja, gracz)",
|
||||||
"line_no": "Nr\nlinii",
|
"headers": {
|
||||||
"line_km": "Km",
|
"line_no": "Nr\nlinii",
|
||||||
"station": "Stacja",
|
"line_km": "Km",
|
||||||
"time": "Godzina",
|
"station": "Stacja",
|
||||||
"loco_1": "Lok I",
|
"time": "Godzina",
|
||||||
"loco_2": "Lok II",
|
"loco_1": "Lok I",
|
||||||
"loco_3": "Lok III",
|
"loco_2": "Lok II",
|
||||||
"mass": "Obc. lok.",
|
"loco_3": "Lok III",
|
||||||
"length": "Dł. poc.",
|
"mass": "Obc. lok.",
|
||||||
"vmax": "Vmax",
|
"length": "Dł. poc.",
|
||||||
"relation": "Relacja"
|
"vmax": "Vmax",
|
||||||
},
|
"relation": "Relacja"
|
||||||
"storage-empty-header": "TRYB WYSZUKIWANA ZAPISANYCH ROZKŁADÓW JAZDY",
|
},
|
||||||
"storage-empty-info": "Użyj funkcji zapisu rozkładu jazdy, aby go tutaj wyświetlić.",
|
"storage-empty-header": "TRYB WYSZUKIWANA ZAPISANYCH ROZKŁADÓW JAZDY",
|
||||||
"storage-preview-title": "ZAPISANE ROZKŁADY JAZDY",
|
"storage-empty-info": "Użyj funkcji zapisu rozkładu jazdy, aby go tutaj wyświetlić.",
|
||||||
"storage-preview-empty": "Nie znaleziono żadnych wpisów dla podanych parametrów",
|
"storage-preview-title": "ZAPISANE ROZKŁADY JAZDY",
|
||||||
"storage-preview-info": "Rozkład archiwalny {id} maszynisty {driverName} z dnia {date}",
|
"storage-preview-empty": "Nie znaleziono żadnych wpisów dla podanych parametrów",
|
||||||
"storage-preview-button-text": "Powróć",
|
"storage-preview-info": "Rozkład archiwalny {id} maszynisty {driverName} z dnia {date}",
|
||||||
"delete-timetable-confirm": "Czy na pewno chcesz usunąć ten rozkład jazdy z archiwum?",
|
"storage-preview-button-text": "Powróć",
|
||||||
"storage-mode": {
|
"delete-timetable-confirm": "Czy na pewno chcesz usunąć ten rozkład jazdy z archiwum?",
|
||||||
"local": "LOKALNIE",
|
|
||||||
"api": "STACJOWNIK"
|
"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-search-placeholder": "nick:Spythere date:02.04.2025 from:Krnów to:Biała_Sudecka",
|
||||||
|
"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-reminder-text": "Uwaga: szczegółowe rozkłady jazdy są zapisywane od 1 lutego 2025r. wyłącznie dla osób wspierających projekt Stacjownika!",
|
||||||
|
"journal-footer-text": "Szczegółowe dane o rozkładach jazdy do wygenerowania SRJP są zbierane od 1 lutego 2025r. wyłącznie dla maszynistów wspierających projekt Stacjownika!"
|
||||||
|
}
|
||||||
|
|||||||
+85
-5
@@ -1,8 +1,19 @@
|
|||||||
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 {
|
||||||
import type { ActiveData, SceneryData } from '../types/common.types';
|
DataStatus,
|
||||||
|
type ActiveDataResponse,
|
||||||
|
type SceneriesDataResponse,
|
||||||
|
type JournalTimetablesShortResponse
|
||||||
|
} from '../types/api.types';
|
||||||
|
import type {
|
||||||
|
ActiveData,
|
||||||
|
JournalTimetableDetailed,
|
||||||
|
JournalTimetableShort,
|
||||||
|
SceneryData
|
||||||
|
} from '../types/common.types';
|
||||||
|
import { useGlobalStore } from './global.store';
|
||||||
|
|
||||||
export const useApiStore = defineStore('api', {
|
export const useApiStore = defineStore('api', {
|
||||||
state() {
|
state() {
|
||||||
@@ -11,11 +22,13 @@ 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,
|
||||||
|
journalTimetablesData: null as JournalTimetableShort[] | null,
|
||||||
|
|
||||||
outdatedTimerId: -1,
|
outdatedTimerId: -1,
|
||||||
isActiveDataOutdated: false,
|
isActiveDataOutdated: false,
|
||||||
|
|
||||||
activeDataStatus: DataStatus.LOADING,
|
activeDataStatus: DataStatus.LOADING,
|
||||||
|
journalDataStatus: DataStatus.SUCCESS
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -37,13 +50,12 @@ export const useApiStore = defineStore('api', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.client = axios.create({
|
this.client = axios.create({
|
||||||
baseURL,
|
baseURL
|
||||||
});
|
});
|
||||||
|
|
||||||
this.fetchSceneriesData();
|
this.fetchSceneriesData();
|
||||||
await this.fetchActiveData();
|
await this.fetchActiveData();
|
||||||
|
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
this.fetchActiveData();
|
this.fetchActiveData();
|
||||||
}, 25000);
|
}, 25000);
|
||||||
@@ -76,5 +88,73 @@ export const useApiStore = defineStore('api', {
|
|||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+56
-13
@@ -1,6 +1,12 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useApiStore } from './api.store';
|
import { useApiStore } from './api.store';
|
||||||
import type { ActiveTrain, TimetableData, ViewMode } from '../types/common.types';
|
import type {
|
||||||
|
ActiveTrain,
|
||||||
|
JournalTimetableDetailed,
|
||||||
|
JournalTimetableShort,
|
||||||
|
TimetableData,
|
||||||
|
ViewMode
|
||||||
|
} from '../types/common.types';
|
||||||
import { unitNameCorrections } from '../utils/trainUtils';
|
import { unitNameCorrections } from '../utils/trainUtils';
|
||||||
|
|
||||||
export const useGlobalStore = defineStore('global', {
|
export const useGlobalStore = defineStore('global', {
|
||||||
@@ -11,6 +17,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 as JournalTimetableDetailed | null,
|
||||||
|
|
||||||
storageTimetables: {} as Record<number, TimetableData>,
|
storageTimetables: {} as Record<number, TimetableData>,
|
||||||
|
|
||||||
timetableWarnings: [] as string[],
|
timetableWarnings: [] as string[],
|
||||||
@@ -18,9 +26,10 @@ export const useGlobalStore = defineStore('global', {
|
|||||||
generatedDate: null as Date | null,
|
generatedDate: null as Date | null,
|
||||||
generatedMs: 0,
|
generatedMs: 0,
|
||||||
|
|
||||||
timetableSearch: '',
|
localTimetableSearch: '',
|
||||||
|
journalTimetableSearch: '',
|
||||||
|
|
||||||
showSettings: false,
|
showSettings: false
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
activeTimetableTrains() {
|
activeTimetableTrains() {
|
||||||
@@ -28,7 +37,9 @@ export const useGlobalStore = defineStore('global', {
|
|||||||
|
|
||||||
if (!apiStore.activeData) return [];
|
if (!apiStore.activeData) return [];
|
||||||
|
|
||||||
return apiStore.activeData.trains.filter((train) => train.timetable).sort((t1, t2) => t1.driverName.localeCompare(t2.driverName, 'pl-PL'));
|
return apiStore.activeData.trains
|
||||||
|
.filter((train) => train.timetable)
|
||||||
|
.sort((t1, t2) => t1.driverName.localeCompare(t2.driverName, 'pl-PL'));
|
||||||
},
|
},
|
||||||
|
|
||||||
currentTimetableData(): TimetableData | null {
|
currentTimetableData(): TimetableData | null {
|
||||||
@@ -52,12 +63,14 @@ 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.mainStop
|
stop.stopTime ? stop.stopTime + '_' + stop.stopType : ''
|
||||||
};${stop.stopDistance};${stop.departureTimestamp};${stop.departureLine ?? ''}`
|
};${stop.mainStop};${stop.stopDistance};${stop.departureTimestamp};${
|
||||||
|
stop.departureLine ?? ''
|
||||||
|
}`
|
||||||
)
|
)
|
||||||
.join('~~'),
|
.join('~~'),
|
||||||
headUnits: selectedTrain.stockString
|
headUnits: selectedTrain.stockString
|
||||||
@@ -68,13 +81,43 @@ export const useGlobalStore = defineStore('global', {
|
|||||||
const unitName = s.slice(0, s.indexOf('-'));
|
const unitName = s.slice(0, s.indexOf('-'));
|
||||||
|
|
||||||
return unitNameCorrections[unitName] ?? unitName;
|
return unitNameCorrections[unitName] ?? unitName;
|
||||||
}),
|
})
|
||||||
|
};
|
||||||
|
} else if (this.viewMode == 'journal') {
|
||||||
|
const selectedTimetable = this.selectedJournalTimetable;
|
||||||
|
|
||||||
|
if (!selectedTimetable || !selectedTimetable.stopListString) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
journalCreatedAt: new Date(selectedTimetable.createdAt).getTime(),
|
||||||
|
trainNo: selectedTimetable.trainNo,
|
||||||
|
mass: selectedTimetable.stockMass,
|
||||||
|
length: selectedTimetable.stockLength,
|
||||||
|
driverId: selectedTimetable.driverId,
|
||||||
|
driverName: selectedTimetable.driverName,
|
||||||
|
category: selectedTimetable.trainCategoryCode,
|
||||||
|
hasDangerousCargo: selectedTimetable.hasDangerousCargo,
|
||||||
|
hasExtraDeliveries: selectedTimetable.hasExtraDeliveries,
|
||||||
|
warningNotes: selectedTimetable.warningNotes,
|
||||||
|
path: selectedTimetable.path,
|
||||||
|
route: selectedTimetable.route,
|
||||||
|
trainMaxSpeed: selectedTimetable.trainMaxSpeed,
|
||||||
|
timetableId: selectedTimetable.id,
|
||||||
|
stopListString: selectedTimetable.stopListString,
|
||||||
|
headUnits: selectedTimetable.stockString
|
||||||
|
.split(';')
|
||||||
|
.slice(0, 3)
|
||||||
|
.filter((s, i) => i == 0 || /-\d+$/.test(s))
|
||||||
|
.map((s) => {
|
||||||
|
const unitName = s.slice(0, s.indexOf('-'));
|
||||||
|
|
||||||
|
return unitNameCorrections[unitName] ?? unitName;
|
||||||
|
})
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const selectedStorageTimetable = this.selectedStorageTimetable;
|
return this.selectedStorageTimetable;
|
||||||
return selectedStorageTimetable;
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
actions: {},
|
actions: {}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import type { ActiveData, SceneryData } from './common.types';
|
import type { ActiveData, JournalTimetableShort, SceneryData } from './common.types';
|
||||||
|
|
||||||
export type ActiveDataResponse = ActiveData;
|
export type ActiveDataResponse = ActiveData;
|
||||||
|
|
||||||
export type SceneriesDataResponse = SceneryData[];
|
export type SceneriesDataResponse = SceneryData[];
|
||||||
|
|
||||||
|
export type JournalTimetablesShortResponse = JournalTimetableShort[];
|
||||||
|
|
||||||
export enum DataStatus {
|
export enum DataStatus {
|
||||||
|
'INIT' = -1,
|
||||||
'LOADING' = 0,
|
'LOADING' = 0,
|
||||||
'SUCCESS' = 1,
|
'SUCCESS' = 1,
|
||||||
'ERROR' = 2,
|
'ERROR' = 2,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type ViewMode = 'active' | 'storage';
|
export type ViewMode = 'active' | 'storage' | 'journal';
|
||||||
|
|
||||||
export enum StorageMode {
|
export enum StorageMode {
|
||||||
LOCAL = 'local',
|
LOCAL = 'local',
|
||||||
@@ -245,7 +245,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 {
|
||||||
@@ -265,4 +265,5 @@ export interface TimetableData {
|
|||||||
stopListString: string;
|
stopListString: string;
|
||||||
headUnits: string[];
|
headUnits: string[];
|
||||||
savedTimestamp?: number;
|
savedTimestamp?: number;
|
||||||
|
journalCreatedAt?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user