- Introduced console logs to track the number of better prices received for each product and the state of the betterPricesMap after updates. - Enhanced the getBetterPrices method with logging to provide visibility into the prices being returned, improving traceability during price evaluations. - These changes aim to facilitate debugging and provide clearer insights into the price handling process within the RevenueSection component.
223 lines
7.4 KiB
Vue
223 lines
7.4 KiB
Vue
<template>
|
|
<div class="revenue-section">
|
|
<h3>
|
|
<button @click="toggleRevenueTable">
|
|
{{ $t('falukant.branch.revenue.title') }}
|
|
{{ isRevenueTableOpen ? '▲' : '▼' }}
|
|
</button>
|
|
</h3>
|
|
<div v-if="isRevenueTableOpen" class="revenue-table">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>{{ $t('falukant.branch.revenue.product') }}</th>
|
|
<th>{{ $t('falukant.branch.revenue.knowledge') }}</th>
|
|
<th>{{ $t('falukant.branch.revenue.absolute') }}</th>
|
|
<th>{{ $t('falukant.branch.revenue.perMinute') }}</th>
|
|
<th>{{ $t('falukant.branch.revenue.profitAbsolute') }}</th>
|
|
<th>{{ $t('falukant.branch.revenue.profitPerMinute') }}</th>
|
|
<th>Bessere Preise</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="product in products" :key="product.id" :class="{ highlight: product.id === productWithMaxRevenuePerMinute?.id }">
|
|
<td>{{ $t(`falukant.product.${product.labelTr}`) }}</td>
|
|
<td>{{ product.knowledges && product.knowledges[0] ? product.knowledges[0].knowledge : 0 }}</td>
|
|
<td>{{ calculateProductRevenue(product).absolute }}</td>
|
|
<td>{{ calculateProductRevenue(product).perMinute }}</td>
|
|
<td>{{ calculateProductProfit(product).absolute }}</td>
|
|
<td>{{ calculateProductProfit(product).perMinute }}</td>
|
|
<td>
|
|
<div v-if="getBetterPrices(product.id) && getBetterPrices(product.id).length > 0" class="price-cities">
|
|
<span v-for="city in getBetterPrices(product.id)" :key="city.regionId"
|
|
:class="['city-price', getCityPriceClass(city.branchType)]"
|
|
:title="`${city.regionName}: ${formatPrice(city.price)}`">
|
|
{{ city.regionName }}
|
|
</span>
|
|
</div>
|
|
<span v-else class="no-better-prices">—</span>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import apiClient from '@/utils/axios.js';
|
|
|
|
export default {
|
|
name: "RevenueSection",
|
|
props: {
|
|
products: { type: Array, required: true },
|
|
calculateProductRevenue: { type: Function, required: true },
|
|
calculateProductProfit: { type: Function, required: true },
|
|
currentRegionId: { type: Number, default: null },
|
|
},
|
|
data() {
|
|
return {
|
|
isRevenueTableOpen: false,
|
|
loadingPrices: new Set(),
|
|
betterPricesMap: {}, // Map von productId zu betterPrices Array
|
|
};
|
|
},
|
|
computed: {
|
|
productWithMaxRevenuePerMinute() {
|
|
if (!this.products || this.products.length === 0) return null;
|
|
return this.products.reduce((maxProduct, currentProduct) => {
|
|
const currentRevenue = parseFloat(this.calculateProductRevenue(currentProduct).perMinute);
|
|
const maxRevenue = maxProduct ? parseFloat(this.calculateProductRevenue(maxProduct).perMinute) : 0;
|
|
return currentRevenue > maxRevenue ? currentProduct : maxProduct;
|
|
}, null);
|
|
},
|
|
},
|
|
async mounted() {
|
|
if (this.isRevenueTableOpen) {
|
|
await this.loadPricesForAllProducts();
|
|
}
|
|
},
|
|
watch: {
|
|
isRevenueTableOpen(newVal) {
|
|
if (newVal && this.currentRegionId !== null) {
|
|
this.loadPricesForAllProducts();
|
|
}
|
|
},
|
|
products: {
|
|
handler(newProducts, oldProducts) {
|
|
// Leere betterPricesMap wenn sich die Produktliste ändert
|
|
if (oldProducts && oldProducts.length > 0) {
|
|
this.betterPricesMap = {};
|
|
}
|
|
if (this.isRevenueTableOpen && this.currentRegionId !== null) {
|
|
this.loadPricesForAllProducts();
|
|
}
|
|
},
|
|
deep: true
|
|
},
|
|
currentRegionId(newVal, oldVal) {
|
|
// Leere betterPricesMap wenn sich die Region ändert
|
|
if (oldVal !== null && oldVal !== undefined) {
|
|
this.betterPricesMap = {};
|
|
}
|
|
if (this.isRevenueTableOpen && newVal !== null) {
|
|
this.loadPricesForAllProducts();
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
toggleRevenueTable() {
|
|
this.isRevenueTableOpen = !this.isRevenueTableOpen;
|
|
if (this.isRevenueTableOpen) {
|
|
this.loadPricesForAllProducts();
|
|
}
|
|
},
|
|
async loadPricesForAllProducts() {
|
|
if (this.currentRegionId === null || this.currentRegionId === undefined) {
|
|
return;
|
|
}
|
|
for (const product of this.products) {
|
|
if (this.loadingPrices.has(product.id)) continue;
|
|
this.loadingPrices.add(product.id);
|
|
try {
|
|
// Verwende den gerundeten Preis aus calculateProductRevenue (wie gewollt)
|
|
const currentPrice = parseFloat(this.calculateProductRevenue(product).absolute);
|
|
|
|
const { data } = await apiClient.get('/api/falukant/products/prices-in-cities', {
|
|
params: {
|
|
productId: product.id,
|
|
currentPrice: currentPrice,
|
|
currentRegionId: this.currentRegionId
|
|
}
|
|
});
|
|
console.log(`[RevenueSection] Product ${product.id} (${product.labelTr}): received ${data?.length || 0} better prices:`, data);
|
|
// Speichere betterPrices in einem separaten Map, nicht auf dem product Objekt
|
|
this.$set(this.betterPricesMap, product.id, data || []);
|
|
console.log(`[RevenueSection] betterPricesMap after update:`, JSON.parse(JSON.stringify(this.betterPricesMap)));
|
|
} catch (error) {
|
|
console.error(`Error loading prices for product ${product.id}:`, error);
|
|
this.$set(this.betterPricesMap, product.id, []);
|
|
} finally {
|
|
this.loadingPrices.delete(product.id);
|
|
}
|
|
}
|
|
},
|
|
getBetterPrices(productId) {
|
|
const prices = this.betterPricesMap[productId] || [];
|
|
console.log(`[RevenueSection] getBetterPrices(${productId}): returning`, prices);
|
|
return prices;
|
|
},
|
|
getCityPriceClass(branchType) {
|
|
if (branchType === 'store') return 'city-price-green';
|
|
if (branchType === 'production') return 'city-price-orange';
|
|
return 'city-price-red';
|
|
},
|
|
formatPrice(price) {
|
|
return new Intl.NumberFormat(navigator.language, {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
}).format(price);
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.revenue-section {
|
|
border: 1px solid #ccc;
|
|
margin: 10px 0;
|
|
border-radius: 4px;
|
|
padding: 10px;
|
|
}
|
|
.revenue-section button {
|
|
background: none;
|
|
border: none;
|
|
color: #007bff;
|
|
cursor: pointer;
|
|
font-size: 1em;
|
|
text-decoration: underline;
|
|
}
|
|
.revenue-table {
|
|
margin-top: 10px;
|
|
overflow-x: auto;
|
|
}
|
|
.revenue-table table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
th, td {
|
|
padding: 8px;
|
|
border: 1px solid #ddd;
|
|
}
|
|
.highlight {
|
|
background-color: #dfffd6;
|
|
}
|
|
.price-cities {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.3em;
|
|
}
|
|
.city-price {
|
|
padding: 0.2em 0.4em;
|
|
border-radius: 3px;
|
|
font-size: 0.85em;
|
|
cursor: help;
|
|
}
|
|
.city-price-green {
|
|
background-color: #90EE90;
|
|
color: #000;
|
|
}
|
|
.city-price-orange {
|
|
background-color: #FFA500;
|
|
color: #000;
|
|
}
|
|
.city-price-red {
|
|
background-color: #FF6B6B;
|
|
color: #fff;
|
|
}
|
|
.no-better-prices {
|
|
color: #999;
|
|
font-style: italic;
|
|
}
|
|
</style>
|
|
|