Add Falukant region and transport management features
- Implemented new endpoints in AdminController for managing Falukant regions, including fetching, updating, and deleting region distances. - Enhanced the FalukantService with methods for retrieving region distances and handling upsert operations. - Updated the router to expose new routes for region management and transport creation. - Introduced a transport management interface in the frontend, allowing users to create and manage transports between branches. - Added localization for new transport-related terms and improved the vehicle management interface to include transport options. - Enhanced the database initialization logic to support new region and transport models.
This commit is contained in:
@@ -24,6 +24,18 @@
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="form-label">
|
||||
{{ $t('falukant.branch.transport.mode') }}
|
||||
<select v-model="mode" class="form-control">
|
||||
<option value="buy">
|
||||
{{ $t('falukant.branch.transport.modeBuy') }}
|
||||
</option>
|
||||
<option value="build">
|
||||
{{ $t('falukant.branch.transport.modeBuild') }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="form-label">
|
||||
{{ $t('falukant.branch.transport.quantity') }}
|
||||
<input
|
||||
@@ -39,6 +51,11 @@
|
||||
<strong>{{ formatCost(totalCost) }}</strong>
|
||||
</p>
|
||||
|
||||
<p v-if="selectedType" class="buildtime">
|
||||
{{ $t('falukant.branch.transport.buildTime') }}:
|
||||
<span>{{ formattedBuildTime }}</span>
|
||||
</p>
|
||||
|
||||
<p v-if="totalCost > money" class="warning">
|
||||
{{ $t('falukant.branch.transport.notEnoughMoney') }}
|
||||
</p>
|
||||
@@ -64,6 +81,7 @@ export default {
|
||||
vehicleTypes: [],
|
||||
selectedTypeId: null,
|
||||
quantity: 1,
|
||||
mode: 'buy',
|
||||
money: 0,
|
||||
loaded: false,
|
||||
};
|
||||
@@ -73,7 +91,10 @@ export default {
|
||||
return [
|
||||
{ text: this.$t('Cancel'), action: this.close },
|
||||
{
|
||||
text: this.$t('falukant.branch.transport.buy'),
|
||||
text:
|
||||
this.mode === 'build'
|
||||
? this.$t('falukant.branch.transport.buildAction')
|
||||
: this.$t('falukant.branch.transport.buyAction'),
|
||||
action: this.onConfirm,
|
||||
disabled: !this.canBuy,
|
||||
},
|
||||
@@ -85,7 +106,11 @@ export default {
|
||||
totalCost() {
|
||||
if (!this.selectedType) return 0;
|
||||
const q = Math.max(1, this.quantity || 0);
|
||||
return this.selectedType.cost * q;
|
||||
const unit =
|
||||
this.mode === 'build'
|
||||
? Math.round(this.selectedType.cost * 0.75)
|
||||
: this.selectedType.cost;
|
||||
return unit * q;
|
||||
},
|
||||
canBuy() {
|
||||
return (
|
||||
@@ -99,11 +124,23 @@ export default {
|
||||
formattedMoney() {
|
||||
return this.formatCost(this.money);
|
||||
},
|
||||
formattedBuildTime() {
|
||||
if (!this.selectedType || !this.selectedType.buildTimeMinutes) return '-';
|
||||
const total = this.selectedType.buildTimeMinutes;
|
||||
const h = Math.floor(total / 60);
|
||||
const m = total % 60;
|
||||
if (h > 0 && m > 0) {
|
||||
return `${h} h ${m} min`;
|
||||
}
|
||||
if (h > 0) return `${h} h`;
|
||||
return `${m} min`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async open() {
|
||||
this.loaded = false;
|
||||
this.quantity = 1;
|
||||
this.mode = 'buy';
|
||||
await Promise.all([this.loadVehicleTypes(), this.loadMoney()]);
|
||||
if (this.vehicleTypes.length && !this.selectedTypeId) {
|
||||
this.selectedTypeId = this.vehicleTypes[0].id;
|
||||
@@ -139,6 +176,7 @@ export default {
|
||||
vehicleTypeId: this.selectedTypeId,
|
||||
quantity: this.quantity,
|
||||
regionId: this.regionId,
|
||||
mode: this.mode,
|
||||
});
|
||||
this.$emit('bought');
|
||||
this.close();
|
||||
|
||||
@@ -11,15 +11,18 @@
|
||||
<div class="create-branch-form">
|
||||
<div class="map-wrapper">
|
||||
<!-- linke Spalte: Karte + Regionen + Dev-Draw -->
|
||||
<div class="map-container">
|
||||
<div
|
||||
class="map-container"
|
||||
@mousedown="onMouseDown"
|
||||
@mousemove="onMouseMove"
|
||||
@mouseup="onMouseUp"
|
||||
@mouseleave="onMouseUp"
|
||||
>
|
||||
<img
|
||||
ref="mapImage"
|
||||
src="/images/falukant/map.png"
|
||||
class="map"
|
||||
@mousedown="onMouseDown"
|
||||
@mousemove="onMouseMove"
|
||||
@mouseup="onMouseUp"
|
||||
@mouseleave="onMouseUp"
|
||||
@load="onMapLoaded"
|
||||
@dragstart.prevent
|
||||
/>
|
||||
<div
|
||||
@@ -27,12 +30,7 @@
|
||||
:key="city.name"
|
||||
class="city-region"
|
||||
:class="city.branches.length > 0 ? 'has-branch' : 'clickable'"
|
||||
:style="{
|
||||
top: city.map.y + 'px',
|
||||
left: city.map.x + 'px',
|
||||
width: city.map.w + 'px',
|
||||
height: city.map.h + 'px'
|
||||
}"
|
||||
:style="cityRegionStyle(city.map)"
|
||||
@click="city.branches.length === 0 && onCityClick(city)"
|
||||
:title="city.name"
|
||||
></div>
|
||||
@@ -101,7 +99,9 @@
|
||||
startX: null,
|
||||
startY: null,
|
||||
currentX: 0,
|
||||
currentY: 0,
|
||||
currentY: 0,
|
||||
mapWidth: 0,
|
||||
mapHeight: 0,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -130,6 +130,11 @@
|
||||
},
|
||||
|
||||
methods: {
|
||||
onMapLoaded() {
|
||||
const bounds = this.$refs.mapImage.getBoundingClientRect();
|
||||
this.mapWidth = bounds.width;
|
||||
this.mapHeight = bounds.height;
|
||||
},
|
||||
open() {
|
||||
this.$refs.dialog.open();
|
||||
},
|
||||
@@ -149,6 +154,12 @@
|
||||
this.$emit('create-branch');
|
||||
this.close();
|
||||
} catch (e) {
|
||||
if (e?.response?.status === 412 && e?.response?.data?.error === 'insufficientFunds') {
|
||||
alert(this.$t('falukant.branch.actions.insufficientFunds'));
|
||||
} else {
|
||||
console.error('Error creating branch', e);
|
||||
alert(this.$t('falukant.error.generic') || 'Fehler beim Erstellen der Niederlassung.');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -208,6 +219,21 @@
|
||||
height: Math.round(height),
|
||||
};
|
||||
},
|
||||
cityRegionStyle(map) {
|
||||
if (!map || !this.mapWidth || !this.mapHeight) return {};
|
||||
const toPxX = (v) => (v >= 0 && v <= 1 ? v * this.mapWidth : v);
|
||||
const toPxY = (v) => (v >= 0 && v <= 1 ? v * this.mapHeight : v);
|
||||
const x = toPxX(map.x);
|
||||
const y = toPxY(map.y);
|
||||
const w = toPxX(map.w);
|
||||
const h = toPxY(map.h);
|
||||
return {
|
||||
top: `${y}px`,
|
||||
left: `${x}px`,
|
||||
width: `${w}px`,
|
||||
height: `${h}px`,
|
||||
};
|
||||
},
|
||||
|
||||
async loadCities() {
|
||||
const { data } = await apiClient.get('/api/falukant/cities');
|
||||
|
||||
Reference in New Issue
Block a user