113 lines
3.2 KiB
Vue
113 lines
3.2 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, reactive, ref } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
import type { CreateOrderItemDto } from '@gold-bank/shared-types';
|
|
import { api, type ProductSummary } from '../api/client';
|
|
import { useSessionStore } from '../stores/session';
|
|
|
|
const session = useSessionStore();
|
|
const router = useRouter();
|
|
|
|
const products = ref<ProductSummary[]>([]);
|
|
const items = reactive<CreateOrderItemDto[]>([
|
|
{ productVariantId: '', quantity: 1, wagePercent: 7, discountPercent: 0, flatRialCharge: 0 },
|
|
]);
|
|
const submitting = ref(false);
|
|
const error = ref<string | null>(null);
|
|
|
|
onMounted(async () => {
|
|
products.value = await api.listProducts();
|
|
});
|
|
|
|
function addItem() {
|
|
items.push({ productVariantId: '', quantity: 1, wagePercent: 7, discountPercent: 0, flatRialCharge: 0 });
|
|
}
|
|
|
|
function removeItem(index: number) {
|
|
items.splice(index, 1);
|
|
}
|
|
|
|
async function submit() {
|
|
if (!session.selectedCustomerId) {
|
|
error.value = 'Select a customer from the dashboard first.';
|
|
return;
|
|
}
|
|
submitting.value = true;
|
|
error.value = null;
|
|
try {
|
|
const order = await api.createOrder({
|
|
customerId: session.selectedCustomerId,
|
|
items: items.map((i) => ({ ...i })),
|
|
});
|
|
router.push({ name: 'order-detail', params: { id: order.id } });
|
|
} catch (e) {
|
|
error.value = (e as Error).message;
|
|
} finally {
|
|
submitting.value = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<section>
|
|
<h1>New order</h1>
|
|
<p v-if="!session.selectedCustomerId" class="error">
|
|
No customer selected — go back to the dashboard and pick one.
|
|
</p>
|
|
<p v-else>Customer: {{ session.selectedCustomerId }}</p>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Variant</th>
|
|
<th>Qty</th>
|
|
<th>Wage %</th>
|
|
<th>Discount %</th>
|
|
<th>Flat Rial</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(item, idx) in items" :key="idx">
|
|
<td>
|
|
<select v-model="item.productVariantId">
|
|
<option value="" disabled>Select…</option>
|
|
<template v-for="p in products" :key="p.id">
|
|
<option v-for="v in p.variants" :key="v.id" :value="v.id">
|
|
{{ p.title }} — {{ v.title }} ({{ v.rashWeightGrams }}g)
|
|
</option>
|
|
</template>
|
|
</select>
|
|
</td>
|
|
<td><input v-model.number="item.quantity" type="number" min="1" /></td>
|
|
<td><input v-model.number="item.wagePercent" type="number" step="0.1" /></td>
|
|
<td><input v-model.number="item.discountPercent" type="number" step="0.1" /></td>
|
|
<td><input v-model.number="item.flatRialCharge" type="number" step="1000" /></td>
|
|
<td><button @click="removeItem(idx)">Remove</button></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<button @click="addItem">Add item</button>
|
|
<br /><br />
|
|
<button :disabled="submitting" @click="submit">Place order</button>
|
|
<p v-if="error" class="error">{{ error }}</p>
|
|
</section>
|
|
</template>
|
|
|
|
<style scoped>
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-bottom: 1rem;
|
|
}
|
|
th,
|
|
td {
|
|
text-align: left;
|
|
padding: 0.4rem;
|
|
}
|
|
.error {
|
|
color: #b00020;
|
|
}
|
|
</style>
|