534 lines
19 KiB
Vue
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>
|