Add blog post untitled helper, use product title helper in order views, and surface API validation messages on product save/delete.
208 lines
7.7 KiB
Vue
208 lines
7.7 KiB
Vue
<template>
|
|
<div>
|
|
<el-card v-if="lines.length" shadow="never">
|
|
<template #header>
|
|
<span>Order cart</span>
|
|
</template>
|
|
|
|
<div class="cms-table-scroll">
|
|
<el-table :data="lines" stripe class="order-cart-table">
|
|
<el-table-column label="Product" min-width="140" show-overflow-tooltip>
|
|
<template #default="{ row }">
|
|
<router-link
|
|
:to="{ name: ROUTE_NAMES.ProductDetail, params: { id: row.productId } }"
|
|
class="order-line-link"
|
|
target="_blank"
|
|
rel="noopener"
|
|
>
|
|
{{ getProductTitle(row.productTitle) }}
|
|
</router-link>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="Variant" min-width="140" show-overflow-tooltip>
|
|
<template #default="{ row }">
|
|
<router-link
|
|
:to="{
|
|
name: ROUTE_NAMES.ProductVariantDetail,
|
|
params: { productId: row.productId, variantId: row.variantId }
|
|
}"
|
|
class="order-line-link"
|
|
target="_blank"
|
|
rel="noopener"
|
|
>
|
|
{{ row.variantTitle }}
|
|
</router-link>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column prop="qty" label="Qty" width="70" />
|
|
|
|
<el-table-column label="Unit price" width="120">
|
|
<template #default="{ row }">
|
|
{{ formatFiatPrice(row.unitPriceFiat, order.fiatCurrency) }}
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="Line total" width="120">
|
|
<template #default="{ row }">
|
|
{{ formatFiatPrice(row.lineSubtotalFiat, order.fiatCurrency) }}
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="Delivery" width="150">
|
|
<template #default="{ row }">
|
|
{{ formatDeliveryMode(row.deliveryMode) }}
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="Fulfillment" width="140">
|
|
<template #default="{ row }">
|
|
<el-tag :type="resolveLineFulfillmentTag(row).type" size="small">
|
|
{{ resolveLineFulfillmentTag(row).label }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column width="100" fixed="right" align="right" class-name="order-cart-action-col">
|
|
<template #default="{ row }">
|
|
<span v-if="order.failureReason" class="secondary-text">—</span>
|
|
|
|
<template v-else>
|
|
<el-button
|
|
v-if="row.deliveryMode === DeliveryMode.Auto"
|
|
link
|
|
type="primary"
|
|
title="View delivery"
|
|
@click="openAutoDelivery(row)"
|
|
>
|
|
View
|
|
</el-button>
|
|
|
|
<el-button
|
|
v-else-if="!isManualLineFulfilled(row)"
|
|
link
|
|
type="primary"
|
|
title="Mark as fulfilled"
|
|
:loading="fulfillingLineId === row.id"
|
|
@click="markAsFulfilled(row)"
|
|
>
|
|
Fulfill
|
|
</el-button>
|
|
|
|
<span v-else class="secondary-text">—</span>
|
|
</template>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
</el-card>
|
|
|
|
<order-line-auto-fulfillment-modal v-model="autoModalVisible" :line="selectedLine" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref, type PropType } from 'vue';
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
import { ROUTE_NAMES } from '@/consts/routeNames';
|
|
import { useOrdersStore } from '@/stores/orders';
|
|
import { ManualLineFulfillmentStatus } from '@/types/order/ManualLineFulfillmentStatus';
|
|
import type { OrderLine } from '@/types/order/OrderLine';
|
|
import type { OrderExtended } from '@/types/order/OrderExtended';
|
|
import { DeliveryMode } from '@/types/product/DeliveryMode';
|
|
import { formatDeliveryMode } from '@/utils/product/formatDeliveryMode';
|
|
import { formatFiatPrice } from '@/utils/formatFiatPrice';
|
|
import { getProductTitle } from '@/utils/product/getProductTitle';
|
|
import { resolveAxiosErrorMessage } from '@/utils/resolveAxiosErrorMessage';
|
|
|
|
const props = defineProps({
|
|
order: {
|
|
type: Object as PropType<OrderExtended>,
|
|
required: true
|
|
}
|
|
});
|
|
|
|
const ordersStore = useOrdersStore();
|
|
|
|
const { fulfillManualLine } = ordersStore;
|
|
|
|
const selectedLine = ref<OrderLine | null>(null);
|
|
const autoModalVisible = ref(false);
|
|
const fulfillingLineId = ref<string | null>(null);
|
|
|
|
const lines = computed(() => props.order.lines ?? []);
|
|
|
|
const isManualLineFulfilled = (line: OrderLine): boolean =>
|
|
line.manualFulfillment?.status === ManualLineFulfillmentStatus.Fulfilled;
|
|
|
|
const openAutoDelivery = (line: OrderLine): void => {
|
|
selectedLine.value = line;
|
|
autoModalVisible.value = true;
|
|
};
|
|
|
|
const markAsFulfilled = async (line: OrderLine): Promise<void> => {
|
|
try {
|
|
await ElMessageBox.confirm(
|
|
`Mark "${getProductTitle(line.productTitle)}" (${line.variantTitle}) as fulfilled? This cannot be undone.`,
|
|
'Mark as fulfilled',
|
|
{
|
|
type: 'warning',
|
|
confirmButtonText: 'Mark fulfilled',
|
|
cancelButtonText: 'Cancel'
|
|
}
|
|
);
|
|
} catch {
|
|
return;
|
|
}
|
|
|
|
fulfillingLineId.value = line.id;
|
|
|
|
try {
|
|
await fulfillManualLine(props.order.id, line.id);
|
|
|
|
ElMessage.success('Line marked as fulfilled.');
|
|
} catch (error) {
|
|
const message = resolveAxiosErrorMessage(error, 'Could not mark line as fulfilled.');
|
|
|
|
ElMessage.error(message);
|
|
} finally {
|
|
fulfillingLineId.value = null;
|
|
}
|
|
};
|
|
|
|
const resolveLineFulfillmentTag = (line: OrderLine) => {
|
|
if (props.order.failureReason) {
|
|
return { label: 'Failed', type: 'danger' as const };
|
|
}
|
|
|
|
if (line.deliveryMode === DeliveryMode.Auto) {
|
|
return line.autoFulfillmentItems.length > 0
|
|
? { label: 'Auto-delivered', type: 'success' as const }
|
|
: { label: 'Pending', type: 'warning' as const };
|
|
}
|
|
|
|
if (isManualLineFulfilled(line)) {
|
|
return { label: 'Fulfilled', type: 'success' as const };
|
|
}
|
|
|
|
return { label: 'Pending', type: 'warning' as const };
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.order-line-link {
|
|
color: var(--el-color-primary);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.order-line-link:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.order-cart-table :deep(.order-cart-action-col .cell) {
|
|
padding-left: 8px;
|
|
padding-right: 8px;
|
|
}
|
|
</style>
|