Files
pleno-vue/src/components/displays/user/bookings/bookingsTable.vue
T

534 lines
19 KiB
Vue

<script setup>
import {useRouter} from "vue-router";
import { loadList } from "@/components/pagination/paginatedList.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
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';
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
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: t('common.success'),
html: t('tables.bookings.booking_cancelled'),
icon: 'success',
confirmButtonText: t('common.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 showDeleteBooking = (object) => {
// Show a delete booking alert
Swal.fire({
title: t('tables.bookings.cancel_booking'),
html: t('tables.bookings.cancel_booking_confirm'),
icon: 'warning',
showCancelButton: true,
confirmButtonText: t('tables.bookings.cancel_booking'),
cancelButtonText: t('tables.bookings.regret'),
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: t('tables.bookings.complete_wash_without_certificate'),
html: t('tables.bookings.complete_wash_without_certificate_prompt'),
icon: 'warning',
input: 'text',
showCancelButton: true,
confirmButtonText: t('common.yes'),
cancelButtonText: t('common.no'),
allowOutsideClick: false,
allowEscapeKey: false,
allowEnterKey: false,
inputValidator: (value) => {
if (!value) {
return t('tables.bookings.booking_id_required');
}
if (object.id !== parseInt(value)) {
return t('tables.bookings.booking_id_incorrect');
}
}
}).then((result) => {
if (result.isConfirmed) {
// Complete the wash without wash certificate
completeWashWithoutWashCertificate(object);
}
});
};
const redirectBookingObjectFillCertificatePage = (object) => {
// Send the user to the object page
window.location.href = "/admin/" + object.department + "/modules/bookings/" + object.id;
};
const redirectBookingObjectFillCertificatePageLegacy = (object) => {
// Send the user to the object page
window.location.href = "/admin/bookings/" + object.id;
};
const onClickPending = (object, token = undefined) => {
console.log('onClickPending', object);
// Check if the user has access to create an order.
//if (!SessionUser.hasPermission('add_order')) {
// If the user does not have access, show an error alert
//console.log('Du har ikke adgang til at oprette en ordre');
redirectBookingObjectFillCertificatePage(object);
//}
// Redirect the user to the (old) page to fill out the certificate
// openUrlInNewTab('https://www.truckwash.dk/vaskecertifikat-administration/?wash_id=' + object.id + '&auth_key=' + token);
}
const createOrderFromBooking = (object) => {
// Create an order from the booking request
console.log('Create order from booking', object);
};
const showCreateOrderFromBooking = (object) => {
Swal.fire({
title: t('tables.bookings.create_order_from_booking'),
html: t('tables.bookings.create_order_from_booking_confirm'),
icon: 'warning',
showCancelButton: true,
confirmButtonText: t('common.yes'),
cancelButtonText: t('common.no'),
allowOutsideClick: false,
allowEscapeKey: false,
allowEnterKey: false
}).then((result) => {
if (result.isConfirmed) {
// Create an order from the booking
createOrderFromBooking(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: t('common.success'),
html: t('tables.bookings.wash_completed'),
icon: 'success',
confirmButtonText: t('common.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;
};
const showShort = (text, length, count = 0) => {
if (text.length > length) {
// If the text is too long, show a short version of it
let suffix = '';
if (count > 1) {
console.log(count)
suffix = ' (' + count + ')';
}
return text.substring(0, length) + '...' + suffix
}
return text;
};
const getWashServicesString = (object) => {
if (object.parsed_services && object.parsed_services.string) {
// If the services are already parsed, return the string
return object.parsed_services.string;
} else {
return object.wash_type;
}
};
const getWashServices = (object) => {
if (object.parsed_services && object.parsed_services.array) {
// If the services are already parsed, return the services
return object.parsed_services.array;
} else {
return [];
}
};
const canUserEditObject = (object) => {
// Check if the user can edit the object
return SessionUser.canAccessAdmin() || (SessionUser.canAccessDepartment(object.department) && object.status === 'pending') || (SessionUser.user.customer_number.value === object.customer_number && object.status === 'pending');
};
</script>
<template>
<div>
<table class="table is-hoverable is-narrow is-fullwidth" :class="isSmall ? 'is-hidden' : ''" style="table-layout: fixed;">
<thead>
<tr>
<th>{{ $t('objects.columns.id') }}</th>
<th>{{ $t('objects.columns.department') }}</th>
<th>{{ $t('tables.bookings.customer_name') }}</th>
<th>{{ $t('objects.columns.customer_number') }}</th>
<th>{{ $t('common.date') }}</th>
<th>{{ $t('tables.bookings.reg_tractor') }}</th>
<th>{{ $t('tables.bookings.reg_trailer') }}</th>
<th>{{ $t('common.reference') }}</th>
<th>{{ $t('objects.columns.notes') }}</th>
<th>{{ $t('tables.bookings.pickup_required') }}</th>
<th>{{ $t('common.created') }}</th>
<th>{{ $t('tables.bookings.wash_certificate_type') }}</th>
<th>{{ $t('tables.actions') }}</th>
</tr>
</thead>
<tbody>
<tr
v-for="object in parseAndSortBookings(objects)"
:key="object.id"
:data-testid="object.isLabel ? undefined : `user-booking-row-${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 ? $t('tables.bookings.today') : object.date === new Date(new Date().setDate(new Date().getDate() - 1)).toISOString().split("T")[0] ? $t('tables.bookings.yesterday') : object.date }}
<!-- Number of bookings on the date -->
({{ $t('tables.bookings.bookings_count', { count: objects.filter((booking) => booking.date === object.date).length }) }})
</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>
<!-- Reference number -->
<EditableTableColumn
:object="object"
:loadList="loadList"
column="reference_number"
:parse-function="(value) => value"
:edit-function="SessionUser.objects.bookings.showEditObjectFieldForm"
:permission-check-function="() => canUserEditObject(object)"
/>
<td>{{ object.notes }}</td>
<td>{{ object.pickup_bool == 1 ? $t('common.yes') : $t('common.no') }}</td>
<td>{{ object.created_at }}</td>
<td>
<div class="buttons is-narrow">
<!-- Show the type of wash -->
<button class="button is-small is-text"
@click="Swal.fire(
t('common.services') + ': ' + getWashServicesString(object),
'',
'info'
)">
<span>
{{ showShort(getWashServicesString(object), 12, getWashServices(object).length) }}</span>
</button>
<!-- Show the wash certificate -->
<button class="button is-small is-text"
v-if="object.washCertificateStatus === 'completed'"
@click="showDownloadWashCertificate(object.id)">
<span class="icon">
<i class="fas fa-file-pdf"></i>
</span>
<span>{{object.wash_certificate_pdf ?? 'wash_certificate_' + object.id + '.pdf'}}</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() && object.wash_certificate_pdf === null" class="button is-small is-warning" @click="onClickPending(object, meta.wash_certificate_token)">
<span class="icon">
<i class="fas fa-edit"></i>
</span>
</button>
<button v-if="(object.status === 'pending' && object.wash_certificate_pdf === null)" 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()">{{ $t('tables.bookings.cancel') }}</span>
</button>
<!-- If the status is completed, show a green checkmark -->
<template v-if="object.status === 'completed' || object.wash_certificate_pdf !== null">
<button class="button is-small is-success" @click="onClickPending(object, meta.wash_certificate_token)">
<span class="icon">
<i class="fas fa-check"></i>
</span>
<span>{{ $t('tables.bookings.completed') }}</span>
</button>
</template>
<!-- If the status is cancelled -->
<button v-else-if="object.status === 'cancelled'" class="button is-small is-danger is-inverted" >
<span>{{ $t('tables.bookings.cancelled') }}</span>
</button>
</div>
</td>
</template>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="13">{{ $t('tables.showing') }} {{ objects.length }} {{ $t('objects.bookings.multiple') }} {{ isSmall ? '' : $t('tables.in_total') }}</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" :data-testid="`user-booking-card-${object.id}`">
<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>{{ $t('tables.bookings.completed') }}</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>{{ $t('tables.bookings.pending') }}</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>{{ $t('tables.bookings.delete') }}</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>{{ $t('tables.bookings.cancelled') }}</span>
</button>
</div>
<p><strong>{{ $t('objects.columns.id') }}:</strong> {{ object.id }}</p>
<p><strong>{{ $t('objects.columns.department') }}:</strong> {{ getDepartmentName(object.department) }}</p>
<p><strong>{{ $t('common.date') }}:</strong> {{ object.date }}</p>
<p><strong>{{ $t('tables.bookings.reg_tractor') }}:</strong> {{ object.regNrTraekker }}</p>
<p><strong>{{ $t('tables.bookings.reg_trailer') }}:</strong> {{ object.regNrTrailer }}</p>
<p><strong>{{ $t('common.reference') }}:</strong> {{ object.reference_number }}</p>
<p><strong>{{ $t('objects.columns.notes') }}:</strong> {{ object.notes }}</p>
<p><strong>{{ $t('tables.bookings.pickup_required') }}</strong> {{ object.pickup_bool == 1 ? $t('common.yes') : $t('common.no') }}</p>
<p><strong>{{ $t('common.created') }}:</strong> {{ object.created_at }}</p>
<p><strong>{{ $t('tables.bookings.wash_certificate_type') }}:</strong>
<button class="button is-small is-text"
@click="Swal.fire(
t('tables.bookings.wash_type') + ': ' + object.wash_type,
'',
'info'
)">
<span>
{{ showShort(object.wash_type, 12) }}</span>
</button>
</p>
<div class="buttons is-right">
<button class="button is-small is-dark" @click="onClickPending(object, meta.wash_certificate_token)">
<span class="icon">
<i class="fas fa-eye"></i>
</span>
<span>{{ $t('tables.bookings.view') }}</span>
</button>
<button v-if="object.status === 'pending'" class="button is-small is-warning" @click="onClickPending(object, meta.wash_certificate_token)">
<span class="icon">
<i class="fas fa-edit"></i>
</span>
</button>
<button v-if="object.status === 'completed'" class="button is-small is-success" @click="onClickPending(object, meta.wash_certificate_token)">
<span class="icon">
<i class="fas fa-check"></i>
</span>
<span>{{ $t('tables.bookings.completed') }}</span>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
</style>