Refactor navigation and enhance director information display

- 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.
This commit is contained in:
Torsten Schulz (local)
2025-11-24 16:38:36 +01:00
parent 23725c20ee
commit 5ed27e5a6a
10 changed files with 298 additions and 64 deletions

View File

@@ -117,10 +117,6 @@ const menuStructure = {
visible: ["hasfalukantaccount"], visible: ["hasfalukantaccount"],
path: "/falukant/branch" path: "/falukant/branch"
}, },
directors: {
visible: ["hasfalukantaccount"],
path: "/falukant/directors"
},
family: { family: {
visible: ["hasfalukantaccount"], visible: ["hasfalukantaccount"],
path: "/falukant/family" path: "/falukant/family"

View File

@@ -1160,23 +1160,45 @@ class FalukantService extends BaseService {
{ {
model: FalukantCharacter, model: FalukantCharacter,
as: 'character', as: 'character',
attributes: ['firstName', 'lastName', 'birthdate', 'titleOfNobility', 'gender'], // Wichtige Felder explizit auswählen, damit die Joins auf Titel,
// Namen und Region im äußeren Select funktionieren.
attributes: ['id', 'birthdate', 'gender', 'titleOfNobility', 'firstName', 'lastName', 'regionId'],
where: { where: {
regionId: branch.regionId, regionId: branch.regionId,
}, },
include: [ include: [
{ {
model: TitleOfNobility, model: TitleOfNobility,
as: 'nobleTitle' as: 'nobleTitle',
attributes: ['labelTr', 'level']
}, },
{ {
model: FalukantPredefineFirstname, model: FalukantPredefineFirstname,
as: 'definedFirstName' as: 'definedFirstName',
attributes: ['name']
}, },
{ {
model: FalukantPredefineLastname, model: FalukantPredefineLastname,
as: 'definedLastName' as: 'definedLastName',
attributes: ['name']
}, },
{
model: Knowledge,
as: 'knowledges',
attributes: ['productId', 'knowledge'],
include: [
{
model: ProductType,
as: 'productType',
attributes: ['labelTr'],
},
],
},
{
model: RegionData,
as: 'region',
attributes: ['name']
}
] ]
}, },
], ],
@@ -1185,6 +1207,16 @@ class FalukantService extends BaseService {
return null; return null;
} }
const age = Math.floor((Date.now() - new Date(director.character.birthdate)) / (24 * 60 * 60 * 1000)); const age = Math.floor((Date.now() - new Date(director.character.birthdate)) / (24 * 60 * 60 * 1000));
// wishedIncome analog zu getAllDirectors() berechnen
const knowledges = director.character.knowledges || [];
const avgKnowledge = knowledges.length
? knowledges.reduce((sum, k) => sum + k.knowledge, 0) / knowledges.length
: 0;
const wishedIncome = Math.round(
director.character.nobleTitle.level * Math.pow(1.231, avgKnowledge / 1.5)
);
return { return {
director: { director: {
id: director.id, id: director.id,
@@ -1193,12 +1225,18 @@ class FalukantService extends BaseService {
title: director.character.nobleTitle.labelTr, title: director.character.nobleTitle.labelTr,
age, age,
gender: director.character.gender, gender: director.character.gender,
nobleTitle: director.character.nobleTitle,
definedFirstName: director.character.definedFirstName,
definedLastName: director.character.definedLastName,
knowledges: director.character.knowledges,
}, },
income: director.income, income: director.income,
satisfaction: director.satisfaction, satisfaction: director.satisfaction,
mayProduce: director.mayProduce, mayProduce: director.mayProduce,
maySell: director.maySell, maySell: director.maySell,
mayStartTransport: director.mayStartTransport, mayStartTransport: director.mayStartTransport,
region: director.character.region?.name || null,
wishedIncome,
}, },
}; };
} }

View File

@@ -1,48 +1,119 @@
<template> <template>
<div class="director-info"> <div class="director-info">
<h3>{{ $t('falukant.branch.director.title') }}</h3>
<div v-if="!director || director === null"> <div v-if="!director || director === null">
<button @click="openNewDirectorDialog">{{ $t('falukant.branch.director.actions.new') }}</button> <button @click="openNewDirectorDialog">
{{ $t('falukant.branch.director.actions.new') }}
</button>
</div> </div>
<div v-else class="director-info-container"> <div v-else class="director-info-container">
<div> <!-- Linke Seite: Stammdaten & Wissen (aus /falukant/directors) -->
<table> <div class="director-main">
<h3 class="director-name">
{{ $t('falukant.titles.' + director.character.gender + '.' + director.character.nobleTitle.labelTr) }}
{{ director.character.definedFirstName.name }} {{ director.character.definedLastName.name }}
</h3>
<p class="director-meta">
{{ $t('falukant.director.age') }}:
{{ director.character.age }}
<span v-if="director.region"> {{ director.region }}</span>
</p>
<div
v-if="director.character.knowledges && director.character.knowledges.length"
class="knowledge-panel"
>
<h4>{{ $t('falukant.director.knowledge.title') }}</h4>
<div class="table-container">
<table class="knowledge-table">
<thead>
<tr> <tr>
<td>{{ $t('falukant.branch.director.name') }}</td> <th>{{ $t('falukant.director.product') }}</th>
<td> <th>{{ $t('falukant.director.knowledge.knowledge') }}</th>
{{ $t('falukant.titles.' + director.character.gender + '.' + director.character.title) }}
{{ director.character.name }}
</td>
</tr> </tr>
<tr> </thead>
<td>{{ $t('falukant.branch.director.salary') }}</td> <tbody>
<td>{{ director.income }}</td> <tr
</tr> v-for="item in director.character.knowledges"
<tr> :key="item.productId"
<td>{{ $t('falukant.branch.director.satisfaction') }}</td> >
<td>{{ director.satisfaction }} %</td> <td>{{ $t(`falukant.product.${item.productType.labelTr}`) }}</td>
<td>{{ item.knowledge }} %</td>
</tr> </tr>
</tbody>
</table> </table>
</div> </div>
<div> </div>
<table> </div>
<tr>
<td><button @click="fireDirector">{{ $t('falukant.branch.director.fire') }}</button></td> <!-- Rechte Seite: Aktionen, Einkommen, Rechte -->
</tr> <div class="director-actions">
<tr> <div class="field">
<td><button @click="teachDirector">{{ $t('falukant.branch.director.teach') }}</button></td>
</tr>
<tr>
<td>
<label> <label>
<input type="checkbox" v-model="director.mayProduce" {{ $t('falukant.branch.director.satisfaction') }}:
@change="saveSetting('mayProduce', director.mayProduce)"> <span>{{ director.satisfaction }} %</span>
</label>
</div>
<div class="field">
<label>
{{ $t('falukant.branch.director.income') }}:
<input type="number" v-model.number="editIncome" />
</label>
<span
v-if="director.wishedIncome != null"
class="link"
@click="setWishedIncome"
>
({{ $t('falukant.director.wishedIncome') }}:
{{ director.wishedIncome }})
</span>
</div>
<div class="field">
<button @click="updateDirector">
{{ $t('falukant.director.updateButton') }}
</button>
</div>
<div class="field toggles">
<label>
<input
type="checkbox"
v-model="director.mayProduce"
@change="saveSetting('mayProduce', director.mayProduce)"
/>
{{ $t('falukant.branch.director.produce') }} {{ $t('falukant.branch.director.produce') }}
</label> </label>
</td> <label>
</tr> <input
<!-- Ähnliche Checkboxen für maySell und mayStartTransport --> type="checkbox"
</table> v-model="director.maySell"
@change="saveSetting('maySell', director.maySell)"
/>
{{ $t('falukant.branch.director.sell') }}
</label>
<label>
<input
type="checkbox"
v-model="director.mayStartTransport"
@change="saveSetting('mayStartTransport', director.mayStartTransport)"
/>
{{ $t('falukant.branch.director.starttransport') }}
</label>
</div>
<div class="field">
<button @click="fireDirector">
{{ $t('falukant.branch.director.fire') }}
</button>
</div>
<div class="field">
<button @click="teachDirector">
{{ $t('falukant.branch.director.teach') }}
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -63,6 +134,7 @@ export default {
return { return {
director: null, director: null,
showNewDirectorDialog: false, showNewDirectorDialog: false,
editIncome: null,
}; };
}, },
async mounted() { async mounted() {
@@ -84,12 +156,15 @@ export default {
data.director === null data.director === null
) { ) {
this.director = null; this.director = null;
this.editIncome = null;
} else { } else {
this.director = data.director; this.director = data.director;
this.editIncome = this.director.income;
} }
} catch (error) { } catch (error) {
console.error('Error loading director:', error); console.error('Error loading director:', error);
this.director = null; this.director = null;
this.editIncome = null;
} }
}, },
@@ -112,6 +187,24 @@ export default {
this.$refs.newDirectorDialog.open(this.branchId); this.$refs.newDirectorDialog.open(this.branchId);
}, },
async updateDirector() {
if (!this.director || this.editIncome == null) return;
try {
await apiClient.post(`/api/falukant/directors`, {
directorId: this.director.id,
income: this.editIncome,
});
await this.loadDirector();
} catch (error) {
console.error('Error updating director:', error);
}
},
setWishedIncome() {
if (!this.director || this.director.wishedIncome == null) return;
this.editIncome = this.director.wishedIncome;
},
fireDirector() { fireDirector() {
alert(this.$t('falukant.branch.director.fireAlert')); alert(this.$t('falukant.branch.director.fireAlert'));
}, },
@@ -133,9 +226,62 @@ export default {
.director-info-container { .director-info-container {
display: flex; display: flex;
gap: 1rem;
} }
.director-info-container>div { .director-main {
flex: 2;
}
.director-actions {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.director-name {
margin: 0 0 0.25rem 0;
}
.director-meta {
margin: 0 0 0.75rem 0;
font-style: italic;
}
.field {
margin-bottom: 0.5rem;
}
.field label {
display: flex;
align-items: center;
gap: 0.5rem;
}
.toggles label {
display: block;
}
.link {
color: #007bff;
cursor: pointer;
margin-left: 0.5rem;
}
.table-container {
max-height: 40vh;
overflow-y: auto;
}
.knowledge-table {
width: 100%; width: 100%;
border-collapse: collapse;
}
.knowledge-table th,
.knowledge-table td {
border: 1px solid #ddd;
padding: 4px 6px;
} }
</style> </style>

View File

@@ -1,6 +1,5 @@
<template> <template>
<div class="production-section"> <div class="production-section">
<h3>{{ $t('falukant.branch.production.title') }}</h3>
<div v-if="productions && productions.length > 0"> <div v-if="productions && productions.length > 0">
<h4>{{ $t('falukant.branch.production.current') }}</h4> <h4>{{ $t('falukant.branch.production.current') }}</h4>
<table> <table>

View File

@@ -1,6 +1,5 @@
<template> <template>
<div class="sale-section"> <div class="sale-section">
<h3>{{ $t('falukant.branch.sale.title') }}</h3>
<!-- Beispielhafte Inventar-Tabelle --> <!-- Beispielhafte Inventar-Tabelle -->
<div v-if="inventory.length > 0" class="inventory-table"> <div v-if="inventory.length > 0" class="inventory-table">
<table> <table>

View File

@@ -1,6 +1,5 @@
<template> <template>
<div class="storage-section"> <div class="storage-section">
<h3>{{ $t('falukant.branch.storage.title') }}</h3>
<div class="storage-info"> <div class="storage-info">
<p> <p>
{{ $t('falukant.branch.storage.currentCapacity') }}: {{ $t('falukant.branch.storage.currentCapacity') }}:

View File

@@ -102,6 +102,12 @@
}, },
"branch": { "branch": {
"title": "Filiale", "title": "Filiale",
"tabs": {
"director": "Direktor",
"inventory": "Inventar",
"production": "Produktion",
"storage": "Lager"
},
"selection": { "selection": {
"title": "Niederlassungsauswahl", "title": "Niederlassungsauswahl",
"selected": "Ausgewählte Niederlassung", "selected": "Ausgewählte Niederlassung",

View File

@@ -76,7 +76,6 @@
"create": "Erstellen", "create": "Erstellen",
"overview": "Übersicht", "overview": "Übersicht",
"towns": "Niederlassungen", "towns": "Niederlassungen",
"directors": "Direktoren",
"factory": "Produktion", "factory": "Produktion",
"family": "Familie", "family": "Familie",
"house": "Haus", "house": "Haus",

View File

@@ -76,7 +76,6 @@
"create": "Create", "create": "Create",
"overview": "Overview", "overview": "Overview",
"towns": "Towns", "towns": "Towns",
"directors": "Directors",
"factory": "Factory", "factory": "Factory",
"family": "Family", "family": "Family",
"house": "House", "house": "House",

View File

@@ -4,21 +4,54 @@
<div class="contentscroll"> <div class="contentscroll">
<h2>{{ $t('falukant.branch.title') }}</h2> <h2>{{ $t('falukant.branch.title') }}</h2>
<BranchSelection :branches="branches" :selectedBranch="selectedBranch" @branchSelected="onBranchSelected" <BranchSelection
@createBranch="createBranch" @upgradeBranch="upgradeBranch" ref="branchSelection" /> :branches="branches"
:selectedBranch="selectedBranch"
@branchSelected="onBranchSelected"
@createBranch="createBranch"
@upgradeBranch="upgradeBranch"
ref="branchSelection"
/>
<DirectorInfo v-if="selectedBranch" :branchId="selectedBranch.id" ref="directorInfo" /> <!-- Tab-Navigation für Inhalte der ausgewählten Niederlassung -->
<SimpleTabs
v-if="selectedBranch"
v-model="activeTab"
:tabs="tabs"
/>
<SaleSection v-if="selectedBranch" :branchId="selectedBranch.id" ref="saleSection" /> <!-- 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>
<ProductionSection v-if="selectedBranch" :branchId="selectedBranch.id" :products="products" <!-- Inventar / Verkauf -->
ref="productionSection" /> <div v-else-if="activeTab === 'inventory'" class="branch-tab-pane">
<SaleSection :branchId="selectedBranch.id" ref="saleSection" />
</div>
<StorageSection v-if="selectedBranch" :branchId="selectedBranch.id" ref="storageSection" /> <!-- 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>
<RevenueSection v-if="selectedBranch" :products="products" <!-- Lager -->
:calculateProductRevenue="calculateProductRevenue" :calculateProductProfit="calculateProductProfit" <div v-else-if="activeTab === 'storage'" class="branch-tab-pane">
ref="revenueSection" /> <StorageSection :branchId="selectedBranch.id" ref="storageSection" />
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -26,6 +59,7 @@
<script> <script>
import StatusBar from '@/components/falukant/StatusBar.vue'; import StatusBar from '@/components/falukant/StatusBar.vue';
import BranchSelection from '@/components/falukant/BranchSelection.vue'; import BranchSelection from '@/components/falukant/BranchSelection.vue';
import SimpleTabs from '@/components/SimpleTabs.vue';
import DirectorInfo from '@/components/falukant/DirectorInfo.vue'; import DirectorInfo from '@/components/falukant/DirectorInfo.vue';
import SaleSection from '@/components/falukant/SaleSection.vue'; import SaleSection from '@/components/falukant/SaleSection.vue';
import ProductionSection from '@/components/falukant/ProductionSection.vue'; import ProductionSection from '@/components/falukant/ProductionSection.vue';
@@ -39,6 +73,7 @@ export default {
components: { components: {
StatusBar, StatusBar,
BranchSelection, BranchSelection,
SimpleTabs,
DirectorInfo, DirectorInfo,
SaleSection, SaleSection,
ProductionSection, ProductionSection,
@@ -51,6 +86,13 @@ export default {
branches: [], branches: [],
selectedBranch: null, selectedBranch: null,
products: [], 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' },
],
}; };
}, },
@@ -144,6 +186,11 @@ export default {
this.$refs.storageSection?.loadStorageData(); this.$refs.storageSection?.loadStorageData();
this.$refs.revenueSection?.refresh && this.$refs.revenueSection.refresh(); 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() { async createBranch() {
@@ -177,6 +224,9 @@ export default {
if (main && main !== this.selectedBranch) { if (main && main !== this.selectedBranch) {
this.selectedBranch = main; this.selectedBranch = main;
} }
if (this.selectedBranch && !this.activeTab) {
this.activeTab = 'director';
}
}, },
calculateProductRevenue(product) { calculateProductRevenue(product) {
@@ -256,7 +306,10 @@ export default {
break; break;
case 'knowledge_update': case 'knowledge_update':
this.loadProducts(); this.loadProducts();
if (this.$refs.revenueSection) {
this.$refs.revenueSection.products = this.products; this.$refs.revenueSection.products = this.products;
this.$refs.revenueSection.refresh && this.$refs.revenueSection.refresh();
}
break; break;
default: default:
console.log('Unhandled event:', eventData); console.log('Unhandled event:', eventData);