From a6900259104c55dc95a3828bb0237d798f615f90 Mon Sep 17 00:00:00 2001 From: Kris Date: Sat, 15 Oct 2022 12:31:36 +0200 Subject: [PATCH] :children_crossing: Change trip destination after checkin (#1103) fixes #427 --- .idea/trwl.iml | 5 ++ .../Backend/Transport/StationController.php | 16 +++++ .../Transport/TrainCheckinController.php | 33 +++++++++ .../Frontend/Transport/StatusController.php | 68 +++++++++++++++++++ .../Controllers/FrontendStatusController.php | 26 ------- .../Resources/PointsCalculationResource.php | 2 +- resources/js/components/business-check-in.js | 58 ++++++++-------- resources/lang/de.json | 3 +- resources/lang/de_pfl.json | 2 +- resources/lang/en.json | 3 +- resources/lang/nl.json | 2 +- resources/lang/sv.json | 2 +- resources/views/includes/edit-modal.blade.php | 42 ++++++++---- .../messages/checkin-success.blade.php | 6 +- resources/views/includes/status.blade.php | 10 ++- resources/views/layouts/app.blade.php | 1 - routes/web.php | 5 +- 17 files changed, 205 insertions(+), 79 deletions(-) create mode 100644 app/Http/Controllers/Frontend/Transport/StatusController.php diff --git a/.idea/trwl.iml b/.idea/trwl.iml index 9156d712c..910391c3d 100644 --- a/.idea/trwl.iml +++ b/.idea/trwl.iml @@ -171,6 +171,11 @@ + + + + + diff --git a/app/Http/Controllers/Backend/Transport/StationController.php b/app/Http/Controllers/Backend/Transport/StationController.php index 299cefbdb..33e19a67e 100644 --- a/app/Http/Controllers/Backend/Transport/StationController.php +++ b/app/Http/Controllers/Backend/Transport/StationController.php @@ -5,7 +5,9 @@ use App\Exceptions\HafasException; use App\Http\Controllers\Controller; use App\Http\Controllers\HafasController; +use App\Models\TrainCheckin; use App\Models\TrainStation; +use App\Models\TrainStopover; use App\Models\User; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Collection; @@ -61,4 +63,18 @@ public static function getLatestArrivals(User $user, int $maxCount = 5): Collect ->limit($maxCount) ->get(); } + + public static function getAlternativeDestinationsForCheckin(TrainCheckin $checkin): Collection { + return $checkin->HafasTrip->stopoversNEW + ->filter(function(TrainStopover $stopover) use ($checkin) { + return $stopover->arrival_planned->isAfter($checkin->departure); + }) + ->map(function(TrainStopover $stopover) { + return [ + 'id' => $stopover->id, + 'name' => $stopover->trainStation->name, + 'arrival_planned' => $stopover->arrival_planned->format('H:i'), + ]; + }); + } } diff --git a/app/Http/Controllers/Backend/Transport/TrainCheckinController.php b/app/Http/Controllers/Backend/Transport/TrainCheckinController.php index fca1f20db..04b82f885 100644 --- a/app/Http/Controllers/Backend/Transport/TrainCheckinController.php +++ b/app/Http/Controllers/Backend/Transport/TrainCheckinController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Backend\Transport; use App\Enum\Business; +use App\Enum\PointReason; use App\Enum\StatusVisibility; use App\Events\UserCheckedIn; use App\Exceptions\Checkin\AlreadyCheckedInException; @@ -21,6 +22,7 @@ use App\Models\Status; use App\Models\TrainCheckin; use App\Models\TrainStation; +use App\Models\TrainStopover; use App\Models\User; use App\Notifications\UserJoinedConnection; use Carbon\Carbon; @@ -187,4 +189,35 @@ private static function createTrainCheckin( throw $exception; // Other scenarios are not handled, so rethrow the exception } } + + public static function changeDestination(TrainCheckin $checkin, TrainStopover $newDestinationStopover): PointReason { + if ($newDestinationStopover->arrival_planned->isBefore($checkin->origin_stopover->arrival_planned) + || $newDestinationStopover->is($checkin->origin_stopover) + || !$checkin->HafasTrip->stopoversNEW->contains('id', $newDestinationStopover->id) + ) { + throw new InvalidArgumentException(); + } + + $newDistance = GeoController::calculateDistance( + hafasTrip: $checkin->HafasTrip, + origin: $checkin->origin_stopover, + destination: $newDestinationStopover, + ); + + $pointsResource = PointsCalculationController::calculatePoints( + distanceInMeter: $newDistance, + hafasTravelType: $checkin->HafasTrip->category, + departure: $checkin->origin_stopover->departure, + arrival: $newDestinationStopover->arrival, + ); + + $checkin->update([ + 'arrival' => $newDestinationStopover->arrival_planned->toIso8601String(), + 'destination' => $newDestinationStopover->trainStation->ibnr, + 'points' => $pointsResource['points'], + ]); + + return PointReason::tryFrom($pointsResource['calculation']['reason']); + } + } diff --git a/app/Http/Controllers/Frontend/Transport/StatusController.php b/app/Http/Controllers/Frontend/Transport/StatusController.php new file mode 100644 index 000000000..7bebcd418 --- /dev/null +++ b/app/Http/Controllers/Frontend/Transport/StatusController.php @@ -0,0 +1,68 @@ +validate([ + 'statusId' => ['required', 'exists:statuses,id'], + 'body' => ['nullable', 'max:280'], + 'business_check' => ['required', new Enum(Business::class)], + 'checkinVisibility' => ['required', new Enum(StatusVisibility::class)], + 'destinationStopoverId' => ['nullable', 'exists:train_stopovers,id'], + ]); + + try { + $status = Status::findOrFail($validated['statusId']); + $this->authorize('update', $status); + $status->update([ + 'body' => $validated['body'] ?? null, + 'business' => Business::from($validated['business_check']), + 'visibility' => StatusVisibility::from($validated['checkinVisibility']), + ]); + + if (isset($validated['destinationStopoverId']) + && $validated['destinationStopoverId'] != $status->trainCheckin->destination_stopover->id) { + $pointReason = TrainCheckinController::changeDestination( + checkin: $status->trainCheckin, + newDestinationStopover: TrainStopover::findOrFail($validated['destinationStopoverId']), + ); + $status->fresh(); + + return redirect()->route('statuses.get', ['id' => $status->id]) + ->with('checkin-success', [ + 'reason' => 'status-updated', + 'distance' => $status->trainCheckin->distance, + 'duration' => $status->trainCheckin->duration, + 'points' => $status->trainCheckin->points, + 'lineName' => $status->trainCheckin->HafasTrip->linename, + 'alsoOnThisConnection' => $status->trainCheckin->alsoOnThisConnection, + 'event' => $status->trainCheckin->event, + 'forced' => false, + 'pointsCalculationReason' => $pointReason, + ]); + } + + return redirect()->route('statuses.get', ['id' => $status->id]) + ->with('success', __('status.update.success')); + } catch (ModelNotFoundException|PermissionException) { + return redirect()->back()->with('alert-danger', __('messages.exception.general')); + } + } +} diff --git a/app/Http/Controllers/FrontendStatusController.php b/app/Http/Controllers/FrontendStatusController.php index 9caf49ed3..f1c5fd1a4 100644 --- a/app/Http/Controllers/FrontendStatusController.php +++ b/app/Http/Controllers/FrontendStatusController.php @@ -2,8 +2,6 @@ namespace App\Http\Controllers; -use App\Enum\Business; -use App\Enum\StatusVisibility; use App\Exceptions\PermissionException; use App\Exceptions\StatusAlreadyLikedException; use App\Http\Controllers\Backend\EventController as EventBackend; @@ -23,7 +21,6 @@ use Illuminate\Http\Response; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Session; -use Illuminate\Validation\Rules\Enum; use InvalidArgumentException; /** @@ -78,29 +75,6 @@ public function DeleteStatus(Request $request): JsonResponse|RedirectResponse { return response()->json(['message' => __('controller.status.delete-ok')]); } - public function EditStatus(Request $request): JsonResponse|RedirectResponse { - $validated = $this->validate($request, [ - 'statusId' => ['required', 'exists:statuses,id'], - 'body' => ['nullable', 'max:280'], - 'business_check' => ['required', new Enum(Business::class)], - 'checkinVisibility' => ['required', new Enum(StatusVisibility::class)], - ]); - - try { - $editStatusResponse = StatusBackend::EditStatus( - user: Auth::user(), - statusId: $validated['statusId'], - body: $validated['body'] ?? null, - business: Business::from($validated['business_check']), - visibility: StatusVisibility::from($validated['checkinVisibility']), - ); - } catch (ModelNotFoundException|PermissionException) { - return redirect()->back(); - } - - return response()->json(['new_body' => $editStatusResponse->body], 200); - } - public function createLike(Request $request) { $validated = $request->validate([ 'statusId' => ['required', 'exists:statuses,id'] diff --git a/app/Http/Resources/PointsCalculationResource.php b/app/Http/Resources/PointsCalculationResource.php index 59c668d4b..67bfb2759 100644 --- a/app/Http/Resources/PointsCalculationResource.php +++ b/app/Http/Resources/PointsCalculationResource.php @@ -22,7 +22,7 @@ public function toArray($request): array { 'base' => $this['calculation']['base'], 'distance' => $this['calculation']['distance'], //TODO: This isn't the distance! Should be renamed. 'factor' => $this['calculation']['factor'] ?? 1, - 'reason' => $this['calculation']['reason'] ?? PointReason::IN_TIME + 'reason' => $this['calculation']['reason'] ?? PointReason::IN_TIME->value, ], 'additional' => AdditionalPointsResource::collection($this['additional']) ]; diff --git a/resources/js/components/business-check-in.js b/resources/js/components/business-check-in.js index 1f7aebc22..e2c03dccd 100644 --- a/resources/js/components/business-check-in.js +++ b/resources/js/components/business-check-in.js @@ -1,9 +1,3 @@ -let statusBusiness; -let statusVisibility; -let statusBody; -let statusId = 0; -let statusBodyElement = $("#status-body"); - let businessCheckInput = $("#business_check"); let businessButton = $("#businessDropdownButton"); const businessIcons = ["fa-user", "fa-briefcase", "fa-building"]; @@ -32,31 +26,41 @@ $(".trwl-visibility-item").on("click", function (event) { $(document).on("click", ".edit", function (event) { event.preventDefault(); - statusId = event.currentTarget.dataset.trwlStatusId; - statusBody = document.getElementById("status-" + statusId).dataset.trwlStatusBody; - statusBusiness = document.getElementById("status-" + statusId).dataset.trwlBusinessId; - statusVisibility = document.getElementById("status-" + statusId).dataset.trwlVisibility; - statusBodyElement.val(statusBody); + let statusId = event.currentTarget.dataset.trwlStatusId; + let dataset = document.getElementById("status-" + statusId).dataset; + + document.querySelector("#status-update input[name='statusId']").value = statusId; + document.querySelector("#status-update textarea[name='body']").value = dataset.trwlStatusBody; + + let statusBusiness = dataset.trwlBusinessId; + let statusVisibility = dataset.trwlVisibility; businessCheckInput.val(statusBusiness); visibilityFormInput.val(statusVisibility); setIconForDropdown(statusBusiness, businessButton, businessCheckInput, businessIcons); setIconForDropdown(statusVisibility, visibilityButton, visibilityFormInput, visibilityIcons); - $("#edit-modal").modal("show"); - document.querySelector('#body-length').innerText = document.querySelector('#status-body').value.length; -}); -$(document).on("click", "#modal-trwl-edit-save", function () { - $.ajax({ - method: "POST", - url: urlEdit, - data: { - body: statusBodyElement.val(), - statusId: statusId, - business_check: businessCheckInput.val(), - checkinVisibility: visibilityFormInput.val(), - _token: token + //Clear list + document.querySelector("#status-update select[name='destinationStopoverId']").innerHTML = ""; + + let alternativeDestinations = JSON.parse(dataset.trwlAlternativeDestinations); + if (alternativeDestinations) { + document.querySelector('.destination-wrapper').classList.remove('d-none'); + for (let destId in alternativeDestinations) { + let dest = alternativeDestinations[destId]; + let stopoverId = dest.id; + let stopoverName = dest.name; + let stopoverArrival = dest.arrival_planned; + + let stopoverOption = document.createElement("option"); + stopoverOption.value = stopoverId; + stopoverOption.text = stopoverArrival + ': ' + stopoverName; + document.querySelector("#status-update select[name='destinationStopoverId']").appendChild(stopoverOption); } - }).done(function (msg) { - window.location.reload(); - }); + document.querySelector("#status-update select[name='destinationStopoverId']").value = dataset.trwlDestinationStopover; + } else { + document.querySelector('.destination-wrapper').classList.add('d-none'); + } + + $("#edit-modal").modal("show"); + document.querySelector('#body-length').innerText = document.querySelector('#status-body').value.length; }); diff --git a/resources/lang/de.json b/resources/lang/de.json index fa186aee1..0db71f2d2 100644 --- a/resources/lang/de.json +++ b/resources/lang/de.json @@ -59,7 +59,7 @@ "controller.status.like-deleted": "Like gelöscht.", "controller.status.like-not-found": "Like nicht gefunden.", "controller.status.like-ok": "Liked!", - "controller.status.not-permitted": "DAS darfst Du nicht.", + "controller.status.not-permitted": "Das darfst Du nicht.", "controller.transport.checkin-heading": "Eingecheckt", "controller.transport.checkin-ok": "Du hast erfolgreich in :lineName eingecheckt!|Du hast erfolgreich in Linie :lineName eingecheckt!", "controller.transport.no-name-given": "Du musst einen Stationsnamen angeben!", @@ -437,6 +437,7 @@ "status.visibility.3.detail": "Nur für dich sichtbar", "status.visibility.4": "Nur angemeldete Nutzer", "status.visibility.4.detail": "Nur für angemeldete Nutzer sichtbar", + "status.update.success": "Dein Status wurde aktualisiert.", "time-format": "HH:mm", "time-format.with-day": "HH:mm (DD.MM.YYYY)", "datetime-format": "DD.MM.YYYY HH:mm", diff --git a/resources/lang/de_pfl.json b/resources/lang/de_pfl.json index 74b4f23c7..a07064369 100644 --- a/resources/lang/de_pfl.json +++ b/resources/lang/de_pfl.json @@ -48,7 +48,7 @@ "controller.status.like-deleted": "Gefallt-mer gelescht.", "controller.status.like-not-found": "Gefallt-mer ned gefunn.", "controller.status.like-ok": "Gefallt-mer!", - "controller.status.not-permitted": "DES därfsch du ned.", + "controller.status.not-permitted": "Des därfsch du ned.", "controller.transport.checkin-heading": "Ingecheckt", "controller.transport.checkin-ok": "Du hasch erfolgreich in :lineName ingecheckt!|Du hasch erfoglreich in Linje :lineName ingecheckt!", "controller.transport.no-name-given": "Du musch e Stationsname angewwe!", diff --git a/resources/lang/en.json b/resources/lang/en.json index 233a07d78..84fd0903a 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -86,7 +86,7 @@ "controller.status.like-deleted": "Like deleted.", "controller.status.like-not-found": "Like not found.", "controller.status.like-ok": "Like created!", - "controller.status.not-permitted": "You 're not permitted to do this.", + "controller.status.not-permitted": "You're not permitted to do this.", "controller.social.already-connected-error": "This Account is already connected to another user.", "controller.social.create-error": "There has been an error creating your account.", "controller.social.delete-never-connected": "Your user does not have a Social Login provider.", @@ -413,6 +413,7 @@ "status.visibility.3.detail": "Only visible for you", "status.visibility.4": "Only logged-in users", "status.visibility.4.detail": "Only visible for logged-in users", + "status.update.success": "Your Status has been updated successfully.", "transport_types.bus": "bus", "transport_types.business": "Business Trip", "transport_types.businessPlural": "Business Trips", diff --git a/resources/lang/nl.json b/resources/lang/nl.json index b5a2642d1..1e587da49 100644 --- a/resources/lang/nl.json +++ b/resources/lang/nl.json @@ -64,7 +64,7 @@ "controller.status.like-deleted": "Like verwijdert.", "controller.status.like-not-found": "Like niet gevonden.", "controller.status.like-ok": "Liked!", - "controller.status.not-permitted": "DAT mag je niet doen.", + "controller.status.not-permitted": "Dat mag je niet doen.", "controller.transport.no-name-given": "Je moet een stationsnaam invullen!", "controller.transport.not-in-stopovers": "De start-id is niet in de tussenstops.", "controller.transport.also-in-connection": "Ook in deze verbinding zijn:", diff --git a/resources/lang/sv.json b/resources/lang/sv.json index 05063fb7b..afa542d6e 100644 --- a/resources/lang/sv.json +++ b/resources/lang/sv.json @@ -36,7 +36,7 @@ "controller.status.like-deleted": "Like raderats.", "controller.status.like-not-found": "Like hittades inte.", "controller.status.like-ok": "Liked!", - "controller.status.not-permitted": "DET får du inte.", + "controller.status.not-permitted": "Det får du inte.", "controller.transport.checkin-heading": "Checkat in", "controller.transport.checkin-ok": "Du har lyckats checka in på :lineName!|Du har lyckats checka in på tågförbindelsen :lineName!", "controller.transport.no-name-given": "Du måste ange ett stationsnamn!", diff --git a/resources/views/includes/edit-modal.blade.php b/resources/views/includes/edit-modal.blade.php index 5032fd3da..e5452a09c 100644 --- a/resources/views/includes/edit-modal.blade.php +++ b/resources/views/includes/edit-modal.blade.php @@ -1,15 +1,26 @@