Add product price retrieval feature in cities

- Implemented a new endpoint in FalukantController to fetch product prices in various cities based on product ID and current price.
- Developed the corresponding service method in FalukantService to calculate and return prices, considering user knowledge and city branches.
- Updated frontend components (RevenueSection and SaleSection) to display better prices for products, including loading logic and UI enhancements for price visibility.
- Added styling for price indicators based on branch types to improve user experience.
This commit is contained in:
Torsten Schulz (local)
2025-12-01 16:42:54 +01:00
parent 8c8841705c
commit adc7132404
5 changed files with 260 additions and 0 deletions

View File

@@ -16,6 +16,7 @@
<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>
@@ -26,6 +27,16 @@
<td>{{ calculateProductRevenue(product).perMinute }}</td>
<td>{{ calculateProductProfit(product).absolute }}</td>
<td>{{ calculateProductProfit(product).perMinute }}</td>
<td>
<div v-if="product.betterPrices && product.betterPrices.length > 0" class="price-cities">
<span v-for="city in product.betterPrices" :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>
@@ -34,6 +45,8 @@
</template>
<script>
import apiClient from '@/utils/axios.js';
export default {
name: "RevenueSection",
props: {
@@ -44,6 +57,7 @@
data() {
return {
isRevenueTableOpen: false,
loadingPrices: new Set(),
};
},
computed: {
@@ -56,9 +70,64 @@
}, null);
},
},
async mounted() {
if (this.isRevenueTableOpen) {
await this.loadPricesForAllProducts();
}
},
watch: {
isRevenueTableOpen(newVal) {
if (newVal) {
this.loadPricesForAllProducts();
}
},
products: {
handler() {
if (this.isRevenueTableOpen) {
this.loadPricesForAllProducts();
}
},
deep: true
}
},
methods: {
toggleRevenueTable() {
this.isRevenueTableOpen = !this.isRevenueTableOpen;
if (this.isRevenueTableOpen) {
this.loadPricesForAllProducts();
}
},
async loadPricesForAllProducts() {
for (const product of this.products) {
if (this.loadingPrices.has(product.id)) continue;
this.loadingPrices.add(product.id);
try {
const currentPrice = parseFloat(this.calculateProductRevenue(product).absolute);
const { data } = await apiClient.get('/api/falukant/products/prices-in-cities', {
params: {
productId: product.id,
currentPrice: currentPrice
}
});
this.$set(product, 'betterPrices', data || []);
} catch (error) {
console.error(`Error loading prices for product ${product.id}:`, error);
this.$set(product, 'betterPrices', []);
} finally {
this.loadingPrices.delete(product.id);
}
}
},
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);
},
},
};
@@ -94,5 +163,32 @@
.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>