Use local date-only parsing and formatting across date inputs and filters so clicked dates persist exactly.\n\nVerified with local format, lint, i18n, build, full unit tests, PR E2E, focused date E2E, broad smoke reruns, and GitHub PR checks.
391 lines
15 KiB
Vue
391 lines
15 KiB
Vue
<script setup>
|
|
|
|
import TableLabeledPagination from "@/components/displays/pagination/TableLabeledPagination.vue";
|
|
|
|
const props = defineProps({
|
|
onlyFromInvoiceCollection: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
autoLoad: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
setCustomerFilter: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
hideSearch: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
invoiceView: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
applyDefaultFilters: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
hidePagination: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
hideFilter: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
dates: {
|
|
type: Object,
|
|
default: () => ({
|
|
dateFrom: null,
|
|
dateTo: null
|
|
})
|
|
},
|
|
groupInvoiceCollection: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
limitResults: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
showOnlyWithIds: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
autoExpandAll: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// Additional query parameters to add to the endpoint
|
|
queryParameters : {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
excludedOrderIds: {
|
|
type: Array, // Used to exclude specific order ids from the list, this will also ignore warnings about hidden orders
|
|
default: () => []
|
|
},
|
|
includeSystemOrders: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
showDraftAssignmentActions: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
invoicePeriodFlags: {
|
|
type: Array,
|
|
default: () => []
|
|
}
|
|
})
|
|
const emit = defineEmits(["flagStatusChanged", "flagCreated"]);
|
|
import { computed, ref, provide } from "vue";
|
|
import { useRouter } from "vue-router";
|
|
import {
|
|
usePaginatedList,
|
|
PaginatedListKey,
|
|
} from "@/components/pagination/paginatedList.vue";
|
|
|
|
const paginatedList = usePaginatedList();
|
|
provide(PaginatedListKey, paginatedList);
|
|
|
|
const {
|
|
isLoading,
|
|
list,
|
|
loadList,
|
|
setEndpoint,
|
|
setMetaItemsPerPage,
|
|
setPage,
|
|
search,
|
|
endpoint,
|
|
setFilter,
|
|
setAdditionalQueryParameters,
|
|
setOrder,
|
|
setHideSearchField,
|
|
} = paginatedList;
|
|
import OrdersTable from "@/components/displays/department/pos/orders/ordersTable.vue";
|
|
import {SessionUser} from "@/components/session/token/SessionUser.vue";
|
|
import PaginationDisplayFilters from "@/components/displays/pagination/PaginationDisplayFilters.vue";
|
|
import DatePeriodSelector from "@/components/displays/buttons/DatePeriodSelector.vue";
|
|
import { useI18n } from 'vue-i18n'
|
|
import { formatLocalDateOnly, parseLocalDateOnly, todayLocalDateOnly } from "@/services/dateOnly.js";
|
|
|
|
const { t } = useI18n()
|
|
const router = useRouter();
|
|
const areFiltersVisible = ref(!props.hideFilter);
|
|
const filterToggleLabel = computed(() => (
|
|
areFiltersVisible.value ? t("pagination.hide_filters") : t("pagination.show_filters")
|
|
));
|
|
const filterToggleIcon = computed(() => (areFiltersVisible.value ? "fa-chevron-up" : "fa-filter"));
|
|
setHideSearchField(props.hideSearch);
|
|
setEndpoint("/orders", false);
|
|
// If the customer filter is set, filter the orders by the customer number
|
|
if (props.setCustomerFilter > 0) {
|
|
setFilter("customer_id", props.setCustomerFilter, false);
|
|
}
|
|
// If there are additional query parameters, set them
|
|
if (Object.keys(props.queryParameters).length > 0) {
|
|
setAdditionalQueryParameters(props.queryParameters);
|
|
}
|
|
// If the departmentId is set, in the route, filter the orders by the departmentId
|
|
if (router.currentRoute.value.params.departmentId) {
|
|
setOrder("created_at", "desc");
|
|
setFilter("department_id", router.currentRoute.value.params.departmentId, false);
|
|
} else {
|
|
setOrder("created_at", "desc");
|
|
}
|
|
|
|
// If the onlyFromInvoiceCollection prop is set, filter the orders by the invoice collection id
|
|
if (props.onlyFromInvoiceCollection > 0) {
|
|
setFilter("invoice_collection_id", props.onlyFromInvoiceCollection, false);
|
|
}
|
|
|
|
// If the dates prop is set, filter the orders by the dateFrom and dateTo
|
|
if (props.dates) {
|
|
if (props.dates.dateFrom !== undefined && props.dates.dateFrom !== null) {
|
|
setFilter("created_at-date_from", props.dates.dateFrom, false);
|
|
}
|
|
if (props.dates.dateTo !== undefined && props.dates.dateTo !== null) {
|
|
setFilter("created_at-date_to", props.dates.dateTo, false);
|
|
}
|
|
}
|
|
|
|
// If the limitResults prop is set to false, do not limit the results
|
|
if (props.limitResults === false) {
|
|
setMetaItemsPerPage(1000, false);
|
|
}
|
|
|
|
// If the includeSystemOrders prop is set to true, include system orders
|
|
if (props.includeSystemOrders) {
|
|
setAdditionalQueryParameters({ show_wash_subscription: true });
|
|
}
|
|
|
|
|
|
// Load the list automatically if the autoLoad prop is set
|
|
if (props.autoLoad) {
|
|
if (props.applyDefaultFilters) {
|
|
setFilter('customer_id-has_key-OtherVaskeabonnement', '!OtherVaskeabonnement', false);
|
|
setFilter('customer_id-has_key-OtherSpecialArrangement', '!OtherSpecialArrangement', false);
|
|
setFilter('booked_invoice_id', 'is_null', false);
|
|
}
|
|
loadList();
|
|
}
|
|
|
|
const date_from = ref(null);
|
|
const date_to = ref(null);
|
|
const onDateRangeSelected = (newSelectionStartDate, newSelectionToDate) => {
|
|
const formattedStartDate = formatLocalDateOnly(newSelectionStartDate);
|
|
const formattedEndDate = formatLocalDateOnly(newSelectionToDate);
|
|
date_from.value = formattedStartDate;
|
|
date_to.value = formattedEndDate;
|
|
setFilter("created_at-date_from", formattedStartDate, true);
|
|
setFilter("created_at-date_to", formattedEndDate, true);
|
|
loadList();
|
|
};
|
|
|
|
|
|
const doesEndpointMatch = (matcher) => {
|
|
// Check if the endpoint matches the current endpoint
|
|
return endpoint.value === matcher;
|
|
}
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<template v-if="doesEndpointMatch('/orders')">
|
|
<TableLabeledPagination
|
|
:label="SessionUser.objects.orders.meta.labels.multiple"
|
|
:show-filters="areFiltersVisible"
|
|
:hide-search="props.hideSearch"
|
|
:hide-pagination="props.hidePagination"
|
|
>
|
|
<template #afterSearch v-if="props.hideFilter">
|
|
<div class="invoice-orders-filter-toggle">
|
|
<button
|
|
type="button"
|
|
class="button is-text is-small"
|
|
data-testid="invoice-orders-toggle-filters"
|
|
:aria-expanded="areFiltersVisible ? 'true' : 'false'"
|
|
@click="areFiltersVisible = !areFiltersVisible"
|
|
>
|
|
<span class="icon is-small">
|
|
<i class="fas" :class="filterToggleIcon" aria-hidden="true"></i>
|
|
</span>
|
|
<span>{{ filterToggleLabel }}</span>
|
|
</button>
|
|
</div>
|
|
</template>
|
|
<template #paginationDisplayFiltersElement>
|
|
<PaginationDisplayFilters
|
|
:departmentColumn="'department_id'"
|
|
/>
|
|
</template>
|
|
<template #leftPaginationColumns>
|
|
<!-- Sort by created_at -->
|
|
<div class="column is-narrow">
|
|
<label class="label is-small">{{ t('pagination.order_direction') }}</label>
|
|
<div class="control">
|
|
<div class="select">
|
|
<select @change="setOrder('created_at', $event.target.value); loadList();">
|
|
<option value="asc">{{ t('pagination.ascending') }}</option>
|
|
<option value="desc" selected>{{ t('pagination.descending') }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Invoice status -->
|
|
<div class="column is-narrow">
|
|
<label class="label is-small">{{ t('pagination.booking_status') }}</label>
|
|
<div class="control">
|
|
<div class="select">
|
|
<select @change="setFilter('booked_invoice_id', $event.target.value, true);">
|
|
<option value="*" :selected="!props.applyDefaultFilters">{{ t('common.all') }}</option>
|
|
<option value="not null">{{ t('pagination.booked') }}</option>
|
|
<option value="is_null" :selected="props.applyDefaultFilters">{{ t('pagination.not_booked') }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Error status -->
|
|
<div class="column is-narrow">
|
|
<label class="label is-small">{{ t('pagination.error_status') }}</label>
|
|
<div class="control">
|
|
<div class="select">
|
|
<select @change="setFilter('error_message', $event.target.value, true);">
|
|
<option value="*" selected>{{ t('common.all') }}</option>
|
|
<option value="not null">{{ t('common.yes') }}</option>
|
|
<option value="is_null">{{ t('common.no') }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Payment processor -->
|
|
<div class="column is-narrow">
|
|
<label class="label is-small">{{ t('pagination.payment_processor') }}</label>
|
|
<div class="control">
|
|
<div class="select">
|
|
<select @change="setFilter('processor', $event.target.value, true);">
|
|
<option value="*" selected>{{ t('common.all') }}</option>
|
|
<option value="1">E-conomic</option>
|
|
<option value="2">Stripe</option>
|
|
<option value="3">{{ t('pagination.other_no_tracking') }}</option>
|
|
<option :value="'is_null'">{{ t('pagination.not_specified') }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Marked as complete ("Gennemført") -->
|
|
<div class="column is-narrow">
|
|
<label class="label is-small">{{ t('pagination.completed') }}</label>
|
|
<div class="control">
|
|
<div class="select">
|
|
<select @change="setFilter('completed_at', $event.target.value, true);">
|
|
<option value="*" selected>{{ t('common.all') }}</option>
|
|
<option value="not null">{{ t('pagination.completed') }}</option>
|
|
<option :value="'is_null'">{{ t('pagination.not_completed') }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Multiple selection, customer has attribute -->
|
|
<div class="column is-narrow">
|
|
<label class="label is-small">{{ t('pagination.invoice_selection') }}</label>
|
|
<div class="control">
|
|
<div class="select">
|
|
<select @change="setFilter('customer_id-has_attribute', $event.target.value, true);">
|
|
<option value="*" selected>{{ t('common.all') }}</option>
|
|
<option value="invoiceAllOrdersIndividually">{{ t('pagination.invoice_per_order') }}</option>
|
|
<option value="!invoiceAllOrdersIndividually">{{ t('pagination.invoice_per_month') }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Multiple selection, customer has special arrangement -->
|
|
<div class="column is-narrow">
|
|
<label class="label is-small">{{ t('pagination.special_agreement') }}</label>
|
|
<div class="control">
|
|
<div class="select">
|
|
<select @change="setFilter('customer_id-has_key-OtherSpecialArrangement', $event.target.value, true);">
|
|
<option value="*" :selected="!props.applyDefaultFilters">{{ t('common.all') }}</option>
|
|
<option value="OtherSpecialArrangement">{{ t('pagination.has_special_agreement') }}</option>
|
|
<option value="!OtherSpecialArrangement" :selected="props.applyDefaultFilters">{{ t('pagination.no_special_agreement') }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Multiple selection, customer has washing subscription -->
|
|
<div class="column is-narrow">
|
|
<label class="label is-small">{{ t('pagination.wash_subscription') }}</label>
|
|
<div class="control">
|
|
<div class="select">
|
|
<select @change="setFilter('customer_id-has_key-OtherVaskeabonnement', $event.target.value, true);">
|
|
<option value="*" :selected="!props.applyDefaultFilters">{{ t('common.all') }}</option>
|
|
<option value="OtherVaskeabonnement">{{ t('pagination.has_wash_subscription') }}</option>
|
|
<option value="!OtherVaskeabonnement" :selected="props.applyDefaultFilters">{{ t('pagination.no_wash_subscription') }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Multiple selection, customer has washing subscription -FIX
|
|
<div class="column is-narrow">
|
|
<label class="label">Only tank cleaning</label>s
|
|
<div class="control">
|
|
<div class="select">
|
|
<select @change="setFilter('customer_id-has_attribute', $event.target.value, true);">
|
|
<option value="*" :selected="!props.applyDefaultFilters">All</option>
|
|
<option value="onlyTankCleaning">Only tank cleaning</option>
|
|
<option value="!onlyTankCleaning" :selected="props.applyDefaultFilters">Ikke tankcleaning</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div> -->
|
|
<!-- Date from -->
|
|
<div class="column is-narrow">
|
|
<label class="label is-small">{{ t('pagination.from_date') }}</label>
|
|
<div class="control">
|
|
<input type="date" class="input" @change="setFilter('created_at-date_from', $event.target.value, true)" v-model="date_from" />
|
|
</div>
|
|
</div>
|
|
<!-- Date to -->
|
|
<div class="column is-narrow">
|
|
<label class="label is-small">{{ t('pagination.to_date') }}</label>
|
|
<div class="control">
|
|
<input type="date" class="input" @change="setFilter('created_at-date_to', $event.target.value, true)" v-model="date_to" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #rightPaginationColumns>
|
|
<div class="column is-12">
|
|
<!-- Shortcuts for date filters -->
|
|
<DatePeriodSelector :on-selection-change="onDateRangeSelected"
|
|
:visibility="{ showDailySelector: false, showWeeklySelector: false, showMultipleMonthWarning: false, showUpdateButton: false, showMonthSelector: false, showStartDate: false, showEndDate: false, showSelectionValidity: false, showYearSelector: false, showToLabel: false }"
|
|
v-bind:selection="{ startDate: parseLocalDateOnly(date_from || todayLocalDateOnly()), endDate: parseLocalDateOnly(date_to || todayLocalDateOnly()) }"/>
|
|
</div>
|
|
</template>
|
|
<template #default>
|
|
<OrdersTable :orders="list"
|
|
:invoiceView="props.invoiceView"
|
|
:allowSelectMultiple="true"
|
|
:show-draft-assignment-actions="props.showDraftAssignmentActions"
|
|
v-bind:groupInvoiceCollection="props.groupInvoiceCollection"
|
|
v-bind:showOnlyWithIds="props.showOnlyWithIds"
|
|
v-bind:autoExpandAll="props.autoExpandAll"
|
|
v-bind:excludedOrderIds="props.excludedOrderIds"
|
|
:invoice-period-flags="props.invoicePeriodFlags"
|
|
@flagStatusChanged="(flag) => emit('flagStatusChanged', flag)"
|
|
@flagCreated="(flag) => emit('flagCreated', flag)"
|
|
/>
|
|
</template>
|
|
</TableLabeledPagination>
|
|
</template>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.invoice-orders-filter-toggle {
|
|
margin-top: -0.35rem;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
</style>
|