From bee47e54bbbaafb929371e9aa3273191f9604444 Mon Sep 17 00:00:00 2001 From: Ethan Clay <Ethan2.Clay@live.uwe.ac.uk> Date: Fri, 21 Feb 2025 11:43:05 +0000 Subject: [PATCH] Finish manage reports, update manage bookings to include cancel within view booking, begin to add cancellation % and refund amount from spec --- app/bookings/routes.py | 4 +- app/main/utils.py | 15 ++++ app/profile/routes.py | 10 ++- app/templates/admin/manage_bookings.html | 1 - app/templates/admin/reports.html | 79 ++++++++++++++++------ app/templates/profile/manage_bookings.html | 77 ++------------------- app/templates/profile/view_booking.html | 20 +++++- 7 files changed, 110 insertions(+), 96 deletions(-) diff --git a/app/bookings/routes.py b/app/bookings/routes.py index b859775..f439d53 100644 --- a/app/bookings/routes.py +++ b/app/bookings/routes.py @@ -193,7 +193,7 @@ def listing(id): else: base_price = listing.economy_fair_cost - main_image_url = None + main_image_url = 'booking_image_not_found.jpg' for image in listing.listing_images: if image.main_image == 1: main_image_url = image.image_location @@ -310,7 +310,7 @@ def filter_bookings(): depart_location = data.get('depart_location', []) destination_location = data.get('destination_location', []) depart_date = data.get('date') - seat_type = data.get('seatType', 'economy') # Default to economy + seat_type = data.get('seatType', 'economy') page = int(data.get('page', 1)) per_page = 10 # How many listings show per page diff --git a/app/main/utils.py b/app/main/utils.py index 756b2ca..fb37247 100644 --- a/app/main/utils.py +++ b/app/main/utils.py @@ -38,6 +38,21 @@ def calculate_discount(date): else: return 0, days_away + +def calculate_refund_amount(amount_paid): + depart_date = datetime.strptime(date, '%Y-%m-%d') + today = datetime.now() + days_away = (depart_date - today).days + + if 80 <= days_away <= 90: + return 25, days_away + elif 60 <= days_away <= 79: + return 15, days_away + elif 45 <= days_away <= 59: + return 10, days_away + else: + return 0, days_away + def pretty_time(unformatted_time, to_12_hour=True): if not isinstance(unformatted_time, (datetime, time)): diff --git a/app/profile/routes.py b/app/profile/routes.py index d2d3560..614e548 100644 --- a/app/profile/routes.py +++ b/app/profile/routes.py @@ -4,7 +4,7 @@ from flask_principal import Identity, identity_changed from flask_login import login_user, logout_user, login_required, current_user from werkzeug.security import check_password_hash from app.profile import bp -from app.main.utils import pretty_time +from app.main.utils import pretty_time, calculate_refund_amount from app.models import User, Bookings, Listings from app.logger import auth_logger from app import db, permission_required, user_permission @@ -281,4 +281,12 @@ def manage_profile_view_booking(id): booking = Bookings.search_booking(id) booking.listing.destination_time = pretty_time(booking.listing.destination_time) booking.listing.depart_time = pretty_time(booking.listing.depart_time) + booking.cancelled_date = pretty_time(booking.cancelled_date) + + cancel_amount, cancel_percentage = calculate_refund_amount() + refund = { + amount: + percentage: + } + return render_template('profile/view_booking.html', booking=booking) diff --git a/app/templates/admin/manage_bookings.html b/app/templates/admin/manage_bookings.html index e030c69..ad58f3e 100644 --- a/app/templates/admin/manage_bookings.html +++ b/app/templates/admin/manage_bookings.html @@ -155,7 +155,6 @@ $('#arrive_after_time').append(new Option(time, time)); }); - // Populate location options, TO UPDATE WITH LIVE LOCATIONS const locations = JSON.parse('{{ locations|tojson|safe }}'); locations.forEach(location => { $('#depart_location').append(new Option(location, location)); diff --git a/app/templates/admin/reports.html b/app/templates/admin/reports.html index 2b702bf..0437f90 100644 --- a/app/templates/admin/reports.html +++ b/app/templates/admin/reports.html @@ -6,19 +6,43 @@ <title>Reporting</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> + <style> + .chart-container { + position: relative; + width: 100%; + height: 300px; + margin-bottom: 30px; /* Margin at the bottom to prevent overlap */ + } + .card h3 { + text-align: center; + } + .chart-center { + display: flex; + justify-content: center; + align-items: center; + } + </style> </head> <body> <div class="container"> <h1 class="text-center my-4">Reporting</h1> - - <form method="get" action="{{ url_for('admin.reports') }}" class="form-inline mb-4"> + <form method="get" action="{{ url_for('admin.reports') }}" class="form mb-4"> <div class="form-group mx-sm-3 mb-2"> - <label for="days" class="sr-only">Show results for the last</label> - <input type="number" class="form-control" id="days" name="days" value="{{ days }}" min="1" placeholder="Days"> + <div class="row"> + <div class="col-12"> + <label for="days" class="form-label">Show results for the last {{ days }} days</label> + </div> + </div> + <div class="row"> + <div class="col-4"> + <input type="number" class="form-control" id="days" name="days" value="{{ days }}" min="1" placeholder="Days"> + </div> + <div class="col-1"> + <button type="submit" class="btn btn-primary mb-2">Update</button> + </div> + </div> </div> - <button type="submit" class="btn btn-primary mb-2">Update</button> - </form> - + </form> <div class="row mb-4"> <div class="col-md-6"> <div class="card text-white bg-info mb-3"> @@ -41,26 +65,34 @@ <div class="row"> <div class="col-md-6 mb-4"> <h3>Destination Locations</h3> - <canvas id="destinationChart" height="200"></canvas> + <div class="chart-container chart-center"> + <canvas id="destinationChart"></canvas> + </div> </div> <div class="col-md-6 mb-4"> <h3>User Count</h3> - <canvas id="roleCountChart" height="200"></canvas> + <div class="chart-container chart-center"> + <canvas id="roleCountChart"></canvas> + </div> </div> </div> <div class="row"> <div class="col-md-6 mb-4"> <h3>Number of Seats Booked Per Day</h3> - <canvas id="seatsBookedChart" height="200"></canvas> + <div class="chart-container chart-center"> + <canvas id="seatsBookedChart"></canvas> + </div> </div> <div class="col-md-6 mb-4"> - <h3>Depart Dates</h3> - <canvas id="departDateChart" height="200"></canvas> + <h3>Departure Dates</h3> + <div class="chart-container chart-center"> + <canvas id="departDateChart"></canvas> + </div> </div> </div> - </div> + </div> <script> - // Destination Locations Pie Chart + // Destination Location var destinationData = { labels: {{ destinations|map(attribute=0)|list|tojson }}, datasets: [{ @@ -83,10 +115,14 @@ var ctx = document.getElementById('destinationChart').getContext('2d'); var destinationChart = new Chart(ctx, { type: 'pie', - data: destinationData + data: destinationData, + options: { + responsive: true, + maintainAspectRatio: false + } }); - // User Role Count Bar Chart + // User Count var roleCountData = { labels: {{ user_counts|map(attribute=0)|list|tojson }}, datasets: [{ @@ -103,6 +139,8 @@ type: 'bar', data: roleCountData, options: { + responsive: true, + maintainAspectRatio: false, scales: { y: { beginAtZero: true @@ -111,7 +149,7 @@ } }); - // Number of Seats Booked Per Day Line Chart + // Seats booked per day var seatsBookedData = { labels: {{ seats_booked_per_day|map(attribute=0)|list|tojson }}, datasets: [{ @@ -128,6 +166,8 @@ type: 'bar', data: seatsBookedData, options: { + responsive: true, + maintainAspectRatio: false, scales: { y: { beginAtZero: true @@ -136,7 +176,7 @@ } }); - // Depart Dates Line Chart + // Departure dates var departDateData = { labels: {{ depart_dates|map(attribute=0)|list|tojson }}, datasets: [{ @@ -153,6 +193,8 @@ type: 'bar', data: departDateData, options: { + responsive: true, + maintainAspectRatio: false, scales: { y: { beginAtZero: true @@ -163,5 +205,4 @@ </script> </body> </html> - {% endblock %} diff --git a/app/templates/profile/manage_bookings.html b/app/templates/profile/manage_bookings.html index c2a0edc..ff32bbb 100644 --- a/app/templates/profile/manage_bookings.html +++ b/app/templates/profile/manage_bookings.html @@ -20,7 +20,7 @@ <th>Departure Location</th> <th>Destination Location</th> <th>Cancelled</th> - <th>Actions</th> + <th>View Booking</th> </tr> </thead> <tbody> @@ -33,40 +33,13 @@ <td>{{ booking.destination_location }}</td> <td>{{ 'Yes' if booking.cancelled else 'No' }}</td> <td class="dt-center"> - <div class="dropdown"> - <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-expanded="false"> - Actions - </button> - <div class="dropdown-menu" aria-labelledby="dropdownMenuButton"> - <a class="dropdown-item edit-btn" href="#">View</a> - <a class="dropdown-item delete-btn" href="#">Cancel Booking</a> - </div> - </div> + <a class="btn btn-secondary view-booking-btn" href="manage_bookings/view/{{ booking.id }}">View Booking</a> </td> </tr> {% endfor %} </tbody> </table> </div> - <!-- Cancel booking modal --> - <div class="modal fade" id="confirm_booking_deletion" tabindex="-1"> - <div class="modal-dialog"> - <div class="modal-content"> - <div class="modal-header"> - <h5 class="modal-title" id="confirm_booking_deletion">Confirm Cancellation</h5> - <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> - </div> - <div class="modal-body"> - <p>Type 'CONFIRM' to cancel your booking:</p> - <input type="text" id="confirmation_input" class="form-control"> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> - <button type="button" class="btn btn-primary" id="confirm_deletion_button">Cancel Booking</button> - </div> - </div> - </div> - </div> <!-- Filter modal --> <div class="modal fade" id="filterModal" tabindex="-1" aria-labelledby="filterModalLabel" aria-hidden="true"> <div class="modal-dialog"> @@ -109,7 +82,6 @@ </div> </div> </div> - <style> .table-container { width: 100%; @@ -144,7 +116,6 @@ minimumResultsForSearch: Infinity }); - // Populate location options, TO UPDATE WITH LIVE LOCATIONS const locations = JSON.parse('{{ locations|tojson|safe }}'); locations.forEach(location => { $('#depart_location').append(new Option(location, location)); @@ -179,23 +150,15 @@ data: null, className: "dt-center", defaultContent: ` - <div class="dropdown"> - <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-expanded="false"> - Actions - </button> - <div class="dropdown-menu" aria-labelledby="dropdownMenuButton"> - <a class="dropdown-item edit-btn" href="#">View</a> - <a class="dropdown-item delete-btn" href="#">Cancel Booking</a> - </div> - </div>` + <a class="btn btn-secondary view-booking-btn" href="#">View Booking</a> + ` } ], language: { emptyTable: "No bookings could be found." }, createdRow: function(row, data, dataIndex) { - $(row).find('.edit-btn').attr('data-id', data.id); - $(row).find('.delete-btn').attr('data-id', data.id); + $(row).find('.view-booking-btn').attr('href', `manage_bookings/view/${data.id}`); } }); @@ -203,36 +166,6 @@ table.ajax.reload(); $('#filterModal').modal('hide'); }); - - $('#manage_bookings tbody').on('click', '.edit-btn', function() { - const id = $(this).data('id'); - window.location.href = `manage_bookings/view/${id}`; - }); - - let delete_booking_id; - $('#manage_bookings tbody').on('click', '.delete-btn', function() { - delete_booking_id = $(this).data('id'); - $('#confirm_booking_deletion').modal('show'); - }); - - $('#confirm_deletion_button').on('click', function() { - const confirmationInput = $('#confirmation_input').val(); - if (confirmationInput === 'CONFIRM') { - $.ajax({ - url: `{{ url_for('profile.cancel_booking') }}`, - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({ booking_id: delete_booking_id }), - success: function(response) { - $('#confirm_booking_deletion').modal('hide'); - table.ajax.reload(); - }, - error: function(xhr, status, error) { - alert('An error occurred while cancelling the booking.'); - } - }); - } - }); }); </script> {% endblock %} diff --git a/app/templates/profile/view_booking.html b/app/templates/profile/view_booking.html index 65d71a8..c0e36ca 100644 --- a/app/templates/profile/view_booking.html +++ b/app/templates/profile/view_booking.html @@ -15,6 +15,9 @@ <p><strong>Departure Time:</strong> {{ booking.listing.depart_time }}</p> <p><strong>Seat Type:</strong> {{ booking.seat_type.capitalize() }}</p> <p><strong>Total Cost:</strong> £{{ booking.amount_paid }}</p> + {% if booking.cancelled %} + <p><strong>Cancellation Date:</strong> £{{ booking.cancelled_date }}</p> + {% endif %} </div> <div class="col-md-6"> <p><strong>Destination Location:</strong> {{ booking.listing.destination_location }}</p> @@ -22,11 +25,14 @@ <p><strong>Number of Seats:</strong> {{ booking.num_seats }}</p> <p><strong>Cost Per Person:</strong> £{{ booking.amount_paid / booking.num_seats }}</p> <p><strong>Cancelled:</strong> {{ 'Yes' if booking.cancelled else 'No' }}</p> + {% if booking.cancelled %} + <p><strong>Refunded Amount:</strong> £{{ booking.cancelled_refund }}</p> + {% endif %} </div> </div> </div> </div> - <div class="card shadow-sm"> + <div class="card mb-4 shadow-sm"> <div class="card-body text-center"> <h3 class="card-title mb-4">Re-Download Booking Details</h3> {% if booking.cancelled %} @@ -47,6 +53,18 @@ {% endif %} </div> </div> + {% if not booking.cancelled %} + <div class="card shadow-sm"> + <div class="card-body text-center"> + <h3 class="card-title mb-4">Cancel Booking</h3> + <div class="alert alert-info" role="alert"> + Need to cancel? Not a problem as you are x days away you are entitled to a x% refund! Your refunded amount will be £xx.x which + will automatically be refunded to your card ending in 1234 + </div> + <button type="button" class="btn btn-danger btn-lg mr-2">Cancel Booking</button> + </div> + </div> + {% endif %} </div> </div> </div> -- GitLab