diff --git a/app/Http/Controllers/Frontend/VueFrontendController.php b/app/Http/Controllers/Frontend/VueFrontendController.php index f0003896f..b178f138b 100644 --- a/app/Http/Controllers/Frontend/VueFrontendController.php +++ b/app/Http/Controllers/Frontend/VueFrontendController.php @@ -2,8 +2,6 @@ namespace App\Http\Controllers\Frontend; - - use Illuminate\View\View; class VueFrontendController diff --git a/lang/de.json b/lang/de.json index f2c8fb06b..596728924 100644 --- a/lang/de.json +++ b/lang/de.json @@ -790,5 +790,9 @@ "trip_creation.limitations.2.small": "(wir versuchen, eine Route über Brouter zu finden, aber das funktioniert nicht immer zuverlässig)", "trip_creation.limitations.3": "Der Trip wird öffentlich erstellt - wenn du also eincheckst, kann jeder, der deinen Status sehen kann, ebenfalls in diesen Trip einchecken.", "trip_creation.limitations.4": "Der Betreiber kann in diesem Formular nicht festgelegt werden (über API möglich)", - "trip_creation.limitations.5": "Es gibt keine sichtbaren Fehlermeldungen für dieses Formular. Wenn also beim Absenden nichts passiert, liegt irgendwo ein Fehler vor." + "trip_creation.limitations.5": "Es gibt keine sichtbaren Fehlermeldungen für dieses Formular. Wenn also beim Absenden nichts passiert, liegt irgendwo ein Fehler vor.", + "action.error": "Diese Aktion konnte leider nicht ausgeführt werden. Bitte versuche es später noch einmal.", + "action.like": "Status liken", + "action.dislike": "Status nicht mehr liken", + "action.set-home": "Heimathaltestelle setzen" } diff --git a/lang/en.json b/lang/en.json index fc8daf631..abbb25b51 100644 --- a/lang/en.json +++ b/lang/en.json @@ -790,5 +790,9 @@ "trip_creation.limitations.2.small": "(we try to approximate a route via Brouter, but this may not always work properly)", "trip_creation.limitations.3": "The trip is created public - so if you check in to a trip, everyone who can see your status can also check in to this trip.", "trip_creation.limitations.4": "The operator can't be set in this form (possible via API)", - "trip_creation.limitations.5": "There are no visible error messages for this form. So, if nothing happens on submit... sorry. There is an error." + "trip_creation.limitations.5": "There are no visible error messages for this form. So, if nothing happens on submit... sorry. There is an error.", + "action.error": "This action could not be executed. Please try again later.", + "action.like": "Like status", + "action.dislike": "Dislike status", + "action.set-home": "Set home station" } diff --git a/resources/types/Departure.ts b/resources/types/Departure.ts new file mode 100644 index 000000000..011810f15 --- /dev/null +++ b/resources/types/Departure.ts @@ -0,0 +1,87 @@ +export type departureEntry = { + tripId: string; + stop: HafasStop; + when: string | null; + plannedWhen: string | null; + delay: number | null; + platform: string | null; + prognosisType: string; + direction: string; + provenance: any | null; + line: HafasLine; + remarks: any[]; + origin: any; + destination: HafasDestination; + currentTripPosition: { + type: string; + latitude: number; + longitude: number + } + cancelled: boolean|null|undefined; + station: { + id: number; + ibnr: number; + wikidata_id: null|any; + ifopt_a: any|null; + ifopt_b: any|null; + ifopt_c: any|null; + ifopt_d: any|null; + ifopt_e: any|null; + rilIdentifier: string|null; + name: string; + latitude: number|null; + longitude: number|null; + ifopt: any|null; + } +} + +export type HafasDestination = { + type: string; + id: string; + name: string; + location: HafasLocation; + products: { + [key: string]: boolean; + } + station: HafasStation; +} + +export type HafasLine = { + type: string; + id: string; + fahrtNr: string; + name: string; + public: any; + adminCode: any; + productName: any; + mode: any; + product: any; + operator: any; +} + +export type HafasStop = { + type: string; + id: string; + name: string; + location: HafasLocation; + products: { + [key: string]: boolean; + } + station: HafasStation +} +export type HafasStation = { + type: string; + id: string; + name: string; + location: HafasLocation; + products: { + [key: string]: boolean; + } +} + +export type HafasLocation = { + type: string; + id: string; + latitude: number; + longitude: number; + } diff --git a/resources/types/User.ts b/resources/types/User.ts new file mode 100644 index 000000000..604e32112 --- /dev/null +++ b/resources/types/User.ts @@ -0,0 +1,17 @@ +import {ShortStation} from "./Station"; + +export type User = { + displayName: string, + username: string, + profilePicture: string, + trainDistance: number, + trainDuration: number, + points: number, + mastodonUrl: string|null, + privateProfile: boolean, + privacyHideDays: number, + preventIndex: boolean, + role: number, + home: ShortStation, + language: string +}; diff --git a/resources/vue/components/Checkin/AutocompleteListEntry.vue b/resources/vue/components/Checkin/AutocompleteListEntry.vue new file mode 100644 index 000000000..9d652fce6 --- /dev/null +++ b/resources/vue/components/Checkin/AutocompleteListEntry.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/resources/vue/components/Checkin/StationBoardEntry.vue b/resources/vue/components/Checkin/StationBoardEntry.vue new file mode 100644 index 000000000..b58766161 --- /dev/null +++ b/resources/vue/components/Checkin/StationBoardEntry.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/resources/vue/components/CheckinLineRun.vue b/resources/vue/components/CheckinLineRun.vue index 980e23fe3..1511f57b0 100644 --- a/resources/vue/components/CheckinLineRun.vue +++ b/resources/vue/components/CheckinLineRun.vue @@ -42,8 +42,8 @@ export default { }); fetch(`/api/v1/trains/trip?${params.toString()}`).then((response) => { response.json().then((result) => { - this.lineRun = result.data; - let remove = true; + this.lineRun = result.data; + let remove = true; this.lineRun.stopovers = this.lineRun.stopovers.filter((item) => { if (remove && item.evaIdentifier === Number(this.$props.selectedTrain.stop.id)) { remove = false; @@ -51,7 +51,7 @@ export default { } return !remove; }); - this.loading = false; + this.loading = false; if (this.$props.fastCheckinIbnr) { this.fastCheckin(); } @@ -90,19 +90,33 @@ export default { + + diff --git a/resources/vue/components/StationAutocomplete.vue b/resources/vue/components/StationAutocomplete.vue index e0cf70dba..bb0c1739f 100644 --- a/resources/vue/components/StationAutocomplete.vue +++ b/resources/vue/components/StationAutocomplete.vue @@ -5,11 +5,19 @@ import {trans} from "laravel-vue-i18n"; import VueDatePicker from '@vuepic/vue-datepicker'; import '@vuepic/vue-datepicker/dist/main.css' import {DateTime} from "luxon"; +import {useUserStore} from "../stores/user"; +import AutocompleteListEntry from "./Checkin/AutocompleteListEntry.vue"; export default { + setup() { + const userStore = useUserStore(); + userStore.fetchSettings(); + + return {userStore}; + }, name: "StationAutocomplete", emits: ["update:station", "update:time", "update:travelType"], - components: {FullScreenModal, VueDatePicker}, + components: {AutocompleteListEntry, FullScreenModal, VueDatePicker}, props: { station: { type: Object, @@ -17,8 +25,9 @@ export default { default: null, }, stationName: { - type: Object, - required: false + type: String, + required: false, + default: null, }, dashboard: { type: Boolean, @@ -69,6 +78,14 @@ export default { showModal() { this.$refs.modal.show(); }, + setHome() { + if (!this.isHome) { + this.userStore.setHome(this.station).catch((error) => { + console.error(error); + window.notyf.error(trans('action.error') + " (" + trans('action.set-home') + ")"); + }) + } + }, getRecent() { fetch(`/api/v1/trains/station/history`).then((response) => { response.json().then((result) => { @@ -101,7 +118,7 @@ export default { this.setStation({name: this.stationInput}); }, setStation(item) { - this.stationInput = item.name; + this.stationInput = item.name; this.selectedStation = item; this.$emit("update:station", item); this.$refs.modal.hide(); @@ -145,8 +162,8 @@ export default { } }, mounted() { - this.date = this.time; - this.stationInput = this.stationName ? this.stationName : ""; + this.date = this.time; + this.stationInput = this.stationName ? this.stationName : ""; this.selectedStation = this.station; this.getRecent(); }, @@ -156,6 +173,9 @@ export default { }, dark() { return localStorage.getItem('darkMode') === 'dark'; + }, + isHome() { + return this.userStore.getHome && this.station && this.userStore.getHome.id === this.station.id; } } } @@ -172,30 +192,39 @@ export default {
-
{{ trans("stationboard.where-are-you") }}
+
+ {{ trans("stationboard.where-are-you") }} + + + +
diff --git a/resources/vue/components/Stationboard.vue b/resources/vue/components/Stationboard.vue index bdf08be4d..41fac208b 100644 --- a/resources/vue/components/Stationboard.vue +++ b/resources/vue/components/Stationboard.vue @@ -7,9 +7,12 @@ import CheckinLineRun from "./CheckinLineRun.vue"; import CheckinInterface from "./CheckinInterface.vue"; import StationAutocomplete from "./StationAutocomplete.vue"; import {trans} from "laravel-vue-i18n"; +import StationBoardEntry from "./Checkin/StationBoardEntry.vue"; export default { - components: {StationAutocomplete, CheckinInterface, CheckinLineRun, LineIndicator, ProductIcon, FullScreenModal}, + components: { + StationBoardEntry, + StationAutocomplete, CheckinInterface, CheckinLineRun, LineIndicator, ProductIcon, FullScreenModal}, data() { return { data: [], @@ -280,48 +283,20 @@ export default {
- +
diff --git a/resources/vue/stores/user.ts b/resources/vue/stores/user.ts new file mode 100644 index 000000000..bb9fd86a7 --- /dev/null +++ b/resources/vue/stores/user.ts @@ -0,0 +1,101 @@ +import {defineStore} from "pinia"; +import {User} from "../../types/User"; +import {ShortStation} from "../../types/Station"; + +export const useUserStore = defineStore('user', { + persist: true, + state: () => ({ + user: null as User | null, + loading: false, + error: null as unknown | null, + refreshed: "2021-08-01T12:00:00Z" + }), + getters: { + getDisplayName(): string { + return this.user ? this.user.displayName : ''; + }, + getUsername(): string { + return this.user ? this.user.username : ''; + }, + getProfilePicture(): string { + return this.user ? this.user.profilePicture : ''; + }, + getTrainDistance(): number { + return this.user ? this.user.trainDistance : 0; + }, + getTrainDuration(): number { + return this.user ? this.user.trainDuration : 0; + }, + getPoints(): number { + return this.user ? this.user.points : 0; + }, + getMastodonUrl(): string | null { + return this.user ? this.user.mastodonUrl : null; + }, + isPrivateProfile(): boolean { + return this.user ? this.user.privateProfile : false; + }, + getPrivacyHideDays(): number { + return this.user ? this.user.privacyHideDays : 0; + }, + isPreventIndex(): boolean { + return this.user ? this.user.preventIndex : false; + }, + getRole(): number { + return this.user ? this.user.role : 0; + }, + getHome(): ShortStation | null { + return this.user ? this.user.home : null; + }, + getLanguage(): string { + return this.user ? this.user.language : ''; + } + }, + actions: { + async setHome(home: ShortStation|any): Promise { + console.log(home); + const curStation = this.user?.home; + if (this.user) { + this.user.home = home; + } + + fetch(`/api/v1/station/${home.id}/home`, { + method: 'PUT' + }) + .then(response => response.json()) + .then((data) => { + if (this.user) { + this.user.home = data.data; + } + + }) + .catch((error) => { + if (this.user) { + this.user.home = curStation; + } + + return error; + }) + }, + async fetchSettings(): Promise { + // Fetch Data every 15 Minutes + // ToDo: reduce interval + // ToDo: refresh with settings update + // ToDo: invalidate when logging out + if (this.refreshed && (new Date().getTime() - new Date(this.refreshed).getTime()) < 60 * 15 * 1000) { + return; + } + this.loading = true; + try { + this.user = await fetch('/api/v1/auth/user') + .then((response: { json: () => any; }) => response.json()) + .then((data: { data: any; }) => data.data); + this.refreshed = new Date().toString(); + } catch (error) { + this.error = error; + } finally { + this.loading = false; + } + } + } +});