309 lines
8.4 KiB
Vue
309 lines
8.4 KiB
Vue
<script setup>
|
|
import { computed, onMounted, ref, useSlots, watch } from "vue";
|
|
import WhiteBox from "@/components/displays/boxes/WhiteBox.vue";
|
|
import PosOrderItemsCurrent from "@/components/displays/department/pos/PosOrderItemsCurrent.vue";
|
|
import PosSelectedCustomer from "@/components/displays/department/pos/PosSelectedCustomer.vue";
|
|
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
|
import { getDepartmentName, getDepartments } from "@/components/pagination/departmentTabs.vue";
|
|
|
|
const props = defineProps({
|
|
mode: {
|
|
type: String,
|
|
default: "summary",
|
|
validator: (value) => ["summary", "catalog"].includes(value),
|
|
},
|
|
rootTestId: {
|
|
type: String,
|
|
default: null,
|
|
},
|
|
orderId: {
|
|
type: [Number, String],
|
|
default: null,
|
|
},
|
|
orderItems: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
loadOrderItems: {
|
|
type: Function,
|
|
default: null,
|
|
},
|
|
editPossible: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
showCatalogPane: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
showCustomerRail: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
summaryOrder: {
|
|
type: Object,
|
|
default: null,
|
|
},
|
|
});
|
|
|
|
const slots = useSlots();
|
|
const fetchedSummaryOrder = ref(null);
|
|
const departmentTabsLoaded = ref(false);
|
|
|
|
const parsePositiveInteger = (value) => {
|
|
const parsed = Number.parseInt(value, 10);
|
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
};
|
|
|
|
const normalizedOrderId = computed(() => parsePositiveInteger(props.orderId));
|
|
const resolvedSummaryOrder = computed(() => props.summaryOrder || fetchedSummaryOrder.value || null);
|
|
const shouldShowRail = computed(() => props.showCustomerRail && !props.showCatalogPane);
|
|
const shouldShowInlineSummary = computed(() => !props.showCatalogPane);
|
|
|
|
const resolvedDepartmentLabel = computed(() => {
|
|
const departmentId = parsePositiveInteger(resolvedSummaryOrder.value?.department_id);
|
|
if (!departmentId) {
|
|
return "";
|
|
}
|
|
|
|
return getDepartmentName(departmentId) || String(departmentId);
|
|
});
|
|
|
|
const resolvedCreatedAt = computed(() => {
|
|
return resolvedSummaryOrder.value?.created_at || "";
|
|
});
|
|
|
|
const hasInlineSummaryTags = computed(() => {
|
|
return shouldShowInlineSummary.value && Boolean(resolvedDepartmentLabel.value || resolvedCreatedAt.value);
|
|
});
|
|
|
|
const loadDepartmentTabs = async () => {
|
|
if (departmentTabsLoaded.value) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await getDepartments();
|
|
} catch (error) {
|
|
console.error("Unable to load POS department labels", error);
|
|
} finally {
|
|
departmentTabsLoaded.value = true;
|
|
}
|
|
};
|
|
|
|
const loadSummaryOrder = async (candidateOrderId = normalizedOrderId.value) => {
|
|
if (props.summaryOrder) {
|
|
fetchedSummaryOrder.value = null;
|
|
return;
|
|
}
|
|
|
|
if (!candidateOrderId) {
|
|
fetchedSummaryOrder.value = null;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
fetchedSummaryOrder.value = await SessionUser.objects.orders.get.single(candidateOrderId);
|
|
} catch (error) {
|
|
console.error("Unable to load POS order summary", error);
|
|
fetchedSummaryOrder.value = null;
|
|
}
|
|
};
|
|
|
|
onMounted(async () => {
|
|
await loadDepartmentTabs();
|
|
await loadSummaryOrder();
|
|
});
|
|
|
|
watch(() => props.summaryOrder, async () => {
|
|
await loadSummaryOrder();
|
|
}, { deep: true });
|
|
|
|
watch(normalizedOrderId, async (nextOrderId, previousOrderId) => {
|
|
if (nextOrderId === previousOrderId) {
|
|
return;
|
|
}
|
|
|
|
await loadSummaryOrder(nextOrderId);
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="pos-shell pos-order-detail"
|
|
:class="{ 'pos-order-detail--editing': showCatalogPane }"
|
|
:data-testid="rootTestId || undefined"
|
|
>
|
|
<div class="pos-main pos-order-detail__main" data-testid="pos-order-main">
|
|
<div v-if="$slots.leading" class="pos-order-detail__leading">
|
|
<slot name="leading"></slot>
|
|
</div>
|
|
|
|
<div
|
|
class="pos-order-detail__workspace"
|
|
data-testid="pos-order-workspace"
|
|
:class="{ 'pos-order-detail__workspace--editing': showCatalogPane }"
|
|
>
|
|
<div
|
|
v-if="showCatalogPane"
|
|
class="pos-order-detail__products"
|
|
data-testid="pos-order-add-items-panel"
|
|
>
|
|
<slot name="catalog"></slot>
|
|
</div>
|
|
|
|
<div
|
|
class="pos-order-detail__order-column"
|
|
:class="{ 'pos-order-detail__order-column--editing': showCatalogPane }"
|
|
>
|
|
<WhiteBox class="pos-card pos-card--flush pos-order-detail__order" data-testid="pos-order-card">
|
|
<PosOrderItemsCurrent
|
|
:orderItems="orderItems"
|
|
:orderId="orderId"
|
|
:loadOrderItems="loadOrderItems"
|
|
:editPossible="editPossible"
|
|
:stackMetadata="showCatalogPane"
|
|
variant="order-detail"
|
|
>
|
|
<template #default>
|
|
<div v-if="$slots.status" class="is-hidden-touch pos-order-detail__status-group">
|
|
<slot name="status"></slot>
|
|
</div>
|
|
</template>
|
|
|
|
<template #headingcenter>
|
|
<div class="pos-inline-tags is-hidden-touch" v-if="hasInlineSummaryTags">
|
|
<span
|
|
v-if="resolvedDepartmentLabel"
|
|
class="pos-inline-tag"
|
|
data-testid="pos-order-inline-department"
|
|
>
|
|
<span class="pos-inline-tag__label">{{ $t('pos.order.department') }}:</span>
|
|
<span class="pos-inline-tag__value">{{ resolvedDepartmentLabel }}</span>
|
|
</span>
|
|
<span
|
|
v-if="resolvedCreatedAt"
|
|
class="pos-inline-tag"
|
|
data-testid="pos-order-inline-created"
|
|
>
|
|
<span class="pos-inline-tag__label">{{ $t('common.created') }}:</span>
|
|
<span class="pos-inline-tag__value">{{ resolvedCreatedAt }}</span>
|
|
</span>
|
|
</div>
|
|
<slot name="headingcenter"></slot>
|
|
</template>
|
|
|
|
<template v-if="$slots.headerActions" #headerActions>
|
|
<slot name="headerActions"></slot>
|
|
</template>
|
|
|
|
<template v-if="$slots.beforeItems" #beforeItems>
|
|
<slot name="beforeItems"></slot>
|
|
</template>
|
|
|
|
<template v-if="$slots.attachments" #attachments>
|
|
<slot name="attachments"></slot>
|
|
</template>
|
|
|
|
<template v-if="$slots.other" #other>
|
|
<slot name="other"></slot>
|
|
</template>
|
|
|
|
<template v-if="$slots.details" #details>
|
|
<slot name="details"></slot>
|
|
</template>
|
|
|
|
<template #bottom-order-items>
|
|
<slot name="bottom-order-items"></slot>
|
|
</template>
|
|
</PosOrderItemsCurrent>
|
|
</WhiteBox>
|
|
|
|
<div
|
|
v-if="$slots.orderFooter"
|
|
class="pos-order-detail__order-footer"
|
|
>
|
|
<slot name="orderFooter"></slot>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="shouldShowRail" class="pos-rail pos-rail--sticky" data-testid="pos-order-rail">
|
|
<WhiteBox class="pos-card pos-card--flush is-hidden-touch" data-testid="pos-customer-card">
|
|
<PosSelectedCustomer variant="order-detail" />
|
|
</WhiteBox>
|
|
|
|
<div v-if="$slots['rail-actions']" class="pos-order-detail__rail-actions">
|
|
<slot name="rail-actions"></slot>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.pos-order-detail__main {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.pos-order-detail__leading,
|
|
.pos-order-detail__rail-actions {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.pos-order-detail__workspace {
|
|
display: grid;
|
|
gap: 1rem;
|
|
align-items: start;
|
|
}
|
|
|
|
.pos-order-detail__order {
|
|
min-width: 0;
|
|
width: 100%;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.pos-order-detail__order-column {
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
align-self: start;
|
|
}
|
|
|
|
.pos-order-detail__products {
|
|
min-width: 0;
|
|
width: 100%;
|
|
}
|
|
|
|
.pos-order-detail__order-footer {
|
|
min-width: 0;
|
|
}
|
|
|
|
.pos-order-detail--editing {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
}
|
|
|
|
.pos-order-detail__workspace--editing {
|
|
grid-template-columns: minmax(0, 1fr) minmax(20rem, 23.75rem);
|
|
}
|
|
|
|
.pos-order-detail__order-column--editing {
|
|
grid-column: 2;
|
|
}
|
|
|
|
@media screen and (max-width: 1023px) {
|
|
.pos-order-detail__workspace--editing {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.pos-order-detail__order-column--editing {
|
|
grid-column: auto;
|
|
}
|
|
}
|
|
</style>
|