init
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="cms-page-header mb-24">
|
||||
<h3 class="m-0">Products</h3>
|
||||
<el-button type="primary" @click="createTypeDialogVisible = true"> New product </el-button>
|
||||
</div>
|
||||
|
||||
<div class="cms-table-scroll">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="products"
|
||||
stripe
|
||||
empty-text="No products yet"
|
||||
class="clickable-table"
|
||||
@row-click="row => router.push({ name: ROUTE_NAMES.ProductDetail, params: { id: row.id } })"
|
||||
>
|
||||
<el-table-column prop="title" label="Title" min-width="160" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ getProductTitle(row.title) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="Status" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.isDraft" type="info" size="small">Draft</el-tag>
|
||||
<el-tag v-else type="success" size="small">Live</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="Delivery" width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatDeliveryMode(row.deliveryMode) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="Categories" min-width="160" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ formatProductCategories(row.categories) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="Stock (available)" width="150">
|
||||
<template #default="{ row }">
|
||||
{{ getProductTotalStock(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Updated" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.updatedAt) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="100" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click.stop="router.push({ name: ROUTE_NAMES.ProductDetail, params: { id: row.id } })"
|
||||
>
|
||||
Open
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<cms-list-pagination v-model:page="page" v-model:limit="limit" :total="total" @change="loadProducts" />
|
||||
|
||||
<el-dialog v-model="createTypeDialogVisible" title="New product" width="400px" destroy-on-close>
|
||||
<p class="mb-16">Choose delivery mode. This cannot be changed later.</p>
|
||||
|
||||
<el-radio-group v-model="createDeliveryMode" class="create-delivery-mode-radio-group">
|
||||
<el-radio :value="DeliveryMode.Auto">Auto-delivered</el-radio>
|
||||
<el-radio :value="DeliveryMode.Manual">Manually fulfilled</el-radio>
|
||||
</el-radio-group>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="createTypeDialogVisible = false">Cancel</el-button>
|
||||
<el-button type="primary" :loading="createDraftLoading" @click="handleNewProductDraft">
|
||||
Create draft
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ROUTE_NAMES } from '@/consts/routeNames';
|
||||
import { useProductsStore } from '@/stores/products';
|
||||
import type { Category } from '@/types/product/Category';
|
||||
import type { ProductWithVariantsExtended } from '@/types/product/ProductWithVariantsExtended';
|
||||
import { DeliveryMode } from '@/types/product/DeliveryMode';
|
||||
import { HttpStatusCode, isAxiosError } from 'axios';
|
||||
import { formatDate } from '@/utils/formatDate';
|
||||
import { formatDeliveryMode } from '@/utils/product/formatDeliveryMode';
|
||||
import { getProductTitle } from '@/utils/product/getProductTitle';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onBeforeMount, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const productsStore = useProductsStore();
|
||||
const { products } = storeToRefs(productsStore);
|
||||
|
||||
const loading = ref(false);
|
||||
const createDraftLoading = ref(false);
|
||||
const createTypeDialogVisible = ref(false);
|
||||
const createDeliveryMode = ref<DeliveryMode>(DeliveryMode.Auto);
|
||||
const page = ref(1);
|
||||
const limit = ref(20);
|
||||
const total = ref(0);
|
||||
|
||||
onBeforeMount(() => {
|
||||
loadProducts();
|
||||
});
|
||||
|
||||
const loadProducts = async (): Promise<void> => {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const data = await productsStore.fetchProducts({
|
||||
page: page.value,
|
||||
limit: limit.value
|
||||
});
|
||||
|
||||
products.value = data.items;
|
||||
total.value = data.total;
|
||||
page.value = data.page;
|
||||
limit.value = data.limit;
|
||||
} catch (e) {
|
||||
if (!isAxiosError(e) || e.response?.status !== HttpStatusCode.Unauthorized) {
|
||||
ElMessage.error('Failed to load products');
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleNewProductDraft = async (): Promise<void> => {
|
||||
createDraftLoading.value = true;
|
||||
|
||||
try {
|
||||
const created = await productsStore.createDraftProduct(createDeliveryMode.value);
|
||||
createTypeDialogVisible.value = false;
|
||||
ElMessage.success('Draft created — add details and publish when ready');
|
||||
router.push({ name: ROUTE_NAMES.ProductDetail, params: { id: created.id } });
|
||||
} catch {
|
||||
ElMessage.error('Could not create draft product');
|
||||
} finally {
|
||||
createDraftLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const getProductTotalStock = (product: ProductWithVariantsExtended): number =>
|
||||
(product.variants ?? []).reduce((sum, variant) => sum + variant.stockAvailable, 0);
|
||||
|
||||
const formatProductCategories = (categories: Category[] | undefined): string => {
|
||||
if (!categories?.length) {
|
||||
return '—';
|
||||
}
|
||||
|
||||
return [...categories]
|
||||
.sort((a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name))
|
||||
.map(category => category.name)
|
||||
.join(', ');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.create-delivery-mode-radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user