Add marriage management features: Implement endpoints for spending time with, gifting to, and reconciling with spouses in the FalukantController. Update UserHouse model to include household tension attributes. Enhance frontend components to manage marriage actions and display household tension details, including localization updates in multiple languages.
This commit is contained in:
@@ -7,6 +7,11 @@
|
||||
<span class="branch-kicker">Niederlassung</span>
|
||||
<h2>{{ $t('falukant.branch.title') }}</h2>
|
||||
<p>Produktion, Lager, Verkauf und Transport in einer spielweltbezogenen Steuerflaeche.</p>
|
||||
<div class="branch-hero__meta">
|
||||
<span class="branch-hero__badge">
|
||||
{{ $t('falukant.branch.currentCertificate') }}: {{ currentCertificate ?? '---' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -398,11 +403,13 @@ export default {
|
||||
branchTaxes: null,
|
||||
branchTaxesLoading: false,
|
||||
branchTaxesError: null,
|
||||
currentCertificate: null,
|
||||
pendingBranchRefresh: null,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['socket', 'daemonSocket']),
|
||||
...mapState(['socket', 'daemonSocket', 'user']),
|
||||
freeVehiclesByType() {
|
||||
const grouped = {};
|
||||
for (const v of this.vehicles || []) {
|
||||
@@ -436,6 +443,7 @@ export default {
|
||||
await this.loadBranches();
|
||||
|
||||
const branchId = this.$route.params.branchId;
|
||||
await this.loadCurrentCertificate();
|
||||
await this.loadProducts();
|
||||
|
||||
if (branchId) {
|
||||
@@ -454,6 +462,7 @@ export default {
|
||||
// Live-Socket-Events (Backend Socket.io)
|
||||
if (this.socket) {
|
||||
this.socket.on('falukantUpdateStatus', (data) => this.handleEvent({ event: 'falukantUpdateStatus', ...data }));
|
||||
this.socket.on('falukantUpdateProductionCertificate', (data) => this.handleEvent({ event: 'falukantUpdateProductionCertificate', ...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 }));
|
||||
@@ -463,12 +472,17 @@ export default {
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
if (this.pendingBranchRefresh) {
|
||||
clearTimeout(this.pendingBranchRefresh);
|
||||
this.pendingBranchRefresh = null;
|
||||
}
|
||||
// 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('falukantUpdateProductionCertificate');
|
||||
this.socket.off('falukantBranchUpdate');
|
||||
this.socket.off('transport_arrived');
|
||||
this.socket.off('inventory_updated');
|
||||
@@ -493,6 +507,34 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
matchesCurrentUser(eventData) {
|
||||
if (eventData?.user_id == null) {
|
||||
return true;
|
||||
}
|
||||
const currentIds = [this.user?.id, this.user?.hashedId]
|
||||
.filter(Boolean)
|
||||
.map((value) => String(value));
|
||||
return currentIds.includes(String(eventData.user_id));
|
||||
},
|
||||
queueBranchRefresh() {
|
||||
if (this.pendingBranchRefresh) {
|
||||
clearTimeout(this.pendingBranchRefresh);
|
||||
}
|
||||
this.pendingBranchRefresh = setTimeout(async () => {
|
||||
this.pendingBranchRefresh = null;
|
||||
this.$refs.statusBar?.fetchStatus();
|
||||
await this.loadCurrentCertificate();
|
||||
await this.loadProducts();
|
||||
this.$refs.productionSection?.loadProductions();
|
||||
this.$refs.productionSection?.loadStorage();
|
||||
this.$refs.storageSection?.loadStorageData();
|
||||
this.$refs.saleSection?.loadInventory();
|
||||
if (this.$refs.revenueSection) {
|
||||
this.$refs.revenueSection.products = this.products;
|
||||
this.$refs.revenueSection.refresh && this.$refs.revenueSection.refresh();
|
||||
}
|
||||
}, 120);
|
||||
},
|
||||
async loadBranches() {
|
||||
try {
|
||||
const result = await apiClient.get('/api/falukant/branches');
|
||||
@@ -512,6 +554,14 @@ export default {
|
||||
console.error('Error loading branches:', error);
|
||||
}
|
||||
},
|
||||
async loadCurrentCertificate() {
|
||||
try {
|
||||
const result = await apiClient.get('/api/falukant/user');
|
||||
this.currentCertificate = result.data?.certificate ?? null;
|
||||
} catch (error) {
|
||||
console.error('Error loading certificate:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async loadProducts() {
|
||||
try {
|
||||
@@ -771,6 +821,9 @@ export default {
|
||||
},
|
||||
|
||||
handleEvent(eventData) {
|
||||
if (!this.matchesCurrentUser(eventData)) {
|
||||
return;
|
||||
}
|
||||
switch (eventData.event) {
|
||||
case 'production_ready':
|
||||
this.$refs.productionSection?.loadProductions();
|
||||
@@ -798,30 +851,12 @@ export default {
|
||||
this.$refs.productionSection?.loadStorage();
|
||||
break;
|
||||
case 'falukantUpdateStatus':
|
||||
case 'falukantUpdateProductionCertificate':
|
||||
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();
|
||||
}
|
||||
this.queueBranchRefresh();
|
||||
break;
|
||||
case 'knowledge_update':
|
||||
this.loadProducts();
|
||||
if (this.$refs.revenueSection) {
|
||||
this.$refs.revenueSection.products = this.products;
|
||||
this.$refs.revenueSection.refresh && this.$refs.revenueSection.refresh();
|
||||
}
|
||||
this.queueBranchRefresh();
|
||||
break;
|
||||
case 'transport_arrived':
|
||||
// Leerer Transport angekommen - Fahrzeug wurde zurückgeholt
|
||||
@@ -1149,6 +1184,22 @@ export default {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.branch-hero__meta {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.branch-hero__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid rgba(138, 84, 17, 0.16);
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
color: #7a4b12;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.branch-tab-content {
|
||||
margin-top: 16px;
|
||||
padding: 18px;
|
||||
|
||||
@@ -149,6 +149,39 @@
|
||||
{{ $t('falukant.family.householdTension.' + householdTension) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="marriage-overview__item" v-if="householdTensionScore != null">
|
||||
<span class="marriage-overview__label">{{ $t('falukant.family.householdTension.score') }}</span>
|
||||
<strong>{{ householdTensionScore }}</strong>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-if="relationships.length > 0 && relationships[0].relationshipType === 'married'" class="marriage-actions surface-card">
|
||||
<h3>{{ $t('falukant.family.marriageActions.title') }}</h3>
|
||||
<div class="marriage-actions__buttons">
|
||||
<button class="button button--secondary" @click="spendTimeWithSpouse">
|
||||
{{ $t('falukant.family.marriageActions.spendTime') }}
|
||||
</button>
|
||||
<button class="button button--secondary" @click="giftToSpouse('small')">
|
||||
{{ $t('falukant.family.marriageActions.giftSmall') }}
|
||||
</button>
|
||||
<button class="button button--secondary" @click="giftToSpouse('decent')">
|
||||
{{ $t('falukant.family.marriageActions.giftDecent') }}
|
||||
</button>
|
||||
<button class="button button--secondary" @click="giftToSpouse('lavish')">
|
||||
{{ $t('falukant.family.marriageActions.giftLavish') }}
|
||||
</button>
|
||||
<button class="button button--secondary" @click="reconcileMarriage">
|
||||
{{ $t('falukant.family.marriageActions.reconcile') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="householdTensionReasons.length > 0" class="marriage-actions__reasons">
|
||||
<span class="marriage-actions__reasons-label">{{ $t('falukant.family.householdTension.reasonsLabel') }}</span>
|
||||
<div class="marriage-actions__reason-list">
|
||||
<span v-for="reason in householdTensionReasons" :key="reason" class="lover-meta-badge lover-meta-badge--warning">
|
||||
{{ $t('falukant.family.householdTension.reasons.' + reason) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="children-section">
|
||||
@@ -351,6 +384,8 @@ export default {
|
||||
marriageSatisfaction: null,
|
||||
marriageState: null,
|
||||
householdTension: null,
|
||||
householdTensionScore: null,
|
||||
householdTensionReasons: [],
|
||||
selectedChild: null,
|
||||
pendingFamilyRefresh: null
|
||||
}
|
||||
@@ -495,11 +530,46 @@ export default {
|
||||
this.marriageSatisfaction = response.data.marriageSatisfaction;
|
||||
this.marriageState = response.data.marriageState;
|
||||
this.householdTension = response.data.householdTension;
|
||||
this.householdTensionScore = response.data.householdTensionScore;
|
||||
this.householdTensionReasons = response.data.householdTensionReasons || [];
|
||||
} catch (error) {
|
||||
console.error('Error loading family data:', error);
|
||||
}
|
||||
},
|
||||
|
||||
async spendTimeWithSpouse() {
|
||||
try {
|
||||
await apiClient.post('/api/falukant/family/marriage/spend-time');
|
||||
await this.loadFamilyData();
|
||||
showSuccess(this, this.$t('falukant.family.marriageActions.spendTimeSuccess'));
|
||||
} catch (error) {
|
||||
console.error('Error spending time with spouse:', error);
|
||||
showError(this, this.$t('falukant.family.marriageActions.actionError'));
|
||||
}
|
||||
},
|
||||
|
||||
async giftToSpouse(giftLevel) {
|
||||
try {
|
||||
await apiClient.post('/api/falukant/family/marriage/gift', { giftLevel });
|
||||
await this.loadFamilyData();
|
||||
showSuccess(this, this.$t('falukant.family.marriageActions.giftSuccess'));
|
||||
} catch (error) {
|
||||
console.error('Error gifting spouse:', error);
|
||||
showError(this, this.$t('falukant.family.marriageActions.actionError'));
|
||||
}
|
||||
},
|
||||
|
||||
async reconcileMarriage() {
|
||||
try {
|
||||
await apiClient.post('/api/falukant/family/marriage/reconcile');
|
||||
await this.loadFamilyData();
|
||||
showSuccess(this, this.$t('falukant.family.marriageActions.reconcileSuccess'));
|
||||
} catch (error) {
|
||||
console.error('Error reconciling marriage:', error);
|
||||
showError(this, this.$t('falukant.family.marriageActions.actionError'));
|
||||
}
|
||||
},
|
||||
|
||||
async loadOwnCharacter() {
|
||||
try {
|
||||
const response = await apiClient.get('/api/falukant/user');
|
||||
@@ -814,6 +884,39 @@ export default {
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.marriage-actions {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-bottom: 18px;
|
||||
padding: 16px 18px;
|
||||
}
|
||||
|
||||
.marriage-actions h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.marriage-actions__buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.marriage-actions__reasons {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.marriage-actions__reasons-label {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.marriage-actions__reason-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.inline-status-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -47,6 +47,9 @@
|
||||
<button class="button-secondary" :disabled="(userHouse.servantCount || 0) <= 0" @click="dismissServant">
|
||||
{{ $t('falukant.house.servants.actions.dismiss') }}
|
||||
</button>
|
||||
<button class="button-secondary" @click="tidyHousehold">
|
||||
{{ $t('falukant.house.servants.actions.tidy') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -71,6 +74,10 @@
|
||||
<span class="servant-card__label">{{ $t('falukant.house.servants.householdOrder') }}</span>
|
||||
<strong>{{ userHouse.householdOrder || 0 }}</strong>
|
||||
</article>
|
||||
<article class="servant-card">
|
||||
<span class="servant-card__label">{{ $t('falukant.family.householdTension.score') }}</span>
|
||||
<strong>{{ userHouse.householdTensionScore ?? 0 }}</strong>
|
||||
</article>
|
||||
<article class="servant-card">
|
||||
<span class="servant-card__label">{{ $t('falukant.house.servants.staffingState.label') }}</span>
|
||||
<strong>{{ $t(`falukant.house.servants.staffingState.${servantSummary.staffingState || 'fitting'}`) }}</strong>
|
||||
@@ -91,6 +98,14 @@
|
||||
<strong>{{ $t(`falukant.house.servants.orderState.${servantSummary.orderState || 'stable'}`) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="Array.isArray(userHouse.householdTensionReasonsJson) && userHouse.householdTensionReasonsJson.length > 0" class="servants-reasons">
|
||||
<span class="servants-reasons__label">{{ $t('falukant.family.householdTension.reasonsLabel') }}</span>
|
||||
<div class="servants-reasons__list">
|
||||
<span v-for="reason in userHouse.householdTensionReasonsJson" :key="reason" class="servants-reasons__badge">
|
||||
{{ $t('falukant.family.householdTension.reasons.' + reason) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="buyable-houses">
|
||||
@@ -299,6 +314,16 @@ export default {
|
||||
showError(this, this.$t('falukant.house.servants.actions.payLevelError'));
|
||||
}
|
||||
},
|
||||
async tidyHousehold() {
|
||||
try {
|
||||
await apiClient.post('/api/falukant/houses/order');
|
||||
await this.loadData();
|
||||
showSuccess(this, this.$t('falukant.house.servants.actions.tidySuccess'));
|
||||
} catch (err) {
|
||||
console.error('Error tidying household', err);
|
||||
showError(this, this.$t('falukant.house.servants.actions.tidyError'));
|
||||
}
|
||||
},
|
||||
handleDaemonMessage(evt) {
|
||||
try {
|
||||
const msg = JSON.parse(evt.data);
|
||||
@@ -344,6 +369,17 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
/* AppContent gibt dem letzten Kind flex:1 + min-height:0 — sonst schrumpfen
|
||||
Spalten-Kinder und überlagern sich (z. B. „Dienerschaft“ unter „Kaufe ein Haus“). */
|
||||
flex: 0 0 auto;
|
||||
min-height: min-content;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.existing-house,
|
||||
.servants-panel,
|
||||
.buyable-houses {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@@ -442,6 +478,34 @@ h2 {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.servants-reasons {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.servants-reasons__label {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.servants-reasons__list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.servants-reasons__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(188, 84, 61, 0.12);
|
||||
color: #9a3c26;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.buyable-houses {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
</div>
|
||||
|
||||
<section v-if="falukantUser?.character" class="falukant-summary-grid">
|
||||
<article class="summary-card surface-card">
|
||||
<span class="summary-card__label">{{ $t('falukant.overview.metadata.certificate') }}</span>
|
||||
<strong>{{ falukantUser?.certificate ?? '---' }}</strong>
|
||||
<p>Bestimmt, welche Produktkategorien du derzeit herstellen darfst.</p>
|
||||
</article>
|
||||
<article class="summary-card surface-card">
|
||||
<span class="summary-card__label">Niederlassungen</span>
|
||||
<strong>{{ branchCount }}</strong>
|
||||
@@ -109,6 +114,10 @@
|
||||
<span>{{ $t('falukant.overview.metadata.mainbranch') }}</span>
|
||||
<strong>{{ falukantUser?.mainBranchRegion?.name }}</strong>
|
||||
</div>
|
||||
<div class="detail-list__item">
|
||||
<span>{{ $t('falukant.overview.metadata.certificate') }}</span>
|
||||
<strong>{{ falukantUser?.certificate ?? '---' }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="overview-panel surface-card">
|
||||
@@ -347,6 +356,7 @@ export default {
|
||||
this.socket.off("falukantUserUpdated", this.fetchFalukantUser);
|
||||
this.socket.off("falukantUpdateStatus");
|
||||
this.socket.off("falukantUpdateFamily");
|
||||
this.socket.off("falukantUpdateProductionCertificate");
|
||||
this.socket.off("children_update");
|
||||
this.socket.off("falukantBranchUpdate");
|
||||
this.socket.off("stock_change");
|
||||
@@ -362,6 +372,9 @@ export default {
|
||||
this.socket.on("falukantUpdateFamily", (data) => {
|
||||
this.handleEvent({ event: 'falukantUpdateFamily', ...data });
|
||||
});
|
||||
this.socket.on("falukantUpdateProductionCertificate", (data) => {
|
||||
this.handleEvent({ event: 'falukantUpdateProductionCertificate', ...data });
|
||||
});
|
||||
this.socket.on("children_update", (data) => {
|
||||
this.handleEvent({ event: 'children_update', ...data });
|
||||
});
|
||||
@@ -428,6 +441,7 @@ export default {
|
||||
switch (eventData.event) {
|
||||
case 'falukantUpdateStatus':
|
||||
case 'falukantUpdateFamily':
|
||||
case 'falukantUpdateProductionCertificate':
|
||||
case 'children_update':
|
||||
case 'falukantBranchUpdate':
|
||||
this.queueOverviewRefresh();
|
||||
|
||||
Reference in New Issue
Block a user