Add invoice collection bulk actions and table header preferences functionality
This commit is contained in:
+19
-1
@@ -49,8 +49,26 @@ textarea.has-sharp-edges {
|
||||
--bulma-card-shadow: none !important;
|
||||
|
||||
--bulma-skeleton-background: hsla(197 100% 35% / 0.70) !important;
|
||||
--pleno-compact-table-header-font-size: 0.72rem;
|
||||
--pleno-compact-table-header-line-height: 1;
|
||||
}
|
||||
|
||||
body:not(.pleno-large-table-headers) .table thead th {
|
||||
font-size: var(--pleno-compact-table-header-font-size);
|
||||
line-height: var(--pleno-compact-table-header-line-height);
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.pleno-table-header-content {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.15rem;
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
/*:root {*/
|
||||
/* --bulma-primary-h: 207deg !important;*/
|
||||
/* --bulma-primary-s: 60% !important;*/
|
||||
@@ -125,4 +143,4 @@ textarea.has-sharp-edges {
|
||||
.mt-30-tablet {
|
||||
margin-top: 30px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
|
||||
import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue";
|
||||
import ActionSettingsWheelItemLabel from "@/components/displays/buttons/ActionSettingsWheelItemLabel.vue";
|
||||
import { INVOICE_COLLECTION_BULK_ACTIONS } from "@/components/displays/department/pos/orders/invoiceCollectionBulkActions.js";
|
||||
|
||||
const props = defineProps({
|
||||
selectedInvoiceCollectionIds: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
totalInvoiceCollectionCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
allSelected: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
allExpanded: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
invoiceQueueBusy: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["invoiceSelected", "bulkAction", "toggleSelectAll", "toggleExpandAll"]);
|
||||
const { t } = useI18n();
|
||||
|
||||
const selectedCount = computed(() => props.selectedInvoiceCollectionIds.length);
|
||||
const hasSelectableCollections = computed(() => props.totalInvoiceCollectionCount > 0);
|
||||
const hasSelectedCollections = computed(() => selectedCount.value > 0);
|
||||
const hasMultipleSelectedCollections = computed(() => selectedCount.value > 1);
|
||||
const triggerLabel = computed(() =>
|
||||
t("invoicing_period.invoice_collection_actions.menu.label", { count: selectedCount.value })
|
||||
);
|
||||
const selectAllLabel = computed(() =>
|
||||
props.allSelected
|
||||
? t("invoicing_period.invoice_collection_actions.menu.unselect_all")
|
||||
: t("invoicing_period.invoice_collection_actions.menu.select_all")
|
||||
);
|
||||
const expandAllLabel = computed(() =>
|
||||
props.allExpanded
|
||||
? t("invoicing_period.invoice_collection_actions.menu.collapse_all")
|
||||
: t("invoicing_period.invoice_collection_actions.menu.expand_all")
|
||||
);
|
||||
|
||||
const emitBulkAction = (action) => emit("bulkAction", action);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="hasSelectableCollections"
|
||||
class="invoice-collection-selection-action-wheel"
|
||||
data-testid="invoice-collection-selection-action-wheel"
|
||||
>
|
||||
<ActionSettingsWheelButton icon="fas fa-sliders-h" :label="triggerLabel">
|
||||
<template #actions>
|
||||
<ActionSettingsWheelItemLabel
|
||||
:label="t('invoicing_period.invoice_collection_actions.menu.selection_section')"
|
||||
data-testid="invoice-collection-selection-action-wheel-selection-section"
|
||||
/>
|
||||
<ActionSettingsWheelItem
|
||||
icon="fas fa-check-double"
|
||||
:label="selectAllLabel"
|
||||
:click-action="() => emit('toggleSelectAll')"
|
||||
test-id="invoice-collection-selection-action-wheel-select-all"
|
||||
/>
|
||||
<ActionSettingsWheelItem
|
||||
:icon="allExpanded ? 'fas fa-compress-alt' : 'fas fa-expand-alt'"
|
||||
:label="expandAllLabel"
|
||||
:click-action="() => emit('toggleExpandAll')"
|
||||
test-id="invoice-collection-selection-action-wheel-expand-all"
|
||||
/>
|
||||
|
||||
<template v-if="hasSelectedCollections">
|
||||
<ActionSettingsWheelItemLabel
|
||||
:label="t('invoicing_period.invoice_collection_actions.menu.modification_section')"
|
||||
data-testid="invoice-collection-selection-action-wheel-modification-section"
|
||||
/>
|
||||
<ActionSettingsWheelItem
|
||||
icon="fas fa-file-invoice-dollar"
|
||||
:label="t('invoicing_period.invoice_collection_actions.actions.queue_economic')"
|
||||
:click-action="() => emit('invoiceSelected')"
|
||||
:disabled="invoiceQueueBusy"
|
||||
test-id="invoice-collection-selection-action-wheel-invoice-selected"
|
||||
/>
|
||||
<ActionSettingsWheelItem
|
||||
icon="fas fa-broom"
|
||||
:label="t('invoicing_period.invoice_collection_actions.actions.remove_customer_rule_violations')"
|
||||
:click-action="() => emitBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.CLEAN_CUSTOMER_RULES)"
|
||||
test-id="invoice-collection-selection-action-wheel-clean-rules"
|
||||
/>
|
||||
<ActionSettingsWheelItem
|
||||
v-if="hasMultipleSelectedCollections"
|
||||
icon="fas fa-compress-arrows-alt"
|
||||
:label="t('invoicing_period.invoice_collection_actions.actions.merge_collections')"
|
||||
:click-action="() => emitBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.MERGE)"
|
||||
test-id="invoice-collection-selection-action-wheel-merge"
|
||||
/>
|
||||
<ActionSettingsWheelItem
|
||||
icon="fas fa-calendar-alt"
|
||||
:label="t('invoicing_period.invoice_collection_actions.actions.split_by_month')"
|
||||
:click-action="() => emitBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.SPLIT_BY_MONTH)"
|
||||
test-id="invoice-collection-selection-action-wheel-split-by-month"
|
||||
/>
|
||||
<ActionSettingsWheelItem
|
||||
icon="fas fa-undo"
|
||||
:label="t('invoicing_period.invoice_collection_actions.actions.reset_hidden_item_prices')"
|
||||
:click-action="() => emitBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.RESET_HIDDEN_PRICES)"
|
||||
test-id="invoice-collection-selection-action-wheel-reset-hidden-prices"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</ActionSettingsWheelButton>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
export const INVOICE_COLLECTION_BULK_ACTIONS = Object.freeze({
|
||||
CLEAN_CUSTOMER_RULES: "remove_customer_rule_violations",
|
||||
MERGE: "merge_collections",
|
||||
SPLIT_BY_MONTH: "split_by_month",
|
||||
RESET_HIDDEN_PRICES: "reset_hidden_item_prices",
|
||||
QUEUE_ECONOMIC: "queue_economic",
|
||||
});
|
||||
@@ -1,12 +1,17 @@
|
||||
<script setup>
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { BCheckbox } from "buefy";
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
|
||||
import InvoiceCollectionSelectionActionWheel from "@/components/displays/department/pos/orders/InvoiceCollectionSelectionActionWheel.vue";
|
||||
import OrderAttachmentsActionButton from "@/components/displays/department/pos/orders/OrderAttachmentsActionButton.vue";
|
||||
import AssignDraftOrderCustomerModal from "@/components/displays/modals/AssignDraftOrderCustomerModal.vue";
|
||||
import InvoicingPeriodFlagList from "@/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodFlagList.vue";
|
||||
import { INVOICE_COLLECTION_BULK_ACTIONS } from "@/components/displays/department/pos/orders/invoiceCollectionBulkActions.js";
|
||||
import SuperuserInvoiceRowActions from "@/components/displays/superuser/tables/SuperuserInvoiceRowActions.vue";
|
||||
import { useLargeTableHeaders } from "@/services/tableHeaderPreferences.js";
|
||||
|
||||
const props = defineProps({
|
||||
orders: {
|
||||
@@ -58,7 +63,6 @@ import { showPopper, removePopperIfOpen, popperBox } from "@/components/displays
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import { usePaginatedListInstance } from "@/components/pagination/paginatedList.vue";
|
||||
const { orderBy, orderDirection, setOrder, loadList } = usePaginatedListInstance();
|
||||
import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue";
|
||||
import OrderContentTable from "@/components/displays/superuser/tables/OrderContentTable.vue";
|
||||
import InvoiceMultipleCollectionsModal from "@/components/displays/modals/InvoiceMultipleCollectionsModal.vue";
|
||||
import Swal from "sweetalert2";
|
||||
@@ -266,14 +270,6 @@ const normalizeInvoiceCollectionId = (invoiceCollectionId) => {
|
||||
: null;
|
||||
};
|
||||
|
||||
const INVOICE_COLLECTION_BULK_ACTIONS = Object.freeze({
|
||||
CLEAN_CUSTOMER_RULES: "remove_customer_rule_violations",
|
||||
MERGE: "merge_collections",
|
||||
SPLIT_BY_MONTH: "split_by_month",
|
||||
RESET_HIDDEN_PRICES: "reset_hidden_item_prices",
|
||||
QUEUE_ECONOMIC: "queue_economic",
|
||||
});
|
||||
|
||||
const getApiPayload = (response) => response?.data?.data ?? response?.data ?? response ?? {};
|
||||
|
||||
const escapeHtml = (value) => String(value ?? "")
|
||||
@@ -476,6 +472,18 @@ const toggleInvoiceCollectionSelection = (invoiceCollectionId) => {
|
||||
}
|
||||
};
|
||||
|
||||
const setInvoiceCollectionSelection = (invoiceCollectionId, checked) => {
|
||||
const normalizedInvoiceCollectionId = normalizeInvoiceCollectionId(invoiceCollectionId);
|
||||
if (normalizedInvoiceCollectionId === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isSelected = selectedInvoiceCollections.value.includes(normalizedInvoiceCollectionId);
|
||||
if ((checked && !isSelected) || (!checked && isSelected)) {
|
||||
toggleInvoiceCollectionSelection(normalizedInvoiceCollectionId);
|
||||
}
|
||||
};
|
||||
|
||||
const isInvoiceCollectionSelected = (invoiceCollectionId) => {
|
||||
const normalizedInvoiceCollectionId = normalizeInvoiceCollectionId(invoiceCollectionId);
|
||||
if (normalizedInvoiceCollectionId === null) {
|
||||
@@ -540,6 +548,7 @@ const isInvoiceQueueBusy = computed(() => {
|
||||
const getSelectedInvoiceCollectionIds = () => selectedInvoiceCollections.value
|
||||
.map((invoiceCollectionId) => normalizeInvoiceCollectionId(invoiceCollectionId))
|
||||
.filter((invoiceCollectionId) => invoiceCollectionId !== null);
|
||||
const selectableInvoiceCollectionCount = computed(() => getUniqueInvoiceCollections().length);
|
||||
|
||||
const getBulkActionLabel = (action) => t(`invoicing_period.invoice_collection_actions.actions.${action}`);
|
||||
|
||||
@@ -1106,70 +1115,17 @@ const formatCashierName = (order) => {
|
||||
<thead>
|
||||
<tr v-if="props.invoiceView && props.allowSelectMultiple">
|
||||
<td colspan="100%">
|
||||
<div class="buttons">
|
||||
<!-- Invoice selected collections -->
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="invoiceSelectedCollections()"
|
||||
:disabled="selectedInvoiceCollections.length === 0 || isInvoiceQueueBusy"
|
||||
>
|
||||
{{ $t("common.invoice") }} {{ $t("global.selected_multiple") }} ({{
|
||||
selectedInvoiceCollections.length
|
||||
}})
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="runInvoiceCollectionBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.CLEAN_CUSTOMER_RULES)"
|
||||
:disabled="selectedInvoiceCollections.length === 0"
|
||||
>
|
||||
{{ t("invoicing_period.invoice_collection_actions.actions.remove_customer_rule_violations") }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="runInvoiceCollectionBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.MERGE)"
|
||||
:disabled="selectedInvoiceCollections.length < 2"
|
||||
>
|
||||
{{ t("invoicing_period.invoice_collection_actions.actions.merge_collections") }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="runInvoiceCollectionBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.SPLIT_BY_MONTH)"
|
||||
:disabled="selectedInvoiceCollections.length === 0"
|
||||
>
|
||||
{{ t("invoicing_period.invoice_collection_actions.actions.split_by_month") }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="runInvoiceCollectionBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.RESET_HIDDEN_PRICES)"
|
||||
:disabled="selectedInvoiceCollections.length === 0"
|
||||
>
|
||||
{{ t("invoicing_period.invoice_collection_actions.actions.reset_hidden_item_prices") }}
|
||||
</button>
|
||||
<!-- Select / Unselect all invoice collections -->
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="selectAllInvoiceCollections()"
|
||||
:class="{
|
||||
'is-light': !isInvoiceCollectionSelectedAll(),
|
||||
'is-dark': isInvoiceCollectionSelectedAll(),
|
||||
}"
|
||||
>
|
||||
{{ isInvoiceCollectionSelectedAll() ? $t("global.unselect") : $t("common.select") }}
|
||||
{{ $t("common.all").toLowerCase() }}
|
||||
</button>
|
||||
<!-- Expand / Collapse all invoice collections -->
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="toggleAutoExpandAll()"
|
||||
:class="{
|
||||
'is-light': !isAutoExpandAll(),
|
||||
'is-dark': isAutoExpandAll(),
|
||||
}"
|
||||
>
|
||||
{{ isAutoExpandAll() ? $t("global.collapse") : $t("global.expand") }}
|
||||
{{ $t("common.all").toLowerCase() }}
|
||||
</button>
|
||||
</div>
|
||||
<InvoiceCollectionSelectionActionWheel
|
||||
:selected-invoice-collection-ids="selectedInvoiceCollections"
|
||||
:total-invoice-collection-count="selectableInvoiceCollectionCount"
|
||||
:all-selected="isInvoiceCollectionSelectedAll()"
|
||||
:all-expanded="isAutoExpandAll()"
|
||||
:invoice-queue-busy="isInvoiceQueueBusy"
|
||||
@invoice-selected="invoiceSelectedCollections"
|
||||
@bulk-action="runInvoiceCollectionBulkAction"
|
||||
@toggle-select-all="selectAllInvoiceCollections"
|
||||
@toggle-expand-all="toggleAutoExpandAll"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -1181,12 +1137,15 @@ const formatCashierName = (order) => {
|
||||
v-if="tableHeaders.sortable"
|
||||
:class="{
|
||||
'is-clickable': tableHeaders.sortable,
|
||||
'has-text-centered': tableHeaders.name === 'id',
|
||||
'is-narrow': ['id', 'reg_1', 'actions'].includes(tableHeaders.name),
|
||||
'pos-orders-table-header--compact': !useLargeTableHeaders,
|
||||
'pos-orders-table-id-header': tableHeaders.name === 'id',
|
||||
'pos-orders-table-header--vehicles': tableHeaders.name === 'reg_1',
|
||||
}"
|
||||
@click="onTableHeaderClick(tableHeaders.name)"
|
||||
>
|
||||
<span class="is-flex-wrap-nowrap">
|
||||
<span class="has-text-weight-bold">{{ tableHeaders.title }}</span>
|
||||
<span class="is-flex-wrap-nowrap pleno-table-header-content">
|
||||
<span class="has-text-weight-bold pos-orders-table-header__label">{{ tableHeaders.title }}</span>
|
||||
<span
|
||||
v-if="isColumnCurrentlyBeingSortedBy(tableHeaders.name)"
|
||||
:class="{
|
||||
@@ -1200,11 +1159,14 @@ const formatCashierName = (order) => {
|
||||
<th
|
||||
v-else
|
||||
:class="{
|
||||
'has-text-centered': tableHeaders.name === 'id',
|
||||
'is-narrow': ['id', 'reg_1', 'actions'].includes(tableHeaders.name),
|
||||
'pos-orders-table-header--compact': !useLargeTableHeaders,
|
||||
'pos-orders-table-id-header': tableHeaders.name === 'id',
|
||||
'pos-orders-table-header--vehicles': tableHeaders.name === 'reg_1',
|
||||
}"
|
||||
>
|
||||
<span class="is-flex-wrap-nowrap">
|
||||
<span class="has-text-weight-bold">{{ tableHeaders.title }}</span>
|
||||
<span class="is-flex-wrap-nowrap pleno-table-header-content">
|
||||
<span class="has-text-weight-bold pos-orders-table-header__label">{{ tableHeaders.title }}</span>
|
||||
</span>
|
||||
</th>
|
||||
</template>
|
||||
@@ -1257,9 +1219,11 @@ const formatCashierName = (order) => {
|
||||
</td>
|
||||
<!-- Actions for the invoice collection -->
|
||||
<td class="is-narrow">
|
||||
<ActionSettingsWheelButton v-bind:invoice_collection_id="order.invoice_collection_id">
|
||||
<template #actions> </template>
|
||||
</ActionSettingsWheelButton>
|
||||
<SuperuserInvoiceRowActions :include-buffer="true">
|
||||
<ActionSettingsWheelButton v-bind:invoice_collection_id="order.invoice_collection_id">
|
||||
<template #actions> </template>
|
||||
</ActionSettingsWheelButton>
|
||||
</SuperuserInvoiceRowActions>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Warning, if there's orders in the collection that are filtered out -->
|
||||
@@ -1295,23 +1259,21 @@ const formatCashierName = (order) => {
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-if="isObjectVisible(order)" :class="{ 'has-background-info-light': isSystemOrder(order) }">
|
||||
<tr
|
||||
v-if="isObjectVisible(order)"
|
||||
class="pos-orders-table-row"
|
||||
:class="{ 'has-background-info-light': isSystemOrder(order) }"
|
||||
>
|
||||
<template v-if="props.invoiceView && props.allowSelectMultiple">
|
||||
<td class="is-narrow">
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="toggleInvoiceCollectionSelection(order.invoice_collection_id)"
|
||||
:class="{
|
||||
'is-light': !isInvoiceCollectionSelected(parseInt(order.invoice_collection_id)),
|
||||
'is-dark': isInvoiceCollectionSelected(parseInt(order.invoice_collection_id)),
|
||||
}"
|
||||
>
|
||||
{{
|
||||
isInvoiceCollectionSelected(parseInt(order.invoice_collection_id))
|
||||
? SessionUser.objects.global.language.unselect
|
||||
: SessionUser.objects.global.language.select
|
||||
}}
|
||||
</button>
|
||||
<td class="is-narrow pos-order-invoice-collection-selector-cell">
|
||||
<BCheckbox
|
||||
class="pos-order-invoice-collection-selector"
|
||||
:model-value="isInvoiceCollectionSelected(order.invoice_collection_id)"
|
||||
:disabled="normalizeInvoiceCollectionId(order.invoice_collection_id) === null"
|
||||
:aria-label="`${SessionUser.objects.global.language.select} ${SessionUser.objects.global.language.invoice.toLowerCase()} #${order.invoice_collection_id || order.id}`"
|
||||
:data-testid="`pos-order-invoice-collection-selector-${order.id}`"
|
||||
@update:model-value="(checked) => setInvoiceCollectionSelection(order.invoice_collection_id, checked)"
|
||||
/>
|
||||
</td>
|
||||
</template>
|
||||
<template v-if="props.invoiceView">
|
||||
@@ -1332,52 +1294,53 @@ const formatCashierName = (order) => {
|
||||
</button>
|
||||
</td>
|
||||
</template>
|
||||
<td>
|
||||
<ColorIndicator
|
||||
v-bind:color_class="getOrderIndicatorColorClass(order)"
|
||||
v-bind:icon_class="getOrderIndicatorIconClass(order)"
|
||||
v-bind:is_narrow="true"
|
||||
v-bind:label="{
|
||||
text: order.id,
|
||||
classes: [],
|
||||
max_length: 7,
|
||||
}"
|
||||
v-bind:visibility="{
|
||||
icon: true,
|
||||
dropdown: true,
|
||||
}"
|
||||
v-bind:dropdown_content="{
|
||||
title: null,
|
||||
buttons_title: null,
|
||||
content: [
|
||||
// Transaction
|
||||
{
|
||||
text: SessionUser.functions.ucFirst(SessionUser.objects.orders.meta.labels.single),
|
||||
action: () => {
|
||||
redirectDepartmentOrderPage(order.id, order.department_id);
|
||||
<td class="is-narrow pos-orders-table-id-cell">
|
||||
<div class="pos-orders-table-id-cell-content">
|
||||
<ColorIndicator
|
||||
v-bind:color_class="getOrderIndicatorColorClass(order)"
|
||||
v-bind:icon_class="getOrderIndicatorIconClass(order)"
|
||||
v-bind:is_narrow="true"
|
||||
v-bind:label="{
|
||||
text: order.id,
|
||||
classes: [],
|
||||
max_length: 7,
|
||||
}"
|
||||
v-bind:visibility="{
|
||||
icon: true,
|
||||
dropdown: true,
|
||||
}"
|
||||
v-bind:dropdown_content="{
|
||||
title: null,
|
||||
buttons_title: null,
|
||||
content: [
|
||||
// Transaction
|
||||
{
|
||||
text: SessionUser.functions.ucFirst(SessionUser.objects.orders.meta.labels.single),
|
||||
action: () => {
|
||||
redirectDepartmentOrderPage(order.id, order.department_id);
|
||||
},
|
||||
...(SessionUser.canAccessSuperUser() ? {} : { disabled: true }),
|
||||
button_classes: ['no-underline-text', 'is-text'],
|
||||
button: true,
|
||||
button_text: order.id,
|
||||
v_centered: true,
|
||||
},
|
||||
...(SessionUser.canAccessSuperUser() ? {} : { disabled: true }),
|
||||
button_classes: ['no-underline-text', 'is-text'],
|
||||
button: true,
|
||||
button_text: order.id,
|
||||
v_centered: true,
|
||||
},
|
||||
// Invoice collection
|
||||
{
|
||||
text:
|
||||
SessionUser.objects.global.language.invoice +
|
||||
' ' +
|
||||
SessionUser.objects.global.language.collection.toLowerCase(),
|
||||
action: () => {
|
||||
redirectSuperUserInvoiceCollectionPage(order.invoice_collection_id);
|
||||
// Invoice collection
|
||||
{
|
||||
text:
|
||||
SessionUser.objects.global.language.invoice +
|
||||
' ' +
|
||||
SessionUser.objects.global.language.collection.toLowerCase(),
|
||||
action: () => {
|
||||
redirectSuperUserInvoiceCollectionPage(order.invoice_collection_id);
|
||||
},
|
||||
...(SessionUser.canAccessSuperUser() ? {} : { disabled: true }),
|
||||
button_classes: ['no-underline-text', 'is-text'],
|
||||
button: true,
|
||||
button_text: order.invoice_collection_id,
|
||||
v_centered: true,
|
||||
},
|
||||
...(SessionUser.canAccessSuperUser() ? {} : { disabled: true }),
|
||||
button_classes: ['no-underline-text', 'is-text'],
|
||||
button: true,
|
||||
button_text: order.invoice_collection_id,
|
||||
v_centered: true,
|
||||
},
|
||||
/**
|
||||
/**
|
||||
* // Completed status
|
||||
* {
|
||||
* text: SessionUser.objects.orders.columns.completed_at.label,
|
||||
@@ -1392,23 +1355,24 @@ const formatCashierName = (order) => {
|
||||
* },
|
||||
*/
|
||||
|
||||
// Handheld status
|
||||
...(isPendingHandheld(order)
|
||||
? [
|
||||
{
|
||||
text: SessionUser.objects.global.language.handheld_pending_order,
|
||||
action: () => {},
|
||||
button_classes: ['no-underline-text', 'is-text', 'has-text-warning'],
|
||||
button: true,
|
||||
button_text: SessionUser.objects.global.language.confirmation_needed,
|
||||
v_centered: true,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
buttons: [],
|
||||
}"
|
||||
/>
|
||||
// Handheld status
|
||||
...(isPendingHandheld(order)
|
||||
? [
|
||||
{
|
||||
text: SessionUser.objects.global.language.handheld_pending_order,
|
||||
action: () => {},
|
||||
button_classes: ['no-underline-text', 'is-text', 'has-text-warning'],
|
||||
button: true,
|
||||
button_text: SessionUser.objects.global.language.confirmation_needed,
|
||||
v_centered: true,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
buttons: [],
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td v-if="!props.isCustomerView">
|
||||
<ColorIndicator
|
||||
@@ -1462,8 +1426,8 @@ const formatCashierName = (order) => {
|
||||
</td>
|
||||
<td>{{ getDepartmentName(order.department_id) }}</td>
|
||||
<!-- Vehicles -->
|
||||
<td>
|
||||
<p>
|
||||
<td class="pos-order-vehicle-cell">
|
||||
<p class="pos-order-vehicle-registration-line">
|
||||
<EditableTableColumn
|
||||
:componentWrapper="'div'"
|
||||
:object="order"
|
||||
@@ -1474,7 +1438,7 @@ const formatCashierName = (order) => {
|
||||
:permissionCheckFunction="(orderobj) => canEditTransaction(order)"
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<p class="pos-order-vehicle-registration-line">
|
||||
<EditableTableColumn
|
||||
:componentWrapper="'div'"
|
||||
:object="order"
|
||||
@@ -1485,7 +1449,7 @@ const formatCashierName = (order) => {
|
||||
:permissionCheckFunction="(bookingobj) => canEditTransaction(order)"
|
||||
/>
|
||||
</p>
|
||||
<p v-show="order.reg_3">
|
||||
<p v-show="order.reg_3" class="pos-order-vehicle-registration-line">
|
||||
<EditableTableColumn
|
||||
:componentWrapper="'div'"
|
||||
:object="order"
|
||||
@@ -1528,8 +1492,8 @@ const formatCashierName = (order) => {
|
||||
<td>{{ order.created_at }}</td>
|
||||
<td>{{ SessionUser.functions.currency.toLocal(order.total_net_amount) }}</td>
|
||||
<!--<td>{{ order.completed_at }}</td>-->
|
||||
<td class="is-narrow">
|
||||
<div class="buttons pos-order-list-actions">
|
||||
<td class="is-narrow pos-orders-table-actions-cell">
|
||||
<SuperuserInvoiceRowActions class="pos-order-list-actions">
|
||||
<button
|
||||
v-if="shouldShowDraftAssignmentActions"
|
||||
class="button is-small is-link is-light pos-order-list-assign-customer-button"
|
||||
@@ -1581,7 +1545,7 @@ const formatCashierName = (order) => {
|
||||
-->
|
||||
</template>
|
||||
</ActionSettingsWheelButton>
|
||||
</div>
|
||||
</SuperuserInvoiceRowActions>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
@@ -1627,74 +1591,17 @@ const formatCashierName = (order) => {
|
||||
<div class="columns is-multiline is-mobile">
|
||||
<!-- Bulk actions when invoice view allows selection -->
|
||||
<div class="column is-full" v-if="props.invoiceView && props.allowSelectMultiple">
|
||||
<div class="buttons">
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="invoiceSelectedCollections()"
|
||||
:disabled="selectedInvoiceCollections.length === 0 || isInvoiceQueueBusy"
|
||||
>
|
||||
{{ SessionUser.objects.global.language.invoice }}
|
||||
{{ SessionUser.objects.global.language.selected_multiple }} ({{ selectedInvoiceCollections.length }})
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="runInvoiceCollectionBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.CLEAN_CUSTOMER_RULES)"
|
||||
:disabled="selectedInvoiceCollections.length === 0"
|
||||
>
|
||||
{{ t("invoicing_period.invoice_collection_actions.actions.remove_customer_rule_violations") }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="runInvoiceCollectionBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.MERGE)"
|
||||
:disabled="selectedInvoiceCollections.length < 2"
|
||||
>
|
||||
{{ t("invoicing_period.invoice_collection_actions.actions.merge_collections") }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="runInvoiceCollectionBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.SPLIT_BY_MONTH)"
|
||||
:disabled="selectedInvoiceCollections.length === 0"
|
||||
>
|
||||
{{ t("invoicing_period.invoice_collection_actions.actions.split_by_month") }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="runInvoiceCollectionBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.RESET_HIDDEN_PRICES)"
|
||||
:disabled="selectedInvoiceCollections.length === 0"
|
||||
>
|
||||
{{ t("invoicing_period.invoice_collection_actions.actions.reset_hidden_item_prices") }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="selectAllInvoiceCollections()"
|
||||
:class="{
|
||||
'is-light': !isInvoiceCollectionSelectedAll(),
|
||||
'is-dark': isInvoiceCollectionSelectedAll(),
|
||||
}"
|
||||
>
|
||||
{{
|
||||
isInvoiceCollectionSelectedAll()
|
||||
? SessionUser.objects.global.language.unselect
|
||||
: SessionUser.objects.global.language.select
|
||||
}}
|
||||
{{ SessionUser.objects.global.language.all.toLowerCase() }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="toggleAutoExpandAll()"
|
||||
:class="{
|
||||
'is-light': !isAutoExpandAll(),
|
||||
'is-dark': isAutoExpandAll(),
|
||||
}"
|
||||
>
|
||||
{{
|
||||
isAutoExpandAll()
|
||||
? SessionUser.objects.global.language.collapse
|
||||
: SessionUser.objects.global.language.expand
|
||||
}}
|
||||
{{ SessionUser.objects.global.language.all.toLowerCase() }}
|
||||
</button>
|
||||
</div>
|
||||
<InvoiceCollectionSelectionActionWheel
|
||||
:selected-invoice-collection-ids="selectedInvoiceCollections"
|
||||
:total-invoice-collection-count="selectableInvoiceCollectionCount"
|
||||
:all-selected="isInvoiceCollectionSelectedAll()"
|
||||
:all-expanded="isAutoExpandAll()"
|
||||
:invoice-queue-busy="isInvoiceQueueBusy"
|
||||
@invoice-selected="invoiceSelectedCollections"
|
||||
@bulk-action="runInvoiceCollectionBulkAction"
|
||||
@toggle-select-all="selectAllInvoiceCollections"
|
||||
@toggle-expand-all="toggleAutoExpandAll"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Orders as cards -->
|
||||
@@ -1738,19 +1645,16 @@ const formatCashierName = (order) => {
|
||||
</span>
|
||||
</div>
|
||||
<div class="column is-12 has-text-right mb-2">
|
||||
<span class="buttons is-right mr-1">
|
||||
<button
|
||||
<span class="pos-order-invoice-collection-selector-wrapper mr-1">
|
||||
<BCheckbox
|
||||
v-if="props.invoiceView && props.allowSelectMultiple"
|
||||
class="button is-light is-small"
|
||||
@click="toggleInvoiceCollectionSelection(order.invoice_collection_id)"
|
||||
:class="{ 'is-dark': isInvoiceCollectionSelected(parseInt(order.invoice_collection_id)) }"
|
||||
>
|
||||
{{
|
||||
isInvoiceCollectionSelected(parseInt(order.invoice_collection_id))
|
||||
? SessionUser.objects.global.language.unselect
|
||||
: SessionUser.objects.global.language.select
|
||||
}}
|
||||
</button>
|
||||
class="pos-order-invoice-collection-selector"
|
||||
:model-value="isInvoiceCollectionSelected(order.invoice_collection_id)"
|
||||
:disabled="normalizeInvoiceCollectionId(order.invoice_collection_id) === null"
|
||||
:aria-label="`${SessionUser.objects.global.language.select} ${SessionUser.objects.global.language.invoice.toLowerCase()} #${order.invoice_collection_id || order.id}`"
|
||||
:data-testid="`pos-order-invoice-collection-selector-${order.id}`"
|
||||
@update:model-value="(checked) => setInvoiceCollectionSelection(order.invoice_collection_id, checked)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1872,74 +1776,17 @@ const formatCashierName = (order) => {
|
||||
<div class="columns is-multiline is-mobile">
|
||||
<!-- Bulk actions when invoice view allows selection -->
|
||||
<div class="column is-full" v-if="props.invoiceView && props.allowSelectMultiple">
|
||||
<div class="buttons">
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="invoiceSelectedCollections()"
|
||||
:disabled="selectedInvoiceCollections.length === 0 || isInvoiceQueueBusy"
|
||||
>
|
||||
{{ SessionUser.objects.global.language.invoice }}
|
||||
{{ SessionUser.objects.global.language.selected_multiple }} ({{ selectedInvoiceCollections.length }})
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="runInvoiceCollectionBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.CLEAN_CUSTOMER_RULES)"
|
||||
:disabled="selectedInvoiceCollections.length === 0"
|
||||
>
|
||||
{{ t("invoicing_period.invoice_collection_actions.actions.remove_customer_rule_violations") }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="runInvoiceCollectionBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.MERGE)"
|
||||
:disabled="selectedInvoiceCollections.length < 2"
|
||||
>
|
||||
{{ t("invoicing_period.invoice_collection_actions.actions.merge_collections") }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="runInvoiceCollectionBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.SPLIT_BY_MONTH)"
|
||||
:disabled="selectedInvoiceCollections.length === 0"
|
||||
>
|
||||
{{ t("invoicing_period.invoice_collection_actions.actions.split_by_month") }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="runInvoiceCollectionBulkAction(INVOICE_COLLECTION_BULK_ACTIONS.RESET_HIDDEN_PRICES)"
|
||||
:disabled="selectedInvoiceCollections.length === 0"
|
||||
>
|
||||
{{ t("invoicing_period.invoice_collection_actions.actions.reset_hidden_item_prices") }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="selectAllInvoiceCollections()"
|
||||
:class="{
|
||||
'is-light': !isInvoiceCollectionSelectedAll(),
|
||||
'is-dark': isInvoiceCollectionSelectedAll(),
|
||||
}"
|
||||
>
|
||||
{{
|
||||
isInvoiceCollectionSelectedAll()
|
||||
? SessionUser.objects.global.language.unselect
|
||||
: SessionUser.objects.global.language.select
|
||||
}}
|
||||
{{ SessionUser.objects.global.language.all.toLowerCase() }}
|
||||
</button>
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="toggleAutoExpandAll()"
|
||||
:class="{
|
||||
'is-light': !isAutoExpandAll(),
|
||||
'is-dark': isAutoExpandAll(),
|
||||
}"
|
||||
>
|
||||
{{
|
||||
isAutoExpandAll()
|
||||
? SessionUser.objects.global.language.collapse
|
||||
: SessionUser.objects.global.language.expand
|
||||
}}
|
||||
{{ SessionUser.objects.global.language.all.toLowerCase() }}
|
||||
</button>
|
||||
</div>
|
||||
<InvoiceCollectionSelectionActionWheel
|
||||
:selected-invoice-collection-ids="selectedInvoiceCollections"
|
||||
:total-invoice-collection-count="selectableInvoiceCollectionCount"
|
||||
:all-selected="isInvoiceCollectionSelectedAll()"
|
||||
:all-expanded="isAutoExpandAll()"
|
||||
:invoice-queue-busy="isInvoiceQueueBusy"
|
||||
@invoice-selected="invoiceSelectedCollections"
|
||||
@bulk-action="runInvoiceCollectionBulkAction"
|
||||
@toggle-select-all="selectAllInvoiceCollections"
|
||||
@toggle-expand-all="toggleAutoExpandAll"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Orders as cards -->
|
||||
@@ -2035,19 +1882,16 @@ const formatCashierName = (order) => {
|
||||
</span>
|
||||
</div>
|
||||
<div class="column is-12 has-text-right mb-2">
|
||||
<span class="buttons is-right mr-1">
|
||||
<button
|
||||
<span class="pos-order-invoice-collection-selector-wrapper mr-1">
|
||||
<BCheckbox
|
||||
v-if="props.invoiceView && props.allowSelectMultiple"
|
||||
class="button is-light is-small"
|
||||
@click="toggleInvoiceCollectionSelection(order.invoice_collection_id)"
|
||||
:class="{ 'is-dark': isInvoiceCollectionSelected(parseInt(order.invoice_collection_id)) }"
|
||||
>
|
||||
{{
|
||||
isInvoiceCollectionSelected(parseInt(order.invoice_collection_id))
|
||||
? SessionUser.objects.global.language.unselect
|
||||
: SessionUser.objects.global.language.select
|
||||
}}
|
||||
</button>
|
||||
class="pos-order-invoice-collection-selector"
|
||||
:model-value="isInvoiceCollectionSelected(order.invoice_collection_id)"
|
||||
:disabled="normalizeInvoiceCollectionId(order.invoice_collection_id) === null"
|
||||
:aria-label="`${SessionUser.objects.global.language.select} ${SessionUser.objects.global.language.invoice.toLowerCase()} #${order.invoice_collection_id || order.id}`"
|
||||
:data-testid="`pos-order-invoice-collection-selector-${order.id}`"
|
||||
@update:model-value="(checked) => setInvoiceCollectionSelection(order.invoice_collection_id, checked)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2415,6 +2259,111 @@ const formatCashierName = (order) => {
|
||||
padding-inline: 0.85rem;
|
||||
}
|
||||
|
||||
.pos-orders-table-row > td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.pos-orders-table-id-header,
|
||||
.pos-orders-table-id-cell {
|
||||
width: 5.75rem;
|
||||
max-width: 5.75rem;
|
||||
min-width: 5.75rem;
|
||||
}
|
||||
|
||||
.pos-orders-table-id-cell {
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pos-orders-table-id-cell-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pos-orders-table-id-cell :deep(.color-indicator) {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pos-orders-table-id-cell :deep(.icon-text) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pos-orders-table-actions-cell {
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pos-order-invoice-collection-selector-cell {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.pos-order-invoice-collection-selector-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pos-order-invoice-collection-selector {
|
||||
--bulma-checkbox-size: 1.35rem;
|
||||
width: 2rem;
|
||||
min-width: 2rem;
|
||||
height: 2rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-inline-end: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.pos-order-invoice-collection-selector :deep(.check) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pos-orders-table-header--compact {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pos-orders-table-header--compact .pos-orders-table-header__label {
|
||||
display: inline-block;
|
||||
font-size: var(--pleno-compact-table-header-font-size);
|
||||
line-height: var(--pleno-compact-table-header-line-height);
|
||||
max-width: 6rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.pos-orders-table-header--compact.pos-orders-table-header--vehicles .pos-orders-table-header__label {
|
||||
max-width: 4.5rem;
|
||||
}
|
||||
|
||||
.pos-order-vehicle-cell {
|
||||
width: 4.5rem;
|
||||
max-width: 4.5rem;
|
||||
min-width: 4.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.pos-order-vehicle-registration-line {
|
||||
max-width: 4.5rem;
|
||||
margin: 0;
|
||||
font-size: var(--pleno-compact-table-header-font-size);
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.pos-order-vehicle-registration-line :deep(*) {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.pos-orders-mobile-card {
|
||||
border: 1px solid #d7dee8;
|
||||
|
||||
@@ -176,7 +176,7 @@ const onClickAttachToOrder = (object) => {
|
||||
}"
|
||||
@click="onTableHeaderClick(tableHeaders.name)"
|
||||
>
|
||||
<span class="is-flex-wrap-nowrap">
|
||||
<span class="is-flex-wrap-nowrap pleno-table-header-content">
|
||||
<span class="has-text-weight-bold">{{ tableHeaders.title }}</span>
|
||||
<span
|
||||
v-if="isColumnCurrentlyBeingSortedBy(tableHeaders.name)"
|
||||
@@ -194,7 +194,7 @@ const onClickAttachToOrder = (object) => {
|
||||
'has-text-centered': tableHeaders.name === 'id'
|
||||
}"
|
||||
>
|
||||
<span class="is-flex-wrap-nowrap">
|
||||
<span class="is-flex-wrap-nowrap pleno-table-header-content">
|
||||
<span class="has-text-weight-bold">{{ tableHeaders.title }}</span>
|
||||
</span>
|
||||
</th>
|
||||
|
||||
@@ -453,7 +453,7 @@ const confirmCloseModal = () => {
|
||||
}"
|
||||
@click="onTableHeaderClick(tableHeaders.name)"
|
||||
>
|
||||
<span class="is-flex-wrap-nowrap">
|
||||
<span class="is-flex-wrap-nowrap pleno-table-header-content">
|
||||
<span class="has-text-weight-bold">{{ tableHeaders.title }}</span>
|
||||
<span
|
||||
v-if="isColumnCurrentlyBeingSortedBy(tableHeaders.name)"
|
||||
@@ -471,7 +471,7 @@ const confirmCloseModal = () => {
|
||||
'has-text-centered': tableHeaders.name === 'id'
|
||||
}"
|
||||
>
|
||||
<span class="is-flex-wrap-nowrap">
|
||||
<span class="is-flex-wrap-nowrap pleno-table-header-content">
|
||||
<span class="has-text-weight-bold">{{ tableHeaders.title }}</span>
|
||||
</span>
|
||||
</th>
|
||||
@@ -512,7 +512,7 @@ const confirmCloseModal = () => {
|
||||
</button>
|
||||
</td>
|
||||
</template>
|
||||
<td>
|
||||
<td class="has-text-centered">
|
||||
<ColorIndicator
|
||||
v-bind:color_class="getOrderInvoiceStatusBarColor(order)"
|
||||
v-bind:is_narrow="true"
|
||||
|
||||
@@ -651,7 +651,7 @@ const filteredObjects = computed(() => {
|
||||
}"
|
||||
@click="onTableHeaderClick(tableHeaders.name)"
|
||||
>
|
||||
<span class="is-flex-wrap-nowrap">
|
||||
<span class="is-flex-wrap-nowrap pleno-table-header-content">
|
||||
<span class="has-text-weight-bold">{{ tableHeaders.title }}</span>
|
||||
<span
|
||||
v-if="isColumnCurrentlyBeingSortedBy(tableHeaders.name)"
|
||||
@@ -669,7 +669,7 @@ const filteredObjects = computed(() => {
|
||||
'has-text-centered': tableHeaders.name === 'id'
|
||||
}"
|
||||
>
|
||||
<span class="is-flex-wrap-nowrap">
|
||||
<span class="is-flex-wrap-nowrap pleno-table-header-content">
|
||||
<span class="has-text-weight-bold">{{ tableHeaders.title }}</span>
|
||||
</span>
|
||||
</th>
|
||||
|
||||
@@ -7,6 +7,7 @@ const { t } = useI18n();
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
|
||||
import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue";
|
||||
import SuperuserInvoiceRowActions from "@/components/displays/superuser/tables/SuperuserInvoiceRowActions.vue";
|
||||
|
||||
const props = defineProps({
|
||||
objects: {
|
||||
@@ -65,8 +66,8 @@ const parseClosedAt = (value) => {
|
||||
<td>{{ parseClosedAt(object.closed_at) }}</td>
|
||||
<td>{{ parseDate(object.created_at) }}</td>
|
||||
<td>{{ parseDate(object.updated_at) }}</td>
|
||||
<td>
|
||||
<div class="buttons is-float-right">
|
||||
<td class="is-narrow">
|
||||
<SuperuserInvoiceRowActions :include-buffer="true">
|
||||
<ActionSettingsWheelButton>
|
||||
<template #actions>
|
||||
<ActionSettingsWheelItem
|
||||
@@ -76,7 +77,7 @@ const parseClosedAt = (value) => {
|
||||
/>
|
||||
</template>
|
||||
</ActionSettingsWheelButton>
|
||||
</div>
|
||||
</SuperuserInvoiceRowActions>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="objects.length === 0">
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
includeBuffer: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="buttons superuser-invoice-row-actions">
|
||||
<button
|
||||
v-if="includeBuffer"
|
||||
type="button"
|
||||
class="button is-small superuser-invoice-row-actions__buffer"
|
||||
aria-hidden="true"
|
||||
tabindex="-1"
|
||||
disabled
|
||||
data-testid="superuser-invoice-action-buffer"
|
||||
>
|
||||
<span class="icon">
|
||||
<i class="fas fa-paperclip"></i>
|
||||
</span>
|
||||
</button>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.superuser-invoice-row-actions {
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.superuser-invoice-row-actions > :deep(*) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.superuser-invoice-row-actions__buffer {
|
||||
width: 2rem;
|
||||
min-width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,10 @@
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { BCheckbox } from "buefy";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
|
||||
import {getCustomerName} from "@/components/shop/POSDepartmentProcess.vue";
|
||||
import { ref } from 'vue';
|
||||
const props = defineProps({
|
||||
objects: {
|
||||
type: Array,
|
||||
@@ -35,6 +34,7 @@ const { loadList } = usePaginatedListInstance();
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
|
||||
import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue";
|
||||
import SuperuserInvoiceRowActions from "@/components/displays/superuser/tables/SuperuserInvoiceRowActions.vue";
|
||||
|
||||
const redirect = (path) => {
|
||||
// Open the url in a new tab
|
||||
@@ -59,6 +59,12 @@ const onSelect = (id) => {
|
||||
props.onSelected(id);
|
||||
}
|
||||
|
||||
const onSelectorChecked = (id, checked) => {
|
||||
if (checked) {
|
||||
onSelect(id);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -124,22 +130,28 @@ const onSelect = (id) => {
|
||||
<!-- Total net amount -->
|
||||
<td v-if="props.showTotal">{{ SessionUser.functions.currency.toLocal(object.total_net_amount) }}</td>
|
||||
<!-- Actions -->
|
||||
<td v-if="!props.isSelector">
|
||||
<ActionSettingsWheelButton>
|
||||
<template #actions>
|
||||
<ActionSettingsWheelItem
|
||||
icon="fas fa-eye"
|
||||
@click="redirect('/superuser/invoices/' + object.id)"
|
||||
:label="t('common.show')"
|
||||
/>
|
||||
</template>
|
||||
</ActionSettingsWheelButton>
|
||||
<td v-if="!props.isSelector" class="is-narrow">
|
||||
<SuperuserInvoiceRowActions :include-buffer="true">
|
||||
<ActionSettingsWheelButton>
|
||||
<template #actions>
|
||||
<ActionSettingsWheelItem
|
||||
icon="fas fa-eye"
|
||||
@click="redirect('/superuser/invoices/' + object.id)"
|
||||
:label="t('common.show')"
|
||||
/>
|
||||
</template>
|
||||
</ActionSettingsWheelButton>
|
||||
</SuperuserInvoiceRowActions>
|
||||
</td>
|
||||
<!-- Selector -->
|
||||
<td v-if="props.isSelector">
|
||||
<button class="button is-small is-dark" @click="onSelect(object.id)">
|
||||
{{ $t('common.select') }}
|
||||
</button>
|
||||
<td v-if="props.isSelector" class="is-narrow">
|
||||
<BCheckbox
|
||||
:model-value="false"
|
||||
size="is-small"
|
||||
:aria-label="`${$t('common.select')} #${object.id}`"
|
||||
:data-testid="`collected-invoice-selector-${object.id}`"
|
||||
@update:model-value="(checked) => onSelectorChecked(object.id, checked)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
@@ -159,4 +171,4 @@ const onSelect = (id) => {
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -4152,6 +4152,15 @@
|
||||
"merge_requires_multiple_text": "Vælg mindst to fakturasamlinger for at sammenlægge dem.",
|
||||
"merge_requires_multiple_title": "Vælg flere fakturasamlinger",
|
||||
"merge_target_title": "Vælg målfakturasamling",
|
||||
"menu": {
|
||||
"collapse_all": "Klap alle sammen",
|
||||
"expand_all": "Udvid alle",
|
||||
"label": "Fakturasamling handlinger ({count})",
|
||||
"modification_section": "Rediger valgte",
|
||||
"select_all": "Vælg alle",
|
||||
"selection_section": "Valg",
|
||||
"unselect_all": "Fravælg alle"
|
||||
},
|
||||
"preview": {
|
||||
"affected_examples": "Eksempler på ændringer",
|
||||
"blocked_title": "Handlingen er blokeret",
|
||||
|
||||
@@ -4263,6 +4263,15 @@
|
||||
"merge_requires_multiple_text": "Wählen Sie mindestens zwei Rechnungssammlungen aus, um sie zusammenzuführen.",
|
||||
"merge_requires_multiple_title": "Mehrere Rechnungssammlungen auswählen",
|
||||
"merge_target_title": "Ziel-Rechnungssammlung auswählen",
|
||||
"menu": {
|
||||
"collapse_all": "Alle einklappen",
|
||||
"expand_all": "Alle erweitern",
|
||||
"label": "Rechnungssammlungsaktionen ({count})",
|
||||
"modification_section": "Ausgewählte ändern",
|
||||
"select_all": "Alle auswählen",
|
||||
"selection_section": "Auswahl",
|
||||
"unselect_all": "Alle abwählen"
|
||||
},
|
||||
"preview": {
|
||||
"affected_examples": "Beispieländerungen",
|
||||
"blocked_title": "Aktion ist blockiert",
|
||||
|
||||
@@ -3984,6 +3984,15 @@
|
||||
"merge_requires_multiple_text": "Select at least two invoice collections to merge them.",
|
||||
"merge_requires_multiple_title": "Select multiple invoice collections",
|
||||
"merge_target_title": "Select target invoice collection",
|
||||
"menu": {
|
||||
"collapse_all": "Collapse all",
|
||||
"expand_all": "Expand all",
|
||||
"label": "Invoice collection actions ({count})",
|
||||
"modification_section": "Modify selected",
|
||||
"select_all": "Select all",
|
||||
"selection_section": "Selection",
|
||||
"unselect_all": "Unselect all"
|
||||
},
|
||||
"preview": {
|
||||
"affected_examples": "Example changes",
|
||||
"blocked_title": "Action is blocked",
|
||||
|
||||
@@ -3237,6 +3237,15 @@
|
||||
"merge_requires_multiple_text": "@:{'templates.generated.compat.invoicing_period.invoice_collection_actions.merge_requires_multiple_text'}",
|
||||
"merge_requires_multiple_title": "@:{'templates.generated.compat.invoicing_period.invoice_collection_actions.merge_requires_multiple_title'}",
|
||||
"merge_target_title": "@:{'templates.generated.compat.invoicing_period.invoice_collection_actions.merge_target_title'}",
|
||||
"menu": {
|
||||
"collapse_all": "@:{'templates.generated.compat.invoicing_period.invoice_collection_actions.menu.collapse_all'}",
|
||||
"expand_all": "@:{'templates.generated.compat.invoicing_period.invoice_collection_actions.menu.expand_all'}",
|
||||
"label": "@:{'templates.generated.compat.invoicing_period.invoice_collection_actions.menu.label'}",
|
||||
"modification_section": "@:{'templates.generated.compat.invoicing_period.invoice_collection_actions.menu.modification_section'}",
|
||||
"select_all": "@:{'templates.generated.compat.invoicing_period.invoice_collection_actions.menu.select_all'}",
|
||||
"selection_section": "@:{'templates.generated.compat.invoicing_period.invoice_collection_actions.menu.selection_section'}",
|
||||
"unselect_all": "@:{'templates.generated.compat.invoicing_period.invoice_collection_actions.menu.unselect_all'}"
|
||||
},
|
||||
"preview": {
|
||||
"affected_examples": "@:{'templates.generated.compat.invoicing_period.invoice_collection_actions.preview.affected_examples'}",
|
||||
"blocked_title": "@:{'templates.generated.compat.invoicing_period.invoice_collection_actions.preview.blocked_title'}",
|
||||
|
||||
@@ -4266,6 +4266,15 @@
|
||||
"merge_requires_multiple_text": "Velg minst to fakturasamlinger for å slå dem sammen.",
|
||||
"merge_requires_multiple_title": "Velg flere fakturasamlinger",
|
||||
"merge_target_title": "Velg målfakturasamling",
|
||||
"menu": {
|
||||
"collapse_all": "Slå sammen alle",
|
||||
"expand_all": "Utvid alle",
|
||||
"label": "Fakturasamling handlinger ({count})",
|
||||
"modification_section": "Endre valgte",
|
||||
"select_all": "Velg alle",
|
||||
"selection_section": "Valg",
|
||||
"unselect_all": "Fjern valg av alle"
|
||||
},
|
||||
"preview": {
|
||||
"affected_examples": "Eksempler på endringer",
|
||||
"blocked_title": "Handlingen er blokkert",
|
||||
|
||||
@@ -4316,6 +4316,15 @@
|
||||
"merge_requires_multiple_text": "Välj minst två fakturasamlingar för att slå ihop dem.",
|
||||
"merge_requires_multiple_title": "Välj flera fakturasamlingar",
|
||||
"merge_target_title": "Välj målfakturasamling",
|
||||
"menu": {
|
||||
"collapse_all": "Fäll ihop alla",
|
||||
"expand_all": "Expandera alla",
|
||||
"label": "Fakturasamlingsåtgärder ({count})",
|
||||
"modification_section": "Ändra valda",
|
||||
"select_all": "Välj alla",
|
||||
"selection_section": "Val",
|
||||
"unselect_all": "Avmarkera alla"
|
||||
},
|
||||
"preview": {
|
||||
"affected_examples": "Exempel på ändringar",
|
||||
"blocked_title": "Åtgärden är blockerad",
|
||||
|
||||
@@ -13,6 +13,15 @@
|
||||
"merge_requires_multiple_text": "Vælg mindst to fakturasamlinger for at sammenlægge dem.",
|
||||
"merge_requires_multiple_title": "Vælg flere fakturasamlinger",
|
||||
"merge_target_title": "Vælg målfakturasamling",
|
||||
"menu": {
|
||||
"collapse_all": "Klap alle sammen",
|
||||
"expand_all": "Udvid alle",
|
||||
"label": "Fakturasamling handlinger ({count})",
|
||||
"modification_section": "Rediger valgte",
|
||||
"select_all": "Vælg alle",
|
||||
"selection_section": "Valg",
|
||||
"unselect_all": "Fravælg alle"
|
||||
},
|
||||
"preview": {
|
||||
"affected_examples": "Eksempler på ændringer",
|
||||
"blocked_title": "Handlingen er blokeret",
|
||||
|
||||
@@ -13,6 +13,15 @@
|
||||
"merge_requires_multiple_text": "Wählen Sie mindestens zwei Rechnungssammlungen aus, um sie zusammenzuführen.",
|
||||
"merge_requires_multiple_title": "Mehrere Rechnungssammlungen auswählen",
|
||||
"merge_target_title": "Ziel-Rechnungssammlung auswählen",
|
||||
"menu": {
|
||||
"collapse_all": "Alle einklappen",
|
||||
"expand_all": "Alle erweitern",
|
||||
"label": "Rechnungssammlungsaktionen ({count})",
|
||||
"modification_section": "Ausgewählte ändern",
|
||||
"select_all": "Alle auswählen",
|
||||
"selection_section": "Auswahl",
|
||||
"unselect_all": "Alle abwählen"
|
||||
},
|
||||
"preview": {
|
||||
"affected_examples": "Beispieländerungen",
|
||||
"blocked_title": "Aktion ist blockiert",
|
||||
|
||||
@@ -13,6 +13,15 @@
|
||||
"merge_requires_multiple_text": "Select at least two invoice collections to merge them.",
|
||||
"merge_requires_multiple_title": "Select multiple invoice collections",
|
||||
"merge_target_title": "Select target invoice collection",
|
||||
"menu": {
|
||||
"collapse_all": "Collapse all",
|
||||
"expand_all": "Expand all",
|
||||
"label": "Invoice collection actions ({count})",
|
||||
"modification_section": "Modify selected",
|
||||
"select_all": "Select all",
|
||||
"selection_section": "Selection",
|
||||
"unselect_all": "Unselect all"
|
||||
},
|
||||
"preview": {
|
||||
"affected_examples": "Example changes",
|
||||
"blocked_title": "Action is blocked",
|
||||
|
||||
@@ -12,6 +12,15 @@
|
||||
"merge_requires_multiple_text": "@:{'phrases.compat.invoicing_period.invoice_collection_actions.merge_requires_multiple_text'}",
|
||||
"merge_requires_multiple_title": "@:{'phrases.compat.invoicing_period.invoice_collection_actions.merge_requires_multiple_title'}",
|
||||
"merge_target_title": "@:{'phrases.compat.invoicing_period.invoice_collection_actions.merge_target_title'}",
|
||||
"menu": {
|
||||
"collapse_all": "@:{'phrases.compat.invoicing_period.invoice_collection_actions.menu.collapse_all'}",
|
||||
"expand_all": "@:{'phrases.compat.invoicing_period.invoice_collection_actions.menu.expand_all'}",
|
||||
"label": "@:{'phrases.compat.invoicing_period.invoice_collection_actions.menu.label'}",
|
||||
"modification_section": "@:{'phrases.compat.invoicing_period.invoice_collection_actions.menu.modification_section'}",
|
||||
"select_all": "@:{'phrases.compat.invoicing_period.invoice_collection_actions.menu.select_all'}",
|
||||
"selection_section": "@:{'phrases.compat.invoicing_period.invoice_collection_actions.menu.selection_section'}",
|
||||
"unselect_all": "@:{'phrases.compat.invoicing_period.invoice_collection_actions.menu.unselect_all'}"
|
||||
},
|
||||
"preview": {
|
||||
"affected_examples": "@:{'phrases.compat.invoicing_period.invoice_collection_actions.preview.affected_examples'}",
|
||||
"blocked_title": "@:{'phrases.compat.invoicing_period.invoice_collection_actions.preview.blocked_title'}",
|
||||
|
||||
@@ -13,6 +13,15 @@
|
||||
"merge_requires_multiple_text": "Velg minst to fakturasamlinger for å slå dem sammen.",
|
||||
"merge_requires_multiple_title": "Velg flere fakturasamlinger",
|
||||
"merge_target_title": "Velg målfakturasamling",
|
||||
"menu": {
|
||||
"collapse_all": "Slå sammen alle",
|
||||
"expand_all": "Utvid alle",
|
||||
"label": "Fakturasamling handlinger ({count})",
|
||||
"modification_section": "Endre valgte",
|
||||
"select_all": "Velg alle",
|
||||
"selection_section": "Valg",
|
||||
"unselect_all": "Fjern valg av alle"
|
||||
},
|
||||
"preview": {
|
||||
"affected_examples": "Eksempler på endringer",
|
||||
"blocked_title": "Handlingen er blokkert",
|
||||
|
||||
@@ -13,6 +13,15 @@
|
||||
"merge_requires_multiple_text": "Välj minst två fakturasamlingar för att slå ihop dem.",
|
||||
"merge_requires_multiple_title": "Välj flera fakturasamlingar",
|
||||
"merge_target_title": "Välj målfakturasamling",
|
||||
"menu": {
|
||||
"collapse_all": "Fäll ihop alla",
|
||||
"expand_all": "Expandera alla",
|
||||
"label": "Fakturasamlingsåtgärder ({count})",
|
||||
"modification_section": "Ändra valda",
|
||||
"select_all": "Välj alla",
|
||||
"selection_section": "Val",
|
||||
"unselect_all": "Avmarkera alla"
|
||||
},
|
||||
"preview": {
|
||||
"affected_examples": "Exempel på ändringar",
|
||||
"blocked_title": "Åtgärden är blockerad",
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
installReleaseErrorInstrumentation,
|
||||
} from '@/services/releaseTimeline.js';
|
||||
import { RELEASE_RUNTIME_GLOBAL_KEY } from '@/services/releaseBootstrap.js';
|
||||
import '@/services/tableHeaderPreferences.js';
|
||||
|
||||
import { IS_DEV } from './config';
|
||||
const VITE_BUILD_DATE = import.meta.env.VITE_BUILD_DATE || '';
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { ref } from "vue";
|
||||
|
||||
export const LARGE_TABLE_HEADERS_STORAGE_KEY = "pleno.useLargeTableHeaders";
|
||||
export const LARGE_TABLE_HEADERS_BODY_CLASS = "pleno-large-table-headers";
|
||||
|
||||
const applyLargeTableHeadersClass = (value) => {
|
||||
if (typeof document === "undefined" || !document.body) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.body.classList.toggle(LARGE_TABLE_HEADERS_BODY_CLASS, Boolean(value));
|
||||
};
|
||||
|
||||
const readStoredBoolean = () => {
|
||||
if (typeof window === "undefined" || !window.localStorage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return window.localStorage.getItem(LARGE_TABLE_HEADERS_STORAGE_KEY) === "true";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const useLargeTableHeaders = ref(readStoredBoolean());
|
||||
applyLargeTableHeadersClass(useLargeTableHeaders.value);
|
||||
|
||||
export const setUseLargeTableHeaders = (value) => {
|
||||
const nextValue = Boolean(value);
|
||||
useLargeTableHeaders.value = nextValue;
|
||||
applyLargeTableHeadersClass(nextValue);
|
||||
|
||||
if (typeof window === "undefined" || !window.localStorage) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.localStorage.setItem(LARGE_TABLE_HEADERS_STORAGE_KEY, nextValue ? "true" : "false");
|
||||
} catch {
|
||||
// Storage is optional; keep the in-memory preference for this session.
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("storage", (event) => {
|
||||
if (event.key === LARGE_TABLE_HEADERS_STORAGE_KEY) {
|
||||
useLargeTableHeaders.value = event.newValue === "true";
|
||||
applyLargeTableHeadersClass(useLargeTableHeaders.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import ShowErrorField from "@/components/global/ShowErrorField.vue";
|
||||
import SubuserGrantSelector from "@/components/session/subuser/SubuserGrantSelector.vue";
|
||||
import { parseError, removeError, addError } from "@/components/request/HandleGlobalError.vue";
|
||||
import ConfigurationSwitch from "@/components/displays/superuser/configuration/ConfigurationSwitch.vue";
|
||||
import { setUseLargeTableHeaders, useLargeTableHeaders } from "@/services/tableHeaderPreferences.js";
|
||||
|
||||
const { t } = useI18n();
|
||||
const isLoading = ref(false);
|
||||
@@ -792,6 +793,22 @@ const onClickSaveWashCertificateEmail = async () => {
|
||||
{{ $t("user_dashboard.profile.driver.customer_switch_help") }}
|
||||
</p>
|
||||
</ConfigurationCategory>
|
||||
<!-- Display preferences -->
|
||||
<ConfigurationCategory
|
||||
class="mt-2"
|
||||
title="Visning"
|
||||
description="Personlige visningsindstillinger for tabeller."
|
||||
icon="fas fa-table"
|
||||
data-testid="user-profile-display-preferences-card"
|
||||
>
|
||||
<ConfigurationSwitch
|
||||
data-testid="user-profile-large-table-headers-switch"
|
||||
title="Store tabeloverskrifter"
|
||||
description="Brug de større tabeloverskrifter i stedet for den kompakte standardvisning."
|
||||
:value="useLargeTableHeaders"
|
||||
:on-switch="setUseLargeTableHeaders"
|
||||
/>
|
||||
</ConfigurationCategory>
|
||||
<!-- Notifications - email notifications -->
|
||||
<ConfigurationCategory
|
||||
v-if="!SessionUser.isSubuser.value"
|
||||
|
||||
@@ -2613,6 +2613,48 @@ test.describe("Admin POS Orders - desktop action menu layout", () => {
|
||||
await primeOperatorSession(page, "pos-orders-action-menu-layout-token", SUPERUSER_POS_PERMISSIONS);
|
||||
});
|
||||
|
||||
test("keeps desktop order list row cells aligned", async ({ page }) => {
|
||||
await page.goto("/admin/12/modules/pos/orders");
|
||||
await expect(page.locator("table")).toBeVisible();
|
||||
|
||||
const row = getVisibleTestId(page, "pos-order-list-settings-54518").locator("xpath=ancestor::tr").first();
|
||||
await expect(row).toBeVisible();
|
||||
|
||||
const rowMetrics = await row.evaluate((rowElement) => {
|
||||
const cells = Array.from(rowElement.children).filter((child) => child.tagName.toLowerCase() === "td");
|
||||
const idCell = rowElement.querySelector(".pos-orders-table-id-cell");
|
||||
const idIndicator = idCell?.querySelector(".pos-orders-table-id-cell-content");
|
||||
const cellSummaries = cells.map((cell) => {
|
||||
const rect = cell.getBoundingClientRect();
|
||||
return {
|
||||
className: cell.className,
|
||||
verticalAlign: window.getComputedStyle(cell).verticalAlign,
|
||||
width: rect.width,
|
||||
};
|
||||
});
|
||||
const idCellRect = idCell?.getBoundingClientRect();
|
||||
const idIndicatorRect = idIndicator?.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
cellSummaries,
|
||||
idCenterDelta:
|
||||
idCellRect && idIndicatorRect
|
||||
? Math.abs(idIndicatorRect.left + idIndicatorRect.width / 2 - (idCellRect.left + idCellRect.width / 2))
|
||||
: null,
|
||||
idCellWidth: idCellRect?.width ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
expect(rowMetrics.cellSummaries.length).toBeGreaterThan(5);
|
||||
expect(rowMetrics.cellSummaries.every((cell) => cell.verticalAlign === "middle")).toBe(true);
|
||||
expect(rowMetrics.idCenterDelta).not.toBeNull();
|
||||
expect(rowMetrics.idCenterDelta ?? 999).toBeLessThanOrEqual(2);
|
||||
expect(rowMetrics.idCellWidth ?? 999).toBeLessThanOrEqual(100);
|
||||
expect(
|
||||
rowMetrics.cellSummaries.some((cell) => String(cell.className).includes("pos-orders-table-actions-cell"))
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("keeps tall menus within the usable viewport and starts from the first action", async ({ page }) => {
|
||||
await page.goto("/admin/12/modules/pos/orders");
|
||||
await expect(page.locator("table")).toBeVisible();
|
||||
|
||||
@@ -166,11 +166,11 @@ test.describe("POS order actions", () => {
|
||||
"Expected invoice collection picker modal to open after clicking the action."
|
||||
).toBeVisible();
|
||||
|
||||
const selectButtons = pickerModal.getByRole("button", { name: /Vælg|Select/i });
|
||||
const selectableCount = await selectButtons.count();
|
||||
const selectCheckboxes = pickerModal.locator('[data-testid^="collected-invoice-selector-"] input[type="checkbox"]');
|
||||
const selectableCount = await selectCheckboxes.count();
|
||||
test.skip(selectableCount === 0, "No invoice collections available for selection in test data.");
|
||||
|
||||
await selectButtons.first().click();
|
||||
await selectCheckboxes.first().check();
|
||||
|
||||
await expect(page).toHaveURL(beforeUrl);
|
||||
await expect(page.locator(".swal2-container, .swal2-popup")).toContainText(
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
// @vitest-environment jsdom
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import CollectedOrderInvoicesTable from "@/components/displays/superuser/tables/collectedOrderInvoicesTable.vue";
|
||||
import CollectedOrderInvoicesDefaultTable from "@/components/displays/superuser/tables/CollectedOrderInvoicesDefaultTable.vue";
|
||||
|
||||
vi.mock("@/components/pagination/paginatedList.vue", () => ({
|
||||
usePaginatedListInstance: () => ({
|
||||
loadList: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
||||
SessionUser: {
|
||||
objects: {
|
||||
collectedOrderInvoices: {
|
||||
columns: {
|
||||
id: { label: "ID" },
|
||||
customer_number: { label: "Customer number" },
|
||||
processor: { label: "Processor" },
|
||||
created_at: { label: "Created" },
|
||||
name: { label: "Name" },
|
||||
notes: { label: "Notes" },
|
||||
external_id: { label: "External ID" },
|
||||
po_number: { label: "PO" },
|
||||
closed_at: { label: "Closed" },
|
||||
updated_at: { label: "Updated" },
|
||||
},
|
||||
meta: {
|
||||
labels: {
|
||||
multiple: "invoice collections",
|
||||
},
|
||||
},
|
||||
},
|
||||
global: {
|
||||
language: {
|
||||
customer_name: "Customer",
|
||||
},
|
||||
},
|
||||
orders: {
|
||||
meta: {
|
||||
title: "Orders",
|
||||
},
|
||||
},
|
||||
},
|
||||
functions: {
|
||||
currency: {
|
||||
toLocal: (value) => String(value ?? ""),
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("vue-i18n", () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) => key,
|
||||
}),
|
||||
createI18n: () => ({
|
||||
global: {
|
||||
t: (key) => key,
|
||||
locale: {
|
||||
value: "en",
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
const EditableTableColumnStub = {
|
||||
props: ["object", "column", "parseFunction"],
|
||||
template: "<td>{{ parseFunction ? parseFunction(object[column]) : object[column] }}</td>",
|
||||
};
|
||||
|
||||
const ActionSettingsWheelButtonStub = {
|
||||
template: '<div data-testid="action-settings-wheel-stub"><slot name="actions" /></div>',
|
||||
};
|
||||
|
||||
const ActionSettingsWheelItemStub = {
|
||||
template: '<button type="button" data-testid="action-settings-wheel-item-stub"></button>',
|
||||
};
|
||||
|
||||
const BCheckboxStub = {
|
||||
props: ["modelValue"],
|
||||
emits: ["update:modelValue"],
|
||||
template: `
|
||||
<label :data-testid="$attrs['data-testid']">
|
||||
<input
|
||||
type="checkbox"
|
||||
:aria-label="$attrs['aria-label']"
|
||||
:checked="modelValue"
|
||||
@change="$emit('update:modelValue', $event.target.checked)"
|
||||
/>
|
||||
</label>
|
||||
`,
|
||||
};
|
||||
|
||||
const mountSelectorTable = (props = {}) =>
|
||||
mount(CollectedOrderInvoicesTable, {
|
||||
props: {
|
||||
objects: [
|
||||
{
|
||||
id: 42,
|
||||
objects: 1,
|
||||
customer_name: "Acme",
|
||||
customer_number: 1001,
|
||||
processor: 1,
|
||||
orders: 2,
|
||||
closed_at: null,
|
||||
created_at: "2026-07-01",
|
||||
total_net_amount: 123,
|
||||
},
|
||||
],
|
||||
showEmpty: true,
|
||||
...props,
|
||||
},
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (key) => key,
|
||||
},
|
||||
stubs: {
|
||||
EditableTableColumn: EditableTableColumnStub,
|
||||
ActionSettingsWheelButton: ActionSettingsWheelButtonStub,
|
||||
ActionSettingsWheelItem: ActionSettingsWheelItemStub,
|
||||
BCheckbox: BCheckboxStub,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe("CollectedOrderInvoicesTable", () => {
|
||||
it("uses a Buefy checkbox for selector mode", async () => {
|
||||
const onSelected = vi.fn();
|
||||
const wrapper = mountSelectorTable({
|
||||
isSelector: true,
|
||||
onSelected,
|
||||
});
|
||||
|
||||
expect(wrapper.find("button.button.is-small.is-dark").exists()).toBe(false);
|
||||
|
||||
await wrapper.get('[data-testid="collected-invoice-selector-42"] input').setValue(true);
|
||||
|
||||
expect(onSelected).toHaveBeenCalledWith(42);
|
||||
expect(wrapper.emitted("update:selected")?.[0]).toEqual([42]);
|
||||
});
|
||||
|
||||
it("keeps a hidden attachment-width buffer before collected invoice row actions", () => {
|
||||
const wrapper = mountSelectorTable();
|
||||
|
||||
expect(wrapper.get('[data-testid="superuser-invoice-action-buffer"]').exists()).toBe(true);
|
||||
expect(wrapper.get('[data-testid="action-settings-wheel-stub"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("CollectedOrderInvoicesDefaultTable", () => {
|
||||
it("keeps a hidden attachment-width buffer before default invoice row actions", () => {
|
||||
const wrapper = mount(CollectedOrderInvoicesDefaultTable, {
|
||||
props: {
|
||||
objects: [
|
||||
{
|
||||
id: 43,
|
||||
customer_number: 1002,
|
||||
name: "Acme July",
|
||||
notes: null,
|
||||
processor: 1,
|
||||
external_id: null,
|
||||
po_number: null,
|
||||
closed_at: null,
|
||||
created_at: "2026-07-01",
|
||||
updated_at: "2026-07-02",
|
||||
},
|
||||
],
|
||||
},
|
||||
global: {
|
||||
mocks: {
|
||||
$t: (key) => key,
|
||||
},
|
||||
stubs: {
|
||||
ActionSettingsWheelButton: ActionSettingsWheelButtonStub,
|
||||
ActionSettingsWheelItem: ActionSettingsWheelItemStub,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.get('[data-testid="superuser-invoice-action-buffer"]').exists()).toBe(true);
|
||||
expect(wrapper.get('[data-testid="action-settings-wheel-stub"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,187 @@
|
||||
// @vitest-environment jsdom
|
||||
import { defineComponent } from "vue";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import InvoiceCollectionSelectionActionWheel from "@/components/displays/department/pos/orders/InvoiceCollectionSelectionActionWheel.vue";
|
||||
import { INVOICE_COLLECTION_BULK_ACTIONS } from "@/components/displays/department/pos/orders/invoiceCollectionBulkActions.js";
|
||||
|
||||
vi.mock("vue-i18n", () => ({
|
||||
useI18n: () => ({
|
||||
t: (key, params = {}) => (params.count === undefined ? key : `${key}:${params.count}`),
|
||||
}),
|
||||
createI18n: () => ({
|
||||
global: {
|
||||
t: (key) => key,
|
||||
},
|
||||
install: () => {},
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/displays/buttons/ActionSettingsWheelButton.vue", () => ({
|
||||
default: {
|
||||
template: '<div data-testid="settings-wheel-stub"><slot name="actions"></slot></div>',
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/components/displays/buttons/ActionSettingsWheelItemLabel.vue", () => ({
|
||||
default: {
|
||||
props: ["label"],
|
||||
template: '<div class="action-settings-wheel-label-stub">{{ label }}</div>',
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/components/displays/buttons/ActionSettingsWheelItem.vue", () => ({
|
||||
default: {
|
||||
props: {
|
||||
clickAction: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
testId: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<button
|
||||
type="button"
|
||||
class="action-settings-wheel-item-stub"
|
||||
:data-testid="testId || undefined"
|
||||
:disabled="disabled"
|
||||
@click="clickAction && clickAction()"
|
||||
>
|
||||
{{ label }}
|
||||
</button>
|
||||
`,
|
||||
},
|
||||
}));
|
||||
|
||||
const ActionSettingsWheelButtonStub = {
|
||||
template: '<div data-testid="settings-wheel-stub"><slot name="actions"></slot></div>',
|
||||
};
|
||||
|
||||
const ActionSettingsWheelItemLabelStub = {
|
||||
props: ["label"],
|
||||
template: '<div class="action-settings-wheel-label-stub">{{ label }}</div>',
|
||||
};
|
||||
|
||||
const ActionSettingsWheelItemStub = defineComponent({
|
||||
name: "ActionSettingsWheelItem",
|
||||
props: {
|
||||
clickAction: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
testId: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<button
|
||||
type="button"
|
||||
class="action-settings-wheel-item-stub"
|
||||
:data-testid="testId || undefined"
|
||||
:disabled="disabled"
|
||||
@click="clickAction && clickAction()"
|
||||
>
|
||||
{{ label }}
|
||||
</button>
|
||||
`,
|
||||
});
|
||||
|
||||
const mountWheel = (props = {}) =>
|
||||
mount(InvoiceCollectionSelectionActionWheel, {
|
||||
props: {
|
||||
selectedInvoiceCollectionIds: [],
|
||||
totalInvoiceCollectionCount: 2,
|
||||
allSelected: false,
|
||||
allExpanded: false,
|
||||
invoiceQueueBusy: false,
|
||||
...props,
|
||||
},
|
||||
global: {
|
||||
stubs: {
|
||||
ActionSettingsWheelButton: ActionSettingsWheelButtonStub,
|
||||
ActionSettingsWheelItem: ActionSettingsWheelItemStub,
|
||||
ActionSettingsWheelItemLabel: ActionSettingsWheelItemLabelStub,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe("InvoiceCollectionSelectionActionWheel", () => {
|
||||
it("does not render without selectable invoice collections", () => {
|
||||
const wrapper = mountWheel({ totalInvoiceCollectionCount: 0 });
|
||||
|
||||
expect(wrapper.find('[data-testid="invoice-collection-selection-action-wheel"]').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it("shows selection controls without modification actions when nothing is selected", async () => {
|
||||
const wrapper = mountWheel();
|
||||
|
||||
expect(wrapper.find('[data-testid="invoice-collection-selection-action-wheel-select-all"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-testid="invoice-collection-selection-action-wheel-expand-all"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-testid="invoice-collection-selection-action-wheel-invoice-selected"]').exists()).toBe(
|
||||
false
|
||||
);
|
||||
expect(wrapper.find('[data-testid="invoice-collection-selection-action-wheel-merge"]').exists()).toBe(false);
|
||||
|
||||
await wrapper.get('[data-testid="invoice-collection-selection-action-wheel-select-all"]').trigger("click");
|
||||
await wrapper.get('[data-testid="invoice-collection-selection-action-wheel-expand-all"]').trigger("click");
|
||||
|
||||
expect(wrapper.emitted("toggleSelectAll")).toHaveLength(1);
|
||||
expect(wrapper.emitted("toggleExpandAll")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("shows single-selection modification actions and hides merge", async () => {
|
||||
const wrapper = mountWheel({ selectedInvoiceCollectionIds: [3001] });
|
||||
|
||||
expect(wrapper.find('[data-testid="invoice-collection-selection-action-wheel-invoice-selected"]').exists()).toBe(
|
||||
true
|
||||
);
|
||||
expect(wrapper.find('[data-testid="invoice-collection-selection-action-wheel-clean-rules"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-testid="invoice-collection-selection-action-wheel-split-by-month"]').exists()).toBe(
|
||||
true
|
||||
);
|
||||
expect(wrapper.find('[data-testid="invoice-collection-selection-action-wheel-reset-hidden-prices"]').exists()).toBe(
|
||||
true
|
||||
);
|
||||
expect(wrapper.find('[data-testid="invoice-collection-selection-action-wheel-merge"]').exists()).toBe(false);
|
||||
|
||||
await wrapper.get('[data-testid="invoice-collection-selection-action-wheel-clean-rules"]').trigger("click");
|
||||
|
||||
expect(wrapper.emitted("bulkAction")).toEqual([[INVOICE_COLLECTION_BULK_ACTIONS.CLEAN_CUSTOMER_RULES]]);
|
||||
});
|
||||
|
||||
it("shows merge for multiple selections and disables invoicing while queue is busy", async () => {
|
||||
const wrapper = mountWheel({
|
||||
selectedInvoiceCollectionIds: [3001, 3002],
|
||||
invoiceQueueBusy: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
wrapper.get('[data-testid="invoice-collection-selection-action-wheel-invoice-selected"]').attributes("disabled")
|
||||
).toBeDefined();
|
||||
expect(wrapper.find('[data-testid="invoice-collection-selection-action-wheel-merge"]').exists()).toBe(true);
|
||||
|
||||
await wrapper.get('[data-testid="invoice-collection-selection-action-wheel-merge"]').trigger("click");
|
||||
|
||||
expect(wrapper.emitted("bulkAction")).toEqual([[INVOICE_COLLECTION_BULK_ACTIONS.MERGE]]);
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,13 @@
|
||||
// @vitest-environment jsdom
|
||||
import { defineComponent, h, ref } from "vue";
|
||||
import { defineComponent, h, nextTick, ref } from "vue";
|
||||
import { flushPromises, mount } from "@vue/test-utils";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { INVOICE_COLLECTION_BULK_ACTIONS } from "@/components/displays/department/pos/orders/invoiceCollectionBulkActions.js";
|
||||
import { setUseLargeTableHeaders } from "@/services/tableHeaderPreferences.js";
|
||||
|
||||
const getSingleMock = vi.hoisted(() => vi.fn());
|
||||
const bulkActionPreviewMock = vi.hoisted(() => vi.fn());
|
||||
const bulkActionApplyMock = vi.hoisted(() => vi.fn());
|
||||
const sessionState = vi.hoisted(() => ({
|
||||
canAccessSuperUser: false,
|
||||
}));
|
||||
@@ -30,6 +34,9 @@ const invoiceQueueState = vi.hoisted(() => {
|
||||
vi.mock("vue-i18n", () => ({
|
||||
useI18n: () => ({
|
||||
t: (key) => key,
|
||||
locale: {
|
||||
value: "en",
|
||||
},
|
||||
}),
|
||||
createI18n: () => ({
|
||||
global: {
|
||||
@@ -146,6 +153,10 @@ vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
||||
get: {
|
||||
single: getSingleMock,
|
||||
},
|
||||
functions: {
|
||||
bulk_action_preview: bulkActionPreviewMock,
|
||||
bulk_action_apply: bulkActionApplyMock,
|
||||
},
|
||||
},
|
||||
global: {
|
||||
language: {
|
||||
@@ -178,6 +189,7 @@ vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
||||
date: {
|
||||
timeAgo: () => "just now",
|
||||
},
|
||||
parseErrorMessage: (error) => error?.message || String(error),
|
||||
},
|
||||
},
|
||||
}));
|
||||
@@ -261,6 +273,74 @@ const ActionSettingsWheelButtonStub = {
|
||||
template: '<div class="action-settings-wheel-button-stub"><slot name="actions"></slot></div>',
|
||||
};
|
||||
|
||||
const InvoiceCollectionSelectionActionWheelStub = defineComponent({
|
||||
name: "InvoiceCollectionSelectionActionWheel",
|
||||
props: {
|
||||
selectedInvoiceCollectionIds: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
totalInvoiceCollectionCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
allSelected: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
allExpanded: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
invoiceQueueBusy: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["invoiceSelected", "bulkAction", "toggleSelectAll", "toggleExpandAll"],
|
||||
template: `
|
||||
<div
|
||||
class="invoice-collection-selection-action-wheel-stub"
|
||||
:data-selected-count="selectedInvoiceCollectionIds.length"
|
||||
:data-total-count="totalInvoiceCollectionCount"
|
||||
:data-all-selected="String(allSelected)"
|
||||
:data-all-expanded="String(allExpanded)"
|
||||
:data-queue-busy="String(invoiceQueueBusy)"
|
||||
>
|
||||
<button data-testid="selection-wheel-invoice-selected" @click="$emit('invoiceSelected')"></button>
|
||||
<button data-testid="selection-wheel-clean-rules" @click="$emit('bulkAction', '${INVOICE_COLLECTION_BULK_ACTIONS.CLEAN_CUSTOMER_RULES}')"></button>
|
||||
<button data-testid="selection-wheel-select-all" @click="$emit('toggleSelectAll')"></button>
|
||||
<button data-testid="selection-wheel-expand-all" @click="$emit('toggleExpandAll')"></button>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
const BCheckboxStub = defineComponent({
|
||||
name: "BCheckbox",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
template: `
|
||||
<label :class="$attrs.class" :data-testid="$attrs['data-testid']">
|
||||
<input
|
||||
type="checkbox"
|
||||
:aria-label="$attrs['aria-label']"
|
||||
:checked="modelValue"
|
||||
:disabled="disabled"
|
||||
@change="$emit('update:modelValue', $event.target.checked)"
|
||||
/>
|
||||
</label>
|
||||
`,
|
||||
});
|
||||
|
||||
const ActionSettingsWheelItemStub = {
|
||||
template: '<div class="action-settings-wheel-item-stub"></div>',
|
||||
};
|
||||
@@ -339,6 +419,8 @@ const mountOrdersTable = (props = {}) => {
|
||||
EditableTableColumn: EditableTableColumnStub,
|
||||
ColorIndicator: ColorIndicatorStub,
|
||||
ActionSettingsWheelButton: ActionSettingsWheelButtonStub,
|
||||
InvoiceCollectionSelectionActionWheel: InvoiceCollectionSelectionActionWheelStub,
|
||||
BCheckbox: BCheckboxStub,
|
||||
ActionSettingsWheelItem: ActionSettingsWheelItemStub,
|
||||
OrderContentTable: OrderContentTableStub,
|
||||
PosDepartmentStepMobileAttachment: PosDepartmentStepMobileAttachmentStub,
|
||||
@@ -371,10 +453,26 @@ describe("OrdersTable", () => {
|
||||
invoiceQueueState.success.value = [];
|
||||
invoiceQueueState.queued.value = [];
|
||||
invoiceQueueState.log.value = [];
|
||||
setUseLargeTableHeaders(false);
|
||||
getSingleMock.mockReset();
|
||||
bulkActionPreviewMock.mockReset();
|
||||
bulkActionPreviewMock.mockResolvedValue({
|
||||
data: {
|
||||
data: {
|
||||
preview_id: "preview-1",
|
||||
confirmation_phrase: "Confirm",
|
||||
summary: {
|
||||
changed_count: 0,
|
||||
},
|
||||
blockers: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
bulkActionApplyMock.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setUseLargeTableHeaders(false);
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
@@ -612,4 +710,148 @@ describe("OrdersTable", () => {
|
||||
expect(getIndicatorForOrder(56631).attributes("data-color-class")).toBe("has-text-warning");
|
||||
expect(getIndicatorForOrder(56632).attributes("data-icon")).toBe("fas fa-circle");
|
||||
});
|
||||
|
||||
it("routes invoice collection selection controls through the action wheel", async () => {
|
||||
const wrapper = mountOrdersTable({
|
||||
invoiceView: true,
|
||||
allowSelectMultiple: true,
|
||||
autoExpandAll: false,
|
||||
orders: [
|
||||
createOrder({
|
||||
id: 56633,
|
||||
invoice_collection_id: 3001,
|
||||
invoice_collection: { id: 3001, closed_at: null, booked_invoice_id: null },
|
||||
}),
|
||||
createOrder({
|
||||
id: 56634,
|
||||
invoice_collection_id: 3001,
|
||||
invoice_collection: { id: 3001, closed_at: null, booked_invoice_id: null },
|
||||
}),
|
||||
createOrder({
|
||||
id: 56635,
|
||||
invoice_collection_id: 3002,
|
||||
invoice_collection: { id: 3002, closed_at: null, booked_invoice_id: null },
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
const wheel = () => wrapper.get(".invoice-collection-selection-action-wheel-stub");
|
||||
const firstCollectionSelector = () =>
|
||||
wrapper.get('[data-testid="pos-order-invoice-collection-selector-56633"] input');
|
||||
const secondSameCollectionSelector = () =>
|
||||
wrapper.get('[data-testid="pos-order-invoice-collection-selector-56634"] input');
|
||||
expect(wheel().attributes("data-total-count")).toBe("2");
|
||||
expect(wheel().attributes("data-selected-count")).toBe("0");
|
||||
expect(wheel().attributes("data-all-selected")).toBe("false");
|
||||
expect(wheel().attributes("data-all-expanded")).toBe("false");
|
||||
expect(wrapper.findAll("button").some((button) => /select|unselect|vælg|fravælg/i.test(button.text()))).toBe(false);
|
||||
expect(wrapper.get('[data-testid="pos-order-invoice-collection-selector-56633"]').classes()).toContain(
|
||||
"pos-order-invoice-collection-selector"
|
||||
);
|
||||
|
||||
await firstCollectionSelector().setValue(true);
|
||||
await flushPromises();
|
||||
|
||||
expect(wheel().attributes("data-selected-count")).toBe("1");
|
||||
expect(firstCollectionSelector().element.checked).toBe(true);
|
||||
expect(secondSameCollectionSelector().element.checked).toBe(true);
|
||||
|
||||
await secondSameCollectionSelector().setValue(false);
|
||||
await flushPromises();
|
||||
|
||||
expect(wheel().attributes("data-selected-count")).toBe("0");
|
||||
expect(firstCollectionSelector().element.checked).toBe(false);
|
||||
|
||||
await wrapper.get('[data-testid="selection-wheel-select-all"]').trigger("click");
|
||||
await flushPromises();
|
||||
|
||||
expect(wheel().attributes("data-selected-count")).toBe("2");
|
||||
expect(wheel().attributes("data-all-selected")).toBe("true");
|
||||
|
||||
await wrapper.get('[data-testid="selection-wheel-expand-all"]').trigger("click");
|
||||
await flushPromises();
|
||||
|
||||
expect(wheel().attributes("data-all-expanded")).toBe("true");
|
||||
|
||||
await wrapper.get('[data-testid="selection-wheel-clean-rules"]').trigger("click");
|
||||
await flushPromises();
|
||||
|
||||
expect(bulkActionPreviewMock).toHaveBeenCalledWith(
|
||||
INVOICE_COLLECTION_BULK_ACTIONS.CLEAN_CUSTOMER_RULES,
|
||||
[3001, 3002],
|
||||
{},
|
||||
"en"
|
||||
);
|
||||
});
|
||||
|
||||
it("uses compact table headers by default and restores large headers from the user setting", async () => {
|
||||
const wrapper = mountOrdersTable({
|
||||
invoiceView: true,
|
||||
orders: [createOrder()],
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(wrapper.find("th.pos-orders-table-header--compact").exists()).toBe(true);
|
||||
expect(wrapper.find("th.pos-orders-table-header--vehicles.pos-orders-table-header--compact").exists()).toBe(true);
|
||||
expect(wrapper.find("th .pleno-table-header-content").exists()).toBe(true);
|
||||
|
||||
setUseLargeTableHeaders(true);
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.find("th.pos-orders-table-header--compact").exists()).toBe(false);
|
||||
expect(wrapper.find("th.pos-orders-table-header--vehicles").exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("renders stacked desktop registration numbers in the compact vehicle column style", async () => {
|
||||
const wrapper = mountOrdersTable({
|
||||
invoiceView: true,
|
||||
orders: [
|
||||
createOrder({
|
||||
reg_1: "AB12345",
|
||||
reg_2: "CD67890",
|
||||
reg_3: "EF24680",
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
const vehicleCell = wrapper.get(".pos-order-vehicle-cell");
|
||||
const registrationLines = vehicleCell.findAll(".pos-order-vehicle-registration-line");
|
||||
|
||||
expect(registrationLines).toHaveLength(3);
|
||||
expect(registrationLines.map((line) => line.text())).toEqual(["AB12345", "CD67890", "EF24680"]);
|
||||
});
|
||||
|
||||
it("centers the desktop ID status indicator group in the ID cell", async () => {
|
||||
const wrapper = mountOrdersTable({
|
||||
invoiceView: true,
|
||||
orders: [createOrder({ id: 54518 })],
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
const idCell = wrapper.get(".pos-orders-table-id-cell");
|
||||
expect(idCell.classes()).toContain("is-narrow");
|
||||
expect(idCell.text()).toContain("54518");
|
||||
expect(idCell.find(".pos-orders-table-id-cell-content").exists()).toBe(true);
|
||||
expect(idCell.find(".color-indicator-stub").exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("uses row-level alignment classes for the desktop orders table row", async () => {
|
||||
const wrapper = mountOrdersTable({
|
||||
invoiceView: true,
|
||||
orders: [createOrder({ id: 54518 })],
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
|
||||
const row = wrapper.get(".pos-orders-table-row");
|
||||
expect(row.find(".pos-orders-table-id-cell").exists()).toBe(true);
|
||||
expect(row.find(".pos-order-vehicle-cell").exists()).toBe(true);
|
||||
expect(row.find(".pos-orders-table-actions-cell").exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// @vitest-environment jsdom
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
LARGE_TABLE_HEADERS_BODY_CLASS,
|
||||
LARGE_TABLE_HEADERS_STORAGE_KEY,
|
||||
setUseLargeTableHeaders,
|
||||
useLargeTableHeaders,
|
||||
} from "@/services/tableHeaderPreferences.js";
|
||||
|
||||
describe("table header preferences", () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage.removeItem(LARGE_TABLE_HEADERS_STORAGE_KEY);
|
||||
setUseLargeTableHeaders(false);
|
||||
});
|
||||
|
||||
it("uses compact table headers by default", () => {
|
||||
expect(useLargeTableHeaders.value).toBe(false);
|
||||
expect(document.body.classList.contains(LARGE_TABLE_HEADERS_BODY_CLASS)).toBe(false);
|
||||
});
|
||||
|
||||
it("toggles the document class for large table headers", () => {
|
||||
setUseLargeTableHeaders(true);
|
||||
|
||||
expect(window.localStorage.getItem(LARGE_TABLE_HEADERS_STORAGE_KEY)).toBe("true");
|
||||
expect(useLargeTableHeaders.value).toBe(true);
|
||||
expect(document.body.classList.contains(LARGE_TABLE_HEADERS_BODY_CLASS)).toBe(true);
|
||||
|
||||
setUseLargeTableHeaders(false);
|
||||
|
||||
expect(window.localStorage.getItem(LARGE_TABLE_HEADERS_STORAGE_KEY)).toBe("false");
|
||||
expect(useLargeTableHeaders.value).toBe(false);
|
||||
expect(document.body.classList.contains(LARGE_TABLE_HEADERS_BODY_CLASS)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user