improve the bill of production
This commit is contained in:
@@ -41,36 +41,26 @@
|
||||
</td>
|
||||
|
||||
<!-- Effective material cost (after RRR) -->
|
||||
<td class="px-4 py-3 text-sm font-mono text-gray-300"
|
||||
<td class="px-4 py-3 text-right text-sm font-mono text-gray-300"
|
||||
:title="result.effectiveMaterialCost > 0 ? result.effectiveMaterialCost.toLocaleString() : undefined">
|
||||
{{ formatSilver(result.effectiveMaterialCost) }}
|
||||
</td>
|
||||
|
||||
<!-- +15% markup -->
|
||||
<td class="px-4 py-3 text-sm font-mono text-blue-300"
|
||||
<!-- +15% markup (no focus) -->
|
||||
<td class="px-4 py-3 text-right text-sm font-mono text-blue-300"
|
||||
:title="result.effectiveMaterialCost > 0 ? Math.round(result.effectiveMaterialCost * 1.15).toLocaleString() : undefined">
|
||||
{{ result.effectiveMaterialCost > 0 ? formatSilver(Math.round(result.effectiveMaterialCost * 1.15)) : '—' }}
|
||||
</td>
|
||||
|
||||
<!-- +30% markup -->
|
||||
<td class="px-4 py-3 text-sm font-mono text-emerald-300"
|
||||
<!-- +30% markup (no focus) -->
|
||||
<td class="px-4 py-3 text-right text-sm font-mono text-emerald-300"
|
||||
:title="result.effectiveMaterialCost > 0 ? Math.round(result.effectiveMaterialCost * 1.30).toLocaleString() : undefined">
|
||||
{{ result.effectiveMaterialCost > 0 ? formatSilver(Math.round(result.effectiveMaterialCost * 1.30)) : '—' }}
|
||||
</td>
|
||||
|
||||
<!-- Price age -->
|
||||
<td class="px-4 py-3">
|
||||
<span
|
||||
class="inline-block w-2.5 h-2.5 rounded-full"
|
||||
:class="ageDotClass(result)"
|
||||
:title="ageDotTitle(result)"
|
||||
/>
|
||||
</td>
|
||||
|
||||
<!-- Add to bill -->
|
||||
<!-- Add to bill (no focus) -->
|
||||
<td class="px-3 py-3 text-right whitespace-nowrap" @click.stop>
|
||||
<!-- Inline qty form -->
|
||||
<div v-if="addingToBill" class="inline-flex items-center gap-1">
|
||||
<div v-if="addingMode === 'nofocus'" class="inline-flex items-center gap-1">
|
||||
<input
|
||||
v-focus
|
||||
type="number"
|
||||
@@ -83,31 +73,90 @@
|
||||
<button class="text-amber-500 hover:text-amber-300 text-xs leading-none" @click="confirmBill">✓</button>
|
||||
<button class="text-gray-500 hover:text-gray-300 text-xs leading-none" @click="cancelBill">✕</button>
|
||||
</div>
|
||||
<!-- In-order badge + remove -->
|
||||
<div v-else-if="isInOrder" class="inline-flex items-center gap-1">
|
||||
<div v-else-if="isInOrderNoFocus" class="inline-flex items-center gap-1">
|
||||
<button
|
||||
class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-amber-600/20 border border-amber-600/40 text-amber-400 text-xs font-mono hover:border-amber-500 transition-colors"
|
||||
@click="startBill"
|
||||
>×{{ currentQty }}</button>
|
||||
@click="startBill('nofocus')"
|
||||
>×{{ currentQtyNoFocus }}</button>
|
||||
<button
|
||||
class="text-gray-600 hover:text-red-400 transition-colors text-xs leading-none"
|
||||
title="Remove from order"
|
||||
@click="remove(result.recipe.outputItemId)"
|
||||
@click="remove(result.recipe.outputItemId, false)"
|
||||
>✕</button>
|
||||
</div>
|
||||
<!-- Add button -->
|
||||
<button
|
||||
v-else
|
||||
class="w-6 h-6 rounded-full bg-gray-700 hover:bg-amber-600/30 hover:text-amber-400 text-gray-400 text-sm leading-none transition-colors inline-flex items-center justify-center"
|
||||
title="Add to Bill of Production"
|
||||
@click="startBill"
|
||||
title="Add to Bill of Production (no focus)"
|
||||
@click="startBill('nofocus')"
|
||||
>+</button>
|
||||
</td>
|
||||
|
||||
<!-- Cost (with focus) -->
|
||||
<td class="px-4 py-3 text-right text-sm font-mono text-violet-300 border-l border-gray-700/40"
|
||||
:title="result.effectiveMaterialCostFocus > 0 ? result.effectiveMaterialCostFocus.toLocaleString() : undefined">
|
||||
{{ formatSilver(result.effectiveMaterialCostFocus) }}
|
||||
</td>
|
||||
|
||||
<!-- +15% markup (with focus) -->
|
||||
<td class="px-4 py-3 text-right text-sm font-mono text-blue-300/70"
|
||||
:title="result.effectiveMaterialCostFocus > 0 ? Math.round(result.effectiveMaterialCostFocus * 1.15).toLocaleString() : undefined">
|
||||
{{ result.effectiveMaterialCostFocus > 0 ? formatSilver(Math.round(result.effectiveMaterialCostFocus * 1.15)) : '—' }}
|
||||
</td>
|
||||
|
||||
<!-- +30% markup (with focus) -->
|
||||
<td class="px-4 py-3 text-right text-sm font-mono text-emerald-300/70"
|
||||
:title="result.effectiveMaterialCostFocus > 0 ? Math.round(result.effectiveMaterialCostFocus * 1.30).toLocaleString() : undefined">
|
||||
{{ result.effectiveMaterialCostFocus > 0 ? formatSilver(Math.round(result.effectiveMaterialCostFocus * 1.30)) : '—' }}
|
||||
</td>
|
||||
|
||||
<!-- Add to bill (with focus) -->
|
||||
<td class="px-3 py-3 text-right whitespace-nowrap" @click.stop>
|
||||
<div v-if="addingMode === 'focus'" class="inline-flex items-center gap-1">
|
||||
<input
|
||||
v-focus
|
||||
type="number"
|
||||
min="1"
|
||||
v-model.number="billQty"
|
||||
class="w-12 bg-gray-900 border border-violet-500 rounded px-1.5 py-0.5 text-center text-xs font-mono text-gray-200 focus:outline-none"
|
||||
@keydown.enter="confirmBill"
|
||||
@keydown.escape="cancelBill"
|
||||
/>
|
||||
<button class="text-violet-400 hover:text-violet-200 text-xs leading-none" @click="confirmBill">✓</button>
|
||||
<button class="text-gray-500 hover:text-gray-300 text-xs leading-none" @click="cancelBill">✕</button>
|
||||
</div>
|
||||
<div v-else-if="isInOrderFocus" class="inline-flex items-center gap-1">
|
||||
<button
|
||||
class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-violet-600/20 border border-violet-600/40 text-violet-400 text-xs font-mono hover:border-violet-500 transition-colors"
|
||||
@click="startBill('focus')"
|
||||
>×{{ currentQtyFocus }}</button>
|
||||
<button
|
||||
class="text-gray-600 hover:text-red-400 transition-colors text-xs leading-none"
|
||||
title="Remove from order"
|
||||
@click="remove(result.recipe.outputItemId, true)"
|
||||
>✕</button>
|
||||
</div>
|
||||
<button
|
||||
v-else
|
||||
class="w-6 h-6 rounded-full bg-gray-700 hover:bg-violet-600/30 hover:text-violet-400 text-gray-400 text-sm leading-none transition-colors inline-flex items-center justify-center"
|
||||
title="Add to Bill of Production (with focus)"
|
||||
@click="startBill('focus')"
|
||||
>+</button>
|
||||
</td>
|
||||
|
||||
<!-- Status -->
|
||||
<td class="px-4 py-3 text-center">
|
||||
<span
|
||||
class="inline-block w-2.5 h-2.5 rounded-full"
|
||||
:class="ageDotClass(result)"
|
||||
:title="ageDotTitle(result)"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Expanded detail row -->
|
||||
<tr v-if="expanded" class="border-b border-gray-700/50 bg-gray-900/60">
|
||||
<td colspan="9" class="px-6 py-4">
|
||||
<td colspan="13" class="px-6 py-4">
|
||||
<div class="flex gap-8">
|
||||
|
||||
<!-- Ingredients breakdown -->
|
||||
@@ -184,13 +233,13 @@
|
||||
<span class="text-gray-400">Raw Materials</span>
|
||||
<span class="text-gray-200">{{ formatSilver(result.materialCost) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-4">
|
||||
<span class="text-gray-400">RRR ({{ result.rrr.toFixed(1) }}%)</span>
|
||||
<span class="text-green-400">−{{ formatSilver(result.materialCost - result.effectiveMaterialCost) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-4 border-t border-gray-700/50 pt-1.5">
|
||||
<span class="text-gray-300 font-semibold">Craft Cost</span>
|
||||
<span class="font-semibold text-gray-100">{{ formatSilver(result.effectiveMaterialCost) }}</span>
|
||||
<span class="text-gray-400">RRR ({{ result.rrr.toFixed(1) }}%)</span>
|
||||
<span class="text-gray-200">{{ formatSilver(result.effectiveMaterialCost) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-4">
|
||||
<span class="text-violet-400/80">RRR + Focus ({{ result.rrrFocus.toFixed(1) }}%)</span>
|
||||
<span class="text-violet-300">{{ formatSilver(result.effectiveMaterialCostFocus) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -241,24 +290,30 @@ const inputValue = ref('')
|
||||
|
||||
// ─── Bill of Production ───────────────────────────────────────────────────────
|
||||
|
||||
const addingToBill = ref(false)
|
||||
const billQty = ref(1)
|
||||
const addingMode = ref<null | 'nofocus' | 'focus'>(null)
|
||||
const billQty = ref(1)
|
||||
|
||||
const isInOrder = computed(() => inOrder(props.result.recipe.outputItemId))
|
||||
const currentQty = computed(() => getQty(props.result.recipe.outputItemId))
|
||||
const isInOrderNoFocus = computed(() => inOrder(props.result.recipe.outputItemId, false))
|
||||
const currentQtyNoFocus = computed(() => getQty(props.result.recipe.outputItemId, false))
|
||||
const isInOrderFocus = computed(() => inOrder(props.result.recipe.outputItemId, true))
|
||||
const currentQtyFocus = computed(() => getQty(props.result.recipe.outputItemId, true))
|
||||
|
||||
function startBill() {
|
||||
billQty.value = isInOrder.value ? currentQty.value : 1
|
||||
addingToBill.value = true
|
||||
function startBill(mode: 'nofocus' | 'focus') {
|
||||
const already = mode === 'nofocus' ? isInOrderNoFocus.value : isInOrderFocus.value
|
||||
const qty = mode === 'nofocus' ? currentQtyNoFocus.value : currentQtyFocus.value
|
||||
billQty.value = already ? qty : 1
|
||||
addingMode.value = mode
|
||||
}
|
||||
|
||||
function confirmBill() {
|
||||
if (billQty.value > 0) upsert(props.result.recipe, billQty.value)
|
||||
addingToBill.value = false
|
||||
if (billQty.value > 0 && addingMode.value) {
|
||||
upsert(props.result.recipe, billQty.value, addingMode.value === 'focus')
|
||||
}
|
||||
addingMode.value = null
|
||||
}
|
||||
|
||||
function cancelBill() {
|
||||
addingToBill.value = false
|
||||
addingMode.value = null
|
||||
}
|
||||
|
||||
// ─── Price age display ────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user