Files
yourpart3/frontend/src/views/falukant/BranchView.vue

1184 lines
46 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="contenthidden">
<StatusBar ref="statusBar" />
<div class="contentscroll">
<h2>{{ $t('falukant.branch.title') }}</h2>
<BranchSelection
:branches="branches"
:selectedBranch="selectedBranch"
@branchSelected="onBranchSelected"
@createBranch="createBranch"
@upgradeBranch="upgradeBranch"
ref="branchSelection"
/>
<!-- Tab-Navigation für Inhalte der ausgewählten Niederlassung -->
<SimpleTabs
v-if="selectedBranch"
v-model="activeTab"
:tabs="tabs"
/>
<!-- Tab-Inhalte -->
<div v-if="selectedBranch" class="branch-tab-content">
<!-- Direktor -->
<div v-if="activeTab === 'director'" class="branch-tab-pane">
<DirectorInfo
:branchId="selectedBranch.id"
:vehicles="vehicles"
:branches="branches"
ref="directorInfo"
@transportCreated="handleTransportCreated"
/>
</div>
<!-- Inventar / Verkauf -->
<div v-else-if="activeTab === 'inventory'" class="branch-tab-pane">
<SaleSection
:branchId="selectedBranch.id"
:vehicles="vehicles"
:branches="branches"
ref="saleSection"
@transportCreated="handleTransportCreated"
/>
</div>
<!-- Produktion + Produkt-Erträge -->
<div v-else-if="activeTab === 'production'" class="branch-tab-pane">
<ProductionSection
:branchId="selectedBranch.id"
:products="products"
ref="productionSection"
/>
<RevenueSection
:products="products"
:calculateProductRevenue="calculateProductRevenue"
:calculateProductProfit="calculateProductProfit"
:currentRegionId="selectedBranch?.regionId"
ref="revenueSection"
/>
</div>
<!-- Lager -->
<div v-else-if="activeTab === 'storage'" class="branch-tab-pane">
<StorageSection :branchId="selectedBranch.id" ref="storageSection" />
</div>
<!-- Transportmittel -->
<div v-else-if="activeTab === 'transport'" class="branch-tab-pane">
<p>{{ $t('falukant.branch.transport.placeholder') }}</p>
<div class="vehicle-action-buttons">
<button @click="openBuyVehicleDialog">
{{ $t('falukant.branch.transport.buy') }}
</button>
<button
v-if="repairableVehiclesCount > 0"
@click="openRepairAllVehiclesDialog"
class="repair-all-button"
>
{{ $t('falukant.branch.transport.repairAll', { count: repairableVehiclesCount, cost: formatMoney(repairAllCost) }) }}
</button>
</div>
<div class="vehicle-overview" v-if="vehicles && vehicles.length">
<table class="vehicle-table">
<thead>
<tr>
<th>{{ $t('falukant.branch.transport.table.type') }}</th>
<th>{{ $t('falukant.branch.transport.table.capacity') }}</th>
<th>{{ $t('falukant.branch.transport.table.condition') }}</th>
<th>{{ $t('falukant.branch.transport.table.mode') }}</th>
<th>{{ $t('falukant.branch.transport.table.speed') }}</th>
<th>{{ $t('falukant.branch.transport.table.availableFrom') }}</th>
<th>{{ $t('falukant.branch.transport.table.status') }}</th>
<th>{{ $t('falukant.branch.transport.table.actions') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="v in vehicles" :key="v.id">
<td>
{{ $t(`falukant.branch.vehicles.${v.type.tr}`) }}
</td>
<td>{{ v.type.capacity }}</td>
<td>{{ conditionLabel(v.condition) }}</td>
<td>{{ transportModeLabel(v.type.transportMode) }}</td>
<td>{{ speedLabel(v.type.speed) }}</td>
<td>{{ formatDateTime(v.availableFrom) }}</td>
<td>
<span v-if="v.status === 'travelling'">
{{ $t('falukant.branch.transport.status.inUse') }}
</span>
<span v-else-if="v.status === 'building'">
{{ $t('falukant.branch.transport.status.building') }}
</span>
<span v-else>
{{ $t('falukant.branch.transport.status.free') }}
</span>
</td>
<td>
<div class="vehicle-actions">
<button
v-if="v.status === 'available'"
@click="openSendVehicleDialog(v.id)"
:title="$t('falukant.branch.transport.sendSingle')"
>
{{ $t('falukant.branch.transport.send') }}
</button>
<button
v-if="v.status === 'available' && v.condition < 100"
@click="openRepairVehicleDialog(v)"
:title="$t('falukant.branch.transport.repairTooltip')"
class="repair-button"
>
{{ $t('falukant.branch.transport.repairWithCost', { cost: formatMoney(calculateRepairCost(v)) }) }}
</button>
<span v-if="v.status !== 'available'" class="no-action"></span>
</div>
</td>
</tr>
</tbody>
</table>
<!-- Buttons zum Versenden aller freien Fahrzeuge eines Typs -->
<div class="send-all-vehicles" v-if="freeVehiclesByType && Object.keys(freeVehiclesByType).length > 0">
<h4>{{ $t('falukant.branch.transport.sendAllFree') }}</h4>
<div class="send-all-buttons">
<button
v-for="(vehicleList, vehicleTypeId) in freeVehiclesByType"
:key="vehicleTypeId"
@click="openSendAllVehiclesDialog(parseInt(vehicleTypeId), vehicleList)"
>
{{ $t('falukant.branch.transport.sendAllOfType', {
type: $t(`falukant.branch.vehicles.${vehicleList[0].type.tr}`),
count: vehicleList.length
}) }}
</button>
</div>
</div>
</div>
<p v-else class="no-vehicles">
{{ $t('falukant.branch.transport.noVehicles') }}
</p>
</div>
</div>
<BuyVehicleDialog
v-if="selectedBranch"
ref="buyVehicleDialog"
:region-id="selectedBranch?.regionId"
@bought="handleVehiclesBought"
/>
<!-- Dialog zum Versenden von Fahrzeugen -->
<div v-if="sendVehicleDialog.show" class="modal-overlay" @click.self="closeSendVehicleDialog">
<div class="modal-content">
<h3>{{ $t('falukant.branch.transport.sendVehiclesTitle') }}</h3>
<div v-if="!sendVehicleDialog.success" class="send-vehicle-form">
<label>
{{ $t('falukant.branch.transport.selectTargetBranch') }}
<select v-model.number="sendVehicleDialog.targetBranchId">
<option :value="null" disabled>{{ $t('falukant.branch.transport.selectTarget') }}</option>
<option v-for="tb in targetBranchOptions()" :key="tb.id" :value="tb.id">
{{ tb.label }}
</option>
</select>
</label>
<div class="modal-buttons">
<button @click="sendVehicles" :disabled="!sendVehicleDialog.targetBranchId">
{{ $t('falukant.branch.transport.send') }}
</button>
<button @click="closeSendVehicleDialog">
{{ $t('falukant.branch.transport.cancel') }}
</button>
</div>
</div>
<div v-else class="send-vehicle-success">
<p>{{ $t('falukant.branch.transport.sendSuccess') }}</p>
<div class="modal-buttons">
<button @click="closeSendVehicleDialog">
{{ $t('falukant.branch.transport.close') }}
</button>
</div>
</div>
</div>
</div>
<!-- Dialog zum Reparieren aller Fahrzeuge -->
<div v-if="repairAllVehiclesDialog.show" class="modal-overlay" @click.self="closeRepairAllVehiclesDialog">
<div class="modal-content">
<h3>{{ $t('falukant.branch.transport.repairAllTitle') }}</h3>
<div class="repair-all-form">
<p>
{{ $t('falukant.branch.transport.repairAllDescription', {
count: repairAllVehiclesDialog.vehicleIds.length,
cost: formatMoney(repairAllVehiclesDialog.totalCost)
}) }}
</p>
<p class="repair-all-discount">
{{ $t('falukant.branch.transport.repairAllDiscount') }}
</p>
<div class="modal-buttons">
<button @click="repairAllVehicles" class="repair-confirm-button">
{{ $t('falukant.branch.transport.repairAllConfirm') }}
</button>
<button @click="closeRepairAllVehiclesDialog">
{{ $t('falukant.branch.transport.cancel') }}
</button>
</div>
</div>
</div>
</div>
<!-- Dialog zum Reparieren von Fahrzeugen -->
<div v-if="repairVehicleDialog.show" class="modal-overlay" @click.self="closeRepairVehicleDialog">
<div class="modal-content">
<h3>{{ $t('falukant.branch.transport.repairVehicleTitle') }}</h3>
<div class="repair-vehicle-form" v-if="repairVehicleDialog.vehicle">
<div class="repair-info">
<p>
<strong>{{ $t('falukant.branch.transport.repairVehicleType') }}:</strong>
{{ $t(`falukant.branch.vehicles.${repairVehicleDialog.vehicle.type.tr}`) }}
</p>
<p>
<strong>{{ $t('falukant.branch.transport.repairCurrentCondition') }}:</strong>
{{ repairVehicleDialog.vehicle.condition }}%
</p>
<p>
<strong>{{ $t('falukant.branch.transport.repairCost') }}:</strong>
{{ formatMoney(repairVehicleDialog.repairCost) }}
</p>
<p>
<strong>{{ $t('falukant.branch.transport.repairBuildTime') }}:</strong>
{{ formatBuildTime(repairVehicleDialog.buildTimeMinutes) }}
</p>
</div>
<div class="modal-buttons">
<button @click="repairVehicle" class="repair-confirm-button">
{{ $t('falukant.branch.transport.repairConfirm') }}
</button>
<button @click="closeRepairVehicleDialog">
{{ $t('falukant.branch.transport.cancel') }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import StatusBar from '@/components/falukant/StatusBar.vue';
import BranchSelection from '@/components/falukant/BranchSelection.vue';
import SimpleTabs from '@/components/SimpleTabs.vue';
import DirectorInfo from '@/components/falukant/DirectorInfo.vue';
import SaleSection from '@/components/falukant/SaleSection.vue';
import ProductionSection from '@/components/falukant/ProductionSection.vue';
import StorageSection from '@/components/falukant/StorageSection.vue';
import RevenueSection from '@/components/falukant/RevenueSection.vue';
import BuyVehicleDialog from '@/dialogues/falukant/BuyVehicleDialog.vue';
import apiClient from '@/utils/axios.js';
import { mapState } from 'vuex';
export default {
name: "BranchView",
components: {
StatusBar,
BranchSelection,
SimpleTabs,
DirectorInfo,
SaleSection,
ProductionSection,
StorageSection,
RevenueSection,
BuyVehicleDialog,
},
watch: {
// Wenn sich der Daemon-Socket ändert (z.B. nach Login/Reconnect),
// Listener sauber entfernen/neu registrieren.
daemonSocket(newSocket, oldSocket) {
if (oldSocket) {
oldSocket.removeEventListener('message', this.handleDaemonMessage);
}
if (newSocket) {
newSocket.addEventListener('message', this.handleDaemonMessage);
}
}
},
data() {
return {
branches: [],
selectedBranch: null,
products: [],
vehicles: [],
activeTab: 'production',
productPricesCache: {}, // Cache für regionale Preise: { productId: price }
tabs: [
{ value: 'production', label: 'falukant.branch.tabs.production' },
{ value: 'inventory', label: 'falukant.branch.tabs.inventory' },
{ value: 'director', label: 'falukant.branch.tabs.director' },
{ value: 'storage', label: 'falukant.branch.tabs.storage' },
{ value: 'transport', label: 'falukant.branch.tabs.transport' },
],
sendVehicleDialog: {
show: false,
vehicleId: null,
vehicleIds: null,
vehicleTypeId: null,
targetBranchId: null,
success: false,
},
repairVehicleDialog: {
show: false,
vehicle: null,
repairCost: null,
buildTimeMinutes: null,
},
repairAllVehiclesDialog: {
show: false,
vehicleIds: [],
totalCost: null,
},
};
},
computed: {
...mapState(['socket', 'daemonSocket']),
freeVehiclesByType() {
const grouped = {};
for (const v of this.vehicles || []) {
if (v.status !== 'available' || !v.type || !v.type.id) continue;
const typeId = v.type.id;
if (!grouped[typeId]) {
grouped[typeId] = [];
}
grouped[typeId].push(v);
}
return grouped;
},
repairableVehicles() {
return (this.vehicles || []).filter(v =>
v.status === 'available' && v.condition < 100
);
},
repairableVehiclesCount() {
return this.repairableVehicles.length;
},
repairAllCost() {
const totalCost = this.repairableVehicles.reduce((sum, v) => {
return sum + this.calculateRepairCost(v);
}, 0);
// 10% Rabatt für Reparatur aller Fahrzeuge
return Math.round(totalCost * 0.9);
},
},
async mounted() {
await this.loadBranches();
const branchId = this.$route.params.branchId;
await this.loadProducts();
if (branchId) {
this.selectedBranch = this.branches.find(
b => b.id === parseInt(branchId, 10)
) || null;
} else {
this.selectMainBranch();
}
// Live-Socket-Events (Daemon WS)
if (this.daemonSocket) {
this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
}
// Live-Socket-Events (Backend Socket.io)
if (this.socket) {
this.socket.on('falukantUpdateStatus', (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data }));
this.socket.on('falukantBranchUpdate', (data) => this.handleEvent({ event: 'falukantBranchUpdate', ...data }));
this.socket.on('transport_arrived', (data) => this.handleEvent({ event: 'transport_arrived', ...data }));
this.socket.on('inventory_updated', (data) => this.handleEvent({ event: 'inventory_updated', ...data }));
}
},
beforeUnmount() {
// Daemon WebSocket: Listener entfernen (der Socket selbst wird beim Logout geschlossen)
if (this.daemonSocket) {
this.daemonSocket.removeEventListener('message', this.handleDaemonMessage);
}
if (this.socket) {
this.socket.off('falukantUpdateStatus');
this.socket.off('falukantBranchUpdate');
this.socket.off('transport_arrived');
this.socket.off('inventory_updated');
}
},
methods: {
async loadBranches() {
try {
const result = await apiClient.get('/api/falukant/branches');
this.branches = result.data.map(branch => ({
id: branch.id,
regionId: branch.regionId,
cityName: branch.region.name,
type: this.$t(`falukant.branch.types.${branch.branchType.labelTr}`),
branchTypeLabelTr: branch.branchType.labelTr,
isMainBranch: branch.isMainBranch,
weather: branch.weather,
}));
if (!this.selectedBranch) {
this.selectMainBranch();
}
} catch (error) {
console.error('Error loading branches:', error);
}
},
async loadProducts() {
try {
const productsResult = await apiClient.get('/api/falukant/products');
this.products = productsResult.data;
} catch (error) {
console.error('Error loading products:', error);
}
},
async onBranchSelected(newBranch) {
this.selectedBranch = newBranch;
// Branches neu laden, um das Wetter zu aktualisieren
await this.loadBranches();
// Den ausgewählten Branch nach dem Neuladen wieder setzen
if (newBranch) {
this.selectedBranch = this.branches.find(b => b.id === newBranch.id) || newBranch;
}
await this.loadProducts();
await this.loadVehicles();
await this.loadProductPricesForCurrentBranch();
this.$nextTick(() => {
this.$refs.directorInfo?.refresh();
this.$refs.saleSection?.loadInventory();
this.$refs.saleSection?.loadTransports();
this.$refs.productionSection?.loadProductions();
this.$refs.productionSection?.loadStorage();
this.$refs.storageSection?.loadStorageData();
this.$refs.revenueSection?.refresh && this.$refs.revenueSection.refresh();
});
// Beim Initial-Laden sicherstellen, dass ein Tab-Inhalt sichtbar ist
if (this.selectedBranch && !this.activeTab) {
this.activeTab = 'director';
}
},
async loadProductPricesForCurrentBranch() {
if (!this.selectedBranch || !this.selectedBranch.regionId) {
this.productPricesCache = {};
return;
}
// Lade Preise für alle Produkte in der aktuellen Region
const prices = {};
for (const product of this.products) {
try {
const { data } = await apiClient.get('/api/falukant/products/price-in-region', {
params: {
productId: product.id,
regionId: this.selectedBranch.regionId
}
});
prices[product.id] = data.price;
} catch (error) {
console.error(`Error loading price for product ${product.id}:`, error);
// Fallback auf Standard-Berechnung
const knowledgeFactor = product.knowledges?.[0]?.knowledge || 0;
const maxPrice = product.sellCost;
const minPrice = maxPrice * 0.6;
prices[product.id] = minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100);
}
}
this.productPricesCache = prices;
},
async createBranch() {
await this.loadBranches();
// Nach dem Anlegen eines neuen Branches automatisch den
// zuletzt/neu erstellten Branch auswählen.
if (this.branches.length > 0) {
const newest = this.branches.reduce((acc, b) =>
!acc || b.id > acc.id ? b : acc,
null
);
if (newest) {
await this.onBranchSelected(newest);
}
}
},
async upgradeBranch() {
if (!this.selectedBranch) return;
try {
await apiClient.post('/api/falukant/branches/upgrade', {
branchId: this.selectedBranch.id,
});
await this.loadBranches();
// Ausgewählten Branch nach dem Upgrade neu setzen
const updated = this.branches.find(b => b.id === this.selectedBranch.id);
if (updated) {
await this.onBranchSelected(updated);
}
} catch (error) {
console.error('Error upgrading branch:', error);
alert(this.$t('falukant.branch.actions.upgradeAlert', { branchId: this.selectedBranch.id }));
}
},
selectMainBranch() {
const main = this.branches.find(b => b.isMainBranch) || null;
if (main && main !== this.selectedBranch) {
this.selectedBranch = main;
}
if (this.selectedBranch) {
this.loadVehicles();
}
if (this.selectedBranch && !this.activeTab) {
this.activeTab = 'director';
}
},
calculateProductRevenue(product) {
if (!product.knowledges || product.knowledges.length === 0) {
return { absolute: 0, perMinute: 0 };
}
// Verwende gecachten regionalen Preis, falls verfügbar
let revenuePerUnit;
if (this.productPricesCache[product.id] !== undefined) {
revenuePerUnit = this.productPricesCache[product.id];
} else {
// Fallback auf Standard-Berechnung
const knowledgeFactor = product.knowledges[0].knowledge || 0;
const maxPrice = product.sellCost;
const minPrice = maxPrice * 0.6;
revenuePerUnit = minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100);
}
const perMinute = product.productionTime > 0
? revenuePerUnit / product.productionTime
: 0;
return {
absolute: revenuePerUnit.toFixed(2),
perMinute: perMinute.toFixed(2),
};
},
calculateProductProfit(product) {
const { absolute: revenueAbsoluteStr, perMinute: revenuePerMinuteStr }
= this.calculateProductRevenue(product);
const revenueAbsolute = parseFloat(revenueAbsoluteStr);
const costPerUnit = 6 * product.category;
const profitAbsolute = revenueAbsolute - costPerUnit;
const costPerMinute = product.productionTime > 0
? costPerUnit / product.productionTime
: 0;
const profitPerMinute = parseFloat(revenuePerMinuteStr) - costPerMinute;
return {
absolute: profitAbsolute.toFixed(2),
perMinute: profitPerMinute.toFixed(2),
};
},
conditionLabel(value) {
const v = Number(value) || 0;
if (v >= 95) return 'Ausgezeichnet'; // 95100
if (v >= 72) return 'Sehr gut'; // 7294
if (v >= 54) return 'Gut'; // 5471
if (v >= 39) return 'Mäßig'; // 3953
if (v >= 22) return 'Schlecht'; // 2238
if (v >= 6) return 'Sehr schlecht'; // 621
if (v >= 1) return 'Katastrophal'; // 15
return 'Unbekannt';
},
speedLabel(value) {
// Expect numeric speeds 1..4; provide localized labels as fallback to raw value
const key = value == null ? 'unknown' : String(value);
const tKey = `falukant.branch.transport.speed.${key}`;
const translated = this.$t(tKey);
// If translation returns the key (no translation found), fall back to the numeric value
if (!translated || translated === tKey) return value;
return translated;
},
transportModeLabel(mode) {
if (!mode) return '';
const key = String(mode);
const tKey = `falukant.branch.transport.modes.${key}`;
const translated = this.$t(tKey);
if (!translated || translated === tKey) return mode;
return translated;
},
async loadVehicles() {
if (!this.selectedBranch) return;
try {
const { data } = await apiClient.get('/api/falukant/vehicles', {
params: { regionId: this.selectedBranch.regionId },
});
this.vehicles = data || [];
} catch (error) {
console.error('Error loading vehicles:', error);
this.vehicles = [];
}
},
formatDateTime(value) {
if (!value) return '';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return '';
return date.toLocaleString();
},
handleEvent(eventData) {
switch (eventData.event) {
case 'production_ready':
this.$refs.productionSection?.loadProductions();
this.$refs.storageSection?.loadStorageData();
this.$refs.productionSection?.loadStorage();
this.$refs.saleSection?.loadInventory();
break;
case 'stock_change':
this.$refs.storageSection?.loadStorageData();
this.$refs.productionSection?.loadStorage();
this.$refs.saleSection?.loadInventory();
break;
case 'price_update':
this.$refs.revenueSection?.refresh();
break;
case 'director_death':
this.$refs.directorInfo?.loadDirector();
break;
case 'production_started':
this.$refs.productionSection?.loadProductions();
break;
case 'selled_items':
this.$refs.saleSection?.loadInventory();
this.$refs.storageSection?.loadStorageData();
this.$refs.productionSection?.loadStorage();
break;
case 'falukantUpdateStatus':
case 'falukantBranchUpdate':
if (this.$refs.statusBar) {
this.$refs.statusBar.fetchStatus();
}
if (this.$refs.productionSection) {
this.$refs.productionSection.loadProductions();
this.$refs.productionSection.loadStorage();
}
if (this.$refs.storageSection) {
this.$refs.storageSection.loadStorageData();
}
if (this.$refs.saleSection) {
this.$refs.saleSection.loadInventory();
}
break;
case 'knowledge_update':
this.loadProducts();
if (this.$refs.revenueSection) {
this.$refs.revenueSection.products = this.products;
this.$refs.revenueSection.refresh && this.$refs.revenueSection.refresh();
}
break;
case 'transport_arrived':
// Leerer Transport angekommen - Fahrzeug wurde zurückgeholt
if (eventData.empty && eventData.branch_id) {
// Nur aktualisieren, wenn der betroffene Branch ausgewählt ist
if (this.selectedBranch && this.selectedBranch.id === eventData.branch_id) {
// Fahrzeuge im Transport-Tab aktualisieren
this.loadVehicles();
// Laufende Transporte im Inventar-Tab aktualisieren
if (this.$refs.saleSection) {
this.$refs.saleSection.loadTransports();
}
}
}
break;
case 'transport_removed':
// Transport wurde entfernt (z.B. abgebrochen oder gelöscht)
if (eventData.branch_id) {
// Nur aktualisieren, wenn der betroffene Branch ausgewählt ist
if (this.selectedBranch && this.selectedBranch.id === eventData.branch_id) {
// Fahrzeuge im Transport-Tab aktualisieren
this.loadVehicles();
// Laufende Transporte im Inventar-Tab aktualisieren
if (this.$refs.saleSection) {
this.$refs.saleSection.loadInventory();
this.$refs.saleSection.loadTransports();
}
// Lager aktualisieren
if (this.$refs.storageSection) {
this.$refs.storageSection.loadStorageData();
}
if (this.$refs.productionSection) {
this.$refs.productionSection.loadStorage();
}
}
}
break;
case 'inventory_updated':
// Inventar wurde aktualisiert (durch Transporte)
if (eventData.branch_id) {
// Nur aktualisieren, wenn der betroffene Branch ausgewählt ist
if (this.selectedBranch && this.selectedBranch.id === eventData.branch_id) {
// Inventar im Inventar-Tab aktualisieren
if (this.$refs.saleSection) {
this.$refs.saleSection.loadInventory();
this.$refs.saleSection.loadTransports();
}
// Lager aktualisieren
if (this.$refs.storageSection) {
this.$refs.storageSection.loadStorageData();
}
if (this.$refs.productionSection) {
this.$refs.productionSection.loadStorage();
}
}
}
break;
default:
console.log('Unhandled event:', eventData);
}
},
handleDaemonMessage(event) {
if (event.data === 'ping') return;
try {
const message = JSON.parse(event.data);
this.handleEvent(message);
} catch (error) {
console.error('Error processing daemon message:', error);
}
},
openBuyVehicleDialog() {
if (!this.selectedBranch) return;
this.$refs.buyVehicleDialog?.open();
},
handleVehiclesBought() {
// Refresh status bar (for updated money) and potentially other data later
this.$refs.statusBar?.fetchStatus();
this.loadVehicles();
},
handleTransportCreated() {
this.loadVehicles();
this.$refs.storageSection?.loadStorageData();
this.$refs.saleSection?.loadTransports();
},
openSendVehicleDialog(vehicleId) {
this.sendVehicleDialog = {
show: true,
vehicleId: vehicleId,
vehicleIds: null,
vehicleTypeId: null,
targetBranchId: null,
success: false,
};
},
openSendAllVehiclesDialog(vehicleTypeId, vehicleList) {
this.sendVehicleDialog = {
show: true,
vehicleId: null,
vehicleIds: vehicleList.map(v => v.id),
vehicleTypeId: vehicleTypeId,
targetBranchId: null,
success: false,
};
},
closeSendVehicleDialog() {
this.sendVehicleDialog = {
show: false,
vehicleId: null,
vehicleIds: null,
vehicleTypeId: null,
targetBranchId: null,
success: false,
};
},
targetBranchOptions() {
return (this.branches || [])
.filter(b => ['store', 'fullstack'].includes(b.branchTypeLabelTr))
.filter(b => b.id !== this.selectedBranch?.id)
.map(b => ({
id: b.id,
label: `${b.cityName} ${b.type}`,
}));
},
async sendVehicles() {
if (!this.sendVehicleDialog.targetBranchId) {
alert(this.$t('falukant.branch.transport.selectTargetError'));
return;
}
try {
const payload = {
branchId: this.selectedBranch.id,
targetBranchId: this.sendVehicleDialog.targetBranchId,
productId: null,
quantity: 0,
};
if (this.sendVehicleDialog.vehicleIds && this.sendVehicleDialog.vehicleIds.length > 0) {
payload.vehicleIds = this.sendVehicleDialog.vehicleIds;
} else if (this.sendVehicleDialog.vehicleId) {
payload.vehicleIds = [this.sendVehicleDialog.vehicleId];
} else if (this.sendVehicleDialog.vehicleTypeId) {
payload.vehicleTypeId = this.sendVehicleDialog.vehicleTypeId;
} else {
alert(this.$t('falukant.branch.transport.noVehiclesSelected'));
return;
}
await apiClient.post('/api/falukant/transports', payload);
await this.loadVehicles();
this.sendVehicleDialog.success = true;
} catch (error) {
console.error('Error sending vehicles:', error);
alert(this.$t('falukant.branch.transport.sendError'));
}
},
calculateRepairCost(vehicle) {
if (!vehicle || !vehicle.type || vehicle.condition >= 100) return 0;
const baseCost = vehicle.type.cost || 0;
return Math.round(baseCost * 0.8 * (100 - vehicle.condition) / 100);
},
openRepairVehicleDialog(vehicle) {
if (!vehicle || vehicle.status !== 'available' || vehicle.condition >= 100) {
return;
}
// Kosten berechnen: Baupreis * 0.8 * (100 - zustand) / 100
const repairCost = this.calculateRepairCost(vehicle);
const buildTimeMinutes = vehicle.type.buildTimeMinutes || 0;
this.repairVehicleDialog = {
show: true,
vehicle: vehicle,
repairCost: repairCost,
buildTimeMinutes: buildTimeMinutes,
};
},
openRepairAllVehiclesDialog() {
const repairableVehicles = this.repairableVehicles;
if (repairableVehicles.length === 0) {
return;
}
this.repairAllVehiclesDialog = {
show: true,
vehicleIds: repairableVehicles.map(v => v.id),
totalCost: this.repairAllCost,
};
},
closeRepairAllVehiclesDialog() {
this.repairAllVehiclesDialog = {
show: false,
vehicleIds: [],
totalCost: null,
};
},
async repairAllVehicles() {
if (!this.repairAllVehiclesDialog.vehicleIds || this.repairAllVehiclesDialog.vehicleIds.length === 0) {
return;
}
try {
await apiClient.post('/api/falukant/vehicles/repair-all', {
vehicleIds: this.repairAllVehiclesDialog.vehicleIds,
});
await this.loadVehicles();
this.closeRepairAllVehiclesDialog();
alert(this.$t('falukant.branch.transport.repairAllSuccess'));
this.$refs.statusBar?.fetchStatus();
} catch (error) {
console.error('Error repairing all vehicles:', error);
const errorMessage = error.response?.data?.message || this.$t('falukant.branch.transport.repairAllError');
alert(errorMessage);
}
},
closeRepairVehicleDialog() {
this.repairVehicleDialog = {
show: false,
vehicle: null,
repairCost: null,
buildTimeMinutes: null,
};
},
formatMoney(amount) {
if (amount == null) return '';
try {
return amount.toLocaleString(undefined, {
minimumFractionDigits: 0,
maximumFractionDigits: 0,
});
} catch (e) {
return String(amount);
}
},
formatBuildTime(minutes) {
if (!minutes || minutes === 0) {
return this.$t('falukant.branch.transport.repairBuildTimeInstant');
}
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
const parts = [];
if (hours > 0) {
parts.push(`${hours} ${this.$t('falukant.branch.transport.repairBuildTimeHours')}`);
}
if (mins > 0) {
parts.push(`${mins} ${this.$t('falukant.branch.transport.repairBuildTimeMinutes')}`);
}
return parts.length > 0 ? parts.join(' ') : '0 ' + this.$t('falukant.branch.transport.repairBuildTimeMinutes');
},
async repairVehicle() {
if (!this.repairVehicleDialog.vehicle) {
return;
}
try {
await apiClient.post(`/api/falukant/vehicles/${this.repairVehicleDialog.vehicle.id}/repair`);
await this.loadVehicles();
this.closeRepairVehicleDialog();
alert(this.$t('falukant.branch.transport.repairSuccess'));
this.$refs.statusBar?.fetchStatus();
} catch (error) {
console.error('Error repairing vehicle:', error);
const errorMessage = error.response?.data?.message || this.$t('falukant.branch.transport.repairError');
alert(errorMessage);
}
},
},
};
</script>
<style scoped lang="scss">
h2 {
padding-top: 20px;
}
.send-all-vehicles {
margin-top: 1.5rem;
padding-top: 1rem;
border-top: 1px solid #ddd;
}
.send-all-buttons {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.5rem;
}
.send-all-buttons button {
padding: 0.5rem 1rem;
background-color: #F9A22C;
color: #000000;
border: 1px solid #F9A22C;
border-radius: 4px;
cursor: pointer;
}
.send-all-buttons button:hover {
background-color: #fdf1db;
color: #7E471B;
border: 1px solid #7E471B;
}
.no-action {
color: #999;
font-style: italic;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 2rem;
border-radius: 8px;
min-width: 400px;
max-width: 600px;
}
.send-vehicle-form {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 1rem;
}
.send-vehicle-form label {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.send-vehicle-form select {
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
}
.modal-buttons {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1rem;
}
.modal-buttons button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.modal-buttons button:first-child {
background-color: #007bff;
color: white;
}
.modal-buttons button:first-child:hover {
background-color: #0056b3;
}
.modal-buttons button:first-child:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.modal-buttons button:last-child {
background-color: #6c757d;
color: white;
}
.modal-buttons button:last-child:hover {
background-color: #5a6268;
}
.vehicle-actions {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.repair-button {
background-color: #28a745;
color: white;
}
.repair-button:hover {
background-color: #218838;
}
.repair-vehicle-form {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 1rem;
}
.repair-info {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.repair-info p {
margin: 0;
}
.repair-confirm-button {
background-color: #28a745;
color: white;
}
.repair-confirm-button:hover {
background-color: #218838;
}
.send-vehicle-success {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 1rem;
text-align: center;
}
.send-vehicle-success p {
margin: 0;
font-size: 1.1em;
color: #28a745;
}
.vehicle-action-buttons {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.repair-all-button {
background-color: #28a745;
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.repair-all-button:hover {
background-color: #218838;
}
.repair-all-form {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 1rem;
}
.repair-all-discount {
color: #28a745;
font-weight: bold;
}
</style>