Files
pleno-vue/src/components/displays/user/bookings/bookingsTable.vue
T
Jepp9350 18dc8fb40f Add Department POS module and related views
Introduced Department POS functionality with navigation, orders, and order detail views. Set up routing and middleware for POS operations. Added base styles, assets, and updated package dependencies to support these changes.
2025-02-25 08:20:04 +01:00

397 lines
13 KiB
Vue

<script setup>
defineProps(['objects', 'meta']);
import { ref, watch } from 'vue';
import { departments, getDepartments, isLoading, getDepartmentName} from "@/components/pagination/departmentTabs.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { showDownloadWashCertificate } from "@/components/shop/DownloadWashCertificate.vue";
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
import Swal from 'sweetalert2';
const redirectBookingObjectPage = (objectId) => {
// Send the user to the object page
window.location.href = `/user/bookings/${objectId}`;
};
const openUrlInNewTab = (url) => {
// Open the URL in a new tab
window.open(url, '_blank');
};
const deleteBooking = (object) => {
// Create a delete booking request
authenticatedRequest('/admin/bookings/delete', 'post', {
id: object.id
}).then(
(response) => {
// Create a success alert
Swal.fire({
title: 'Success',
html: 'The booking has been deleted',
icon: 'success',
confirmButtonText: 'Close',
timer: 5000
});
// Change the booking status to cancelled in the bookings array
object.status = 'cancelled';
}
).catch((error) => {
// Create an error alert
Swal.fire({
title: 'Error',
html: '' + error.response.data.data.message,
icon: 'error',
confirmButtonText: 'Close'
});
});
};
const deleteBookingUser = (object) => {
// Create a delete booking request
authenticatedRequest('/user/bookings/delete', 'post', {
id: object.id
}).then(
(response) => {
// Create a success alert
Swal.fire({
title: 'Success',
html: 'Bookingen er blevet annulleret',
icon: 'success',
confirmButtonText: 'Luk',
timer: 5000
});
// Change the booking status to cancelled in the bookings array
object.status = 'cancelled';
}
).catch((error) => {
// Create an error alert
Swal.fire({
title: 'Error',
html: '' + error.response.data.data.message,
icon: 'error',
confirmButtonText: 'Close'
});
});
};
const showDeleteBooking = (object) => {
// Show a delete booking alert
Swal.fire({
title: 'Annuller booking',
html: 'Er du sikker på at du vil annullere bookingen?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Annuller booking',
cancelButtonText: 'Fortryd',
allowOutsideClick: false,
allowEscapeKey: false,
allowEnterKey: false
}).then((result) => {
if (result.isConfirmed) {
// Delete the booking
if (SessionUser.canAccessAdmin() === true) {
deleteBooking(object);
} else {
deleteBookingUser(object);
}
}
});
};
const showCompleteWashWithoutWashCertificate = (object) => {
// Show a complete wash without wash certificate alert
Swal.fire({
title: 'Gennemfør vask uden vaskecertifikat',
html: 'Skriv booking ID\'et for at gennemføre vasken uden vaskecertifikat',
icon: 'warning',
input: 'text',
showCancelButton: true,
confirmButtonText: 'Yes',
cancelButtonText: 'No',
allowOutsideClick: false,
allowEscapeKey: false,
allowEnterKey: false,
inputValidator: (value) => {
if (!value) {
return 'Du skal skrive booking ID\'et for at gennemføre vasken uden vaskecertifikat';
}
if (object.id !== parseInt(value)) {
return 'Booking ID\'et er forkert';
}
}
}).then((result) => {
if (result.isConfirmed) {
// Complete the wash without wash certificate
completeWashWithoutWashCertificate(object);
}
});
};
const completeWashWithoutWashCertificate = (object) => {
// Create a complete wash without wash certificate request
authenticatedRequest('/admin/bookings/completeWashWithoutWashCertificate', 'post', {
id: object.id
}).then(
(response) => {
// Create a success alert
Swal.fire({
title: 'Success',
html: 'The wash has been completed',
icon: 'success',
confirmButtonText: 'Close',
timer: 5000
});
// Change the booking status to completed in the bookings array
object.status = 'completed';
}
).catch((error) => {
// Create an error alert
Swal.fire({
title: 'Error',
html: '' + error.response.data.data.message,
icon: 'error',
confirmButtonText: 'Close'
});
});
};
// Get the departments (If the departments are not already loaded)
if (departments.value.length === 0) {
getDepartments();
}
const renderCustomerName = (object) => {
if (object.customer_name) {
return object.customer_name;
}
return 'N/A';
};
// If the screen is mobile, set the is small variable to true
const isSmall = ref(window.innerWidth < 1024);
// When the window is resized, check if the screen is mobile
window.addEventListener('resize', () => {
isSmall.value = window.innerWidth < 1024;
});
/**
* To organize the bookings, we want to show the bookings that are created today first, then yesterday, then all other days
* @type {string}
*/
const currentDate = new Date().toISOString().split("T")[0];
/**
* Sort the bookings by date
* @param a
* @param b
* @returns {number}
*/
const sortBookingsByDate = (a, b) => {
if (a.date === currentDate && b.date !== currentDate) {
return -1;
}
if (a.date !== currentDate && b.date === currentDate) {
return 1;
}
if (a.date < b.date) {
return 1;
}
if (a.date > b.date) {
return -1;
}
return 0;
};
/**
* Sort the bookings by date and return the sorted bookings with label rows for the date changes
* @param bookings
* @returns {*}
*/
const parseAndSortBookings = (bookings) => {
// Sort the bookings by date
bookings.sort(sortBookingsByDate);
// Create a new array for the bookings
let newBookings = [];
// Create a variable for the last date
let lastDate = null;
// Loop through the bookings
for (let i = 0; i < bookings.length; i++) {
// If the last date is not the same as the current date, add a label row
if (lastDate !== bookings[i].date) {
newBookings.push({
id: 'label-' + i,
date: bookings[i].date,
isLabel: true
});
lastDate = bookings[i].date;
}
// Add the booking to the new bookings array
newBookings.push(bookings[i]);
}
// Return the new bookings array
return newBookings;
};
</script>
<template>
<div>
<table class="table is-fullwidth is-hoverable is-narrow" :class="isSmall ? 'is-hidden' : ''">
<thead>
<tr>
<th>ID</th>
<th>Afdeling</th>
<th>Kunde navn</th>
<th>Kunde nummer</th>
<th>Dato</th>
<th>Reg. Trækker</th>
<th>Reg. Trailer</th>
<th>Reference</th>
<th>Noter</th>
<th>Skal hentes?</th>
<th>Oprettet</th>
<th>Vaskecertifikat</th>
<th>Handlinger</th>
</tr>
</thead>
<tbody>
<tr v-for="object in parseAndSortBookings(objects)" :key="object.id">
<template v-if="object.isLabel">
<td colspan="13" class="has-text-left">
<span class="has-text-grey">
<i class="fas fa-calendar-day"></i>
</span>
<span class="has-text-grey">
<!-- Human readable date (Today, Yesterday, etc.) -->
{{ object.date === currentDate ? 'I dag' : object.date === new Date(new Date().setDate(new Date().getDate() - 1)).toISOString().split("T")[0] ? 'I går' : object.date }}
<!-- Number of bookings on the date -->
({{ objects.filter((booking) => booking.date === object.date).length }} bookinger)
</span>
</td>
</template>
<template v-else>
<td>{{ object.id }}</td>
<td>{{ getDepartmentName(object.department) }}</td>
<td>{{ renderCustomerName(object) }}</td>
<td>{{ object.customer_number }}</td>
<td>{{ object.date }}</td>
<td>{{ object.regNrTraekker }}</td>
<td>{{ object.regNrTrailer }}</td>
<td>{{ object.reference_number }}</td>
<td>{{ object.notes }}</td>
<td>{{ object.pickup_bool == 1 ? "Ja" : "Nej" }}</td>
<td>{{ object.created_at }}</td>
<td>
<div class="buttons">
<!-- If the washCertificateUrl is not empty, show the button to download the wash certificate -->
<button v-if="object.status === 'completed' && object.washCertificateStatus !== 'cancelled'" class="button is-small is-dark is-text" @click="showDownloadWashCertificate(object.id)" style="text-decoration-line: none;">
<span class="icon">
<i class="fas fa-file-download"></i>
</span>
<span>Download</span>
</button>
</div>
</td>
<td>
<div class="buttons is-right">
<!-- If the status is pending, show the create wash certificate button -->
<button v-if="object.status === 'pending' && SessionUser.canAccessAdmin()" class="button is-small is-warning" @click="openUrlInNewTab('https://www.truckwash.dk/vaskecertifikat-administration/?wash_id=' + object.id + '&auth_key=' + meta.wash_certificate_token)">
<span class="icon">
<i class="fas fa-edit"></i>
</span>
</button>
<button v-if="(object.status === 'pending')" class="button is-small is-danger is-inverted" @click="showDeleteBooking(object)">
<span class="icon">
<i class="fas fa-trash"></i>
</span>
<span v-if="!SessionUser.canAccessAdmin()">Annuller</span>
</button>
<!-- If the status is completed, show a green checkmark -->
<button v-if="object.status === 'completed'" class="button is-small is-success is-inverted" >
<span class="icon">
<i class="fas fa-check"></i>
</span>
<span>Fuldført</span>
</button>
<!-- If the status is cancelled -->
<button v-else-if="object.status === 'cancelled'" class="button is-small is-danger is-inverted" >
<span>Annulleret</span>
</button>
</div>
</td>
</template>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="13">Viser {{ objects.length }} bookinger {{ isSmall ? '' : 'i alt' }}</td>
</tr>
</tfoot>
</table>
<!-- If the screen is mobile, show the mobile cards instead -->
<div class="columns is-multiline is-hidden-desktop mb-2" v-if="isSmall">
<div class="column is-one-third-tablet" v-for="object in objects" :key="object.id">
<div class="card">
<div class="card-content">
<div class="content">
<div class="is-pulled-right">
<button v-if="object.status === 'completed'" class="button is-small is-success is-inverted" >
<span class="icon">
<i class="fas fa-check"></i>
</span>
<span>Fuldført</span>
</button>
<button v-else-if="object.status === 'pending'" class="button is-small is-warning is-inverted" >
<span class="icon">
<i class="fas fa-clock"></i>
</span>
<span>Afventer</span>
</button>
<button v-if="(SessionUser.canAccessAdmin() && object.status === 'pending')" class="button is-small is-danger is-inverted" @click="showDeleteBooking(object)">
<span class="icon">
<i class="fas fa-trash"></i>
</span>
<span>Slet</span>
</button>
<button v-else-if="object.status === 'cancelled'" class="button is-small is-danger is-inverted" >
<span class="icon">
<i class="fas fa-ban"></i>
</span>
<span>Annulleret</span>
</button>
</div>
<p><strong>ID:</strong> {{ object.id }}</p>
<p><strong>Afdeling:</strong> {{ getDepartmentName(object.department) }}</p>
<p><strong>Dato:</strong> {{ object.date }}</p>
<p><strong>Reg. Trækker:</strong> {{ object.regNrTraekker }}</p>
<p><strong>Reg. Trailer:</strong> {{ object.regNrTrailer }}</p>
<p><strong>Reference:</strong> {{ object.reference_number }}</p>
<p><strong>Noter:</strong> {{ object.notes }}</p>
<p><strong>Skal hentes?</strong> {{ object.pickup_bool == 1 ? "Ja" : "Nej" }}</p>
<p v-if="object.status === 'completed'"><strong>Vaskecertifikat: </strong>
<button v-if="object.status === 'completed'" class="button is-small is-dark is-text" @click="showDownloadWashCertificate(object.id)" style="text-decoration-line: none;">
<span class="icon">
<i class="fas fa-file-download"></i>
</span>
<span>Download</span>
</button>
<button v-else-if="SessionUser.canAccessAdmin() && object.status !== 'cancelled'" class="button is-small is-warning" @click="openUrlInNewTab('https://www.truckwash.dk/vaskecertifikat-administration/?wash_id=' + object.id + '&auth_key=' + meta.wash_certificate_token)">
<span class="icon">
<i class="fas fa-edit"></i>
</span>
</button>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>