Init
This commit is contained in:
127
resources/js/Components/OrderFulfillment.vue
Normal file
127
resources/js/Components/OrderFulfillment.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<div class="space-y-3">
|
||||
<p class="text-[11px] text-muted leading-relaxed">
|
||||
saga compensations · external signals · long-running waits · retry policies
|
||||
</p>
|
||||
<div class="flex items-center gap-3 text-[10px] text-muted/60">
|
||||
<span class="font-mono">app/Temporal/OrderFulfillment/</span>
|
||||
<a href="https://docs.temporal.io/develop/php/message-passing#signals" target="_blank" class="text-accent/50 hover:text-accent transition-colors">signals</a>
|
||||
<a href="https://github.com/temporalio/samples-php" target="_blank" class="text-accent/50 hover:text-accent transition-colors">saga pattern</a>
|
||||
<a href="https://docs.temporal.io/develop/php/failure-detection" target="_blank" class="text-accent/50 hover:text-accent transition-colors">failure detection</a>
|
||||
</div>
|
||||
|
||||
<SimulationControls v-model="simulation" />
|
||||
|
||||
<template v-if="localOrders.length > 0">
|
||||
<table class="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr class="text-left text-muted border-b border-border">
|
||||
<th class="py-1 font-normal">Order</th>
|
||||
<th class="font-normal">Customer</th>
|
||||
<th class="font-normal text-right pr-4">Amount</th>
|
||||
<th class="font-normal pl-4">Status</th>
|
||||
<th class="font-normal text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="order in localOrders" :key="order.id"
|
||||
class="border-t border-border/30 text-value hover:bg-section/30 transition-colors">
|
||||
<td class="py-1.5 font-medium">{{ order.order_number }}</td>
|
||||
<td class="text-muted">{{ order.customer_name }}</td>
|
||||
<td class="text-right pr-4">${{ Number(order.total_amount).toFixed(2) }}</td>
|
||||
<td class="pl-4">
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<span class="w-1 h-1 rounded-full" :class="orderStatusDot(order.displayStatus || order.status)"></span>
|
||||
{{ order.displayStatus || order.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="inline-flex gap-1">
|
||||
<button @click="processOrder(order)"
|
||||
class="px-1.5 py-0.5 text-[10px] text-accent border border-accent/20 hover:bg-accent/10 transition-all rounded-sm cursor-pointer">
|
||||
process
|
||||
</button>
|
||||
<button @click="shipOrder(order)"
|
||||
class="px-1.5 py-0.5 text-[10px] text-value border border-border hover:bg-border/30 transition-all rounded-sm cursor-pointer">
|
||||
ship
|
||||
</button>
|
||||
<button @click="checkStatus(order)"
|
||||
class="px-1.5 py-0.5 text-[10px] text-muted border border-border/50 hover:bg-border/20 transition-all rounded-sm cursor-pointer">
|
||||
status
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
<p v-else class="text-[11px] text-muted">
|
||||
No orders found. Run <code class="bg-deep px-1 py-0.5 rounded-sm text-label">TemporalDemoSeeder</code> first.
|
||||
</p>
|
||||
|
||||
<div v-if="resultJson" class="bg-deep border border-border rounded-sm overflow-hidden">
|
||||
<div class="px-2 py-1 border-b border-border/50 text-[10px] text-muted uppercase tracking-wider">Response</div>
|
||||
<pre class="p-2 text-[10px] text-label leading-relaxed overflow-x-auto">{{ resultJson }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { usePolling } from '../composables/usePolling.js';
|
||||
import SimulationControls from './SimulationControls.vue';
|
||||
|
||||
const props = defineProps({
|
||||
orders: { type: Array, default: () => [] },
|
||||
});
|
||||
|
||||
const simulation = ref({
|
||||
failureRate: 0,
|
||||
latencyMs: 0,
|
||||
rateLimiting: { enabled: false, hitChance: 25, retryAfterMs: 2000 },
|
||||
});
|
||||
|
||||
const localOrders = reactive([...props.orders]);
|
||||
const resultJson = ref('');
|
||||
|
||||
let currentPollingOrder = null;
|
||||
const { start: startPolling, stop: stopPolling } = usePolling(async () => {
|
||||
if (!currentPollingOrder) return true;
|
||||
const { data } = await axios.get(`/temporal/order/${currentPollingOrder.id}/status`);
|
||||
if (data.error && !data.status) {
|
||||
currentPollingOrder.displayStatus = 'completed';
|
||||
resultJson.value = JSON.stringify(data, null, 2);
|
||||
return true;
|
||||
}
|
||||
currentPollingOrder.displayStatus = data.status || currentPollingOrder.displayStatus;
|
||||
resultJson.value = JSON.stringify(data, null, 2);
|
||||
return ['completed', 'failed'].includes(data.status);
|
||||
});
|
||||
|
||||
async function processOrder(order) {
|
||||
const { data } = await axios.post(`/temporal/order/${order.id}/process`, { simulation: simulation.value });
|
||||
resultJson.value = JSON.stringify(data, null, 2);
|
||||
stopPolling();
|
||||
currentPollingOrder = order;
|
||||
startPolling();
|
||||
}
|
||||
|
||||
async function shipOrder(order) {
|
||||
const { data } = await axios.post(`/temporal/order/${order.id}/ship`);
|
||||
resultJson.value = JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
async function checkStatus(order) {
|
||||
const { data } = await axios.get(`/temporal/order/${order.id}/status`);
|
||||
order.displayStatus = data.status || order.displayStatus;
|
||||
resultJson.value = JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
function orderStatusDot(status) {
|
||||
if (status === 'completed' || status === 'delivered') return 'bg-accent';
|
||||
if (status === 'failed' || status === 'cancelled') return 'bg-danger';
|
||||
if (status === 'processing' || status === 'shipping') return 'bg-warn';
|
||||
return 'bg-muted';
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user