- Removed the directors section from the navigation menu for a cleaner interface. - Updated the FalukantService to include additional attributes for directors, such as knowledges and region. - Enhanced the DirectorInfo component to display detailed information, including knowledge and income management features. - Implemented tab navigation in BranchView for better organization of director, inventory, production, and storage sections. - Updated localization files to reflect changes in navigation and tab labels.
336 lines
12 KiB
Vue
336 lines
12 KiB
Vue
<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" ref="directorInfo" />
|
|
</div>
|
|
|
|
<!-- Inventar / Verkauf -->
|
|
<div v-else-if="activeTab === 'inventory'" class="branch-tab-pane">
|
|
<SaleSection :branchId="selectedBranch.id" ref="saleSection" />
|
|
</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"
|
|
ref="revenueSection"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Lager -->
|
|
<div v-else-if="activeTab === 'storage'" class="branch-tab-pane">
|
|
<StorageSection :branchId="selectedBranch.id" ref="storageSection" />
|
|
</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 apiClient from '@/utils/axios.js';
|
|
import { mapState } from 'vuex';
|
|
|
|
export default {
|
|
name: "BranchView",
|
|
components: {
|
|
StatusBar,
|
|
BranchSelection,
|
|
SimpleTabs,
|
|
DirectorInfo,
|
|
SaleSection,
|
|
ProductionSection,
|
|
StorageSection,
|
|
RevenueSection,
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
branches: [],
|
|
selectedBranch: null,
|
|
products: [],
|
|
activeTab: 'production',
|
|
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' },
|
|
],
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
...mapState(['socket', 'daemonSocket']),
|
|
},
|
|
|
|
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)
|
|
[
|
|
"production_ready", "stock_change", "price_update",
|
|
"director_death", "production_started", "selled_items",
|
|
"knowledge_update", "falukantUpdateStatus", "falukantBranchUpdate"
|
|
].forEach(eventName => {
|
|
if (this.daemonSocket) {
|
|
this.daemonSocket.addEventListener('message', (event) => {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
if (data.event === eventName) {
|
|
this.handleEvent(data);
|
|
}
|
|
} catch (error) {
|
|
// Ignore non-JSON messages like ping/pong
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// 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 }));
|
|
}
|
|
},
|
|
|
|
beforeUnmount() {
|
|
// Daemon WebSocket wird automatisch beim Logout geschlossen
|
|
if (this.socket) {
|
|
this.socket.off('falukantUpdateStatus');
|
|
this.socket.off('falukantBranchUpdate');
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
async loadBranches() {
|
|
try {
|
|
const result = await apiClient.get('/api/falukant/branches');
|
|
this.branches = result.data.map(branch => ({
|
|
id: branch.id,
|
|
cityName: branch.region.name,
|
|
type: this.$t(`falukant.branch.types.${branch.branchType.labelTr}`),
|
|
isMainBranch: branch.isMainBranch,
|
|
}));
|
|
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;
|
|
await this.loadProducts();
|
|
this.$nextTick(() => {
|
|
this.$refs.directorInfo?.refresh();
|
|
this.$refs.saleSection?.loadInventory();
|
|
this.$refs.productionSection?.loadProductions();
|
|
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 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);
|
|
}
|
|
}
|
|
},
|
|
|
|
upgradeBranch() {
|
|
if (this.selectedBranch) {
|
|
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.activeTab) {
|
|
this.activeTab = 'director';
|
|
}
|
|
},
|
|
|
|
calculateProductRevenue(product) {
|
|
if (!product.knowledges || product.knowledges.length === 0) {
|
|
return { absolute: 0, perMinute: 0 };
|
|
}
|
|
const knowledgeFactor = product.knowledges[0].knowledge || 0;
|
|
const maxPrice = product.sellCost;
|
|
const minPrice = maxPrice * 0.6;
|
|
const 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),
|
|
};
|
|
},
|
|
|
|
handleEvent(eventData) {
|
|
switch (eventData.event) {
|
|
case 'production_ready':
|
|
this.$refs.productionSection?.loadProductions();
|
|
this.$refs.storageSection?.loadStorageData();
|
|
this.$refs.saleSection?.loadInventory();
|
|
break;
|
|
case 'stock_change':
|
|
this.$refs.storageSection?.loadStorageData();
|
|
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();
|
|
break;
|
|
case 'falukantUpdateStatus':
|
|
case 'falukantBranchUpdate':
|
|
if (this.$refs.statusBar) {
|
|
this.$refs.statusBar.fetchStatus();
|
|
}
|
|
|
|
if (this.$refs.productionSection) {
|
|
this.$refs.productionSection.loadProductions();
|
|
}
|
|
|
|
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;
|
|
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);
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
h2 {
|
|
padding-top: 20px;
|
|
}
|
|
</style> |