- Added detailed logging for partner search and creation processes in FalukantService to improve traceability and debugging. - Refactored the partner search logic to use a dynamic where clause for better readability and maintainability. - Implemented error handling in FamilyView's loadGifts method to ensure an empty array is returned on API errors, enhancing user experience.
516 lines
19 KiB
Vue
516 lines
19 KiB
Vue
<template>
|
|
<div class="contenthidden">
|
|
<StatusBar />
|
|
<div class="contentscroll">
|
|
|
|
<h2>{{ $t('falukant.family.title') }}</h2>
|
|
|
|
<div class="spouse-section">
|
|
<h3>{{ $t('falukant.family.spouse.title') }}</h3>
|
|
<div v-if="relationships.length > 0">
|
|
<div class="relationship">
|
|
<table>
|
|
<tr>
|
|
<td>{{ $t('falukant.family.relationships.name') }}</td>
|
|
<td>
|
|
{{ $t('falukant.titles.' + relationships[0].character2.gender + '.' +
|
|
relationships[0].character2.nobleTitle) }}
|
|
{{ relationships[0].character2.firstName }}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>{{ $t('falukant.family.spouse.age') }}</td>
|
|
<td>{{ relationships[0].character2.age }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>{{ $t('falukant.family.spouse.mood') }}</td>
|
|
<td>{{ $t(`falukant.mood.${relationships[0].character2.mood.tr}`) }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td>{{ $t('falukant.family.spouse.status') }}</td>
|
|
<td>{{ $t('falukant.family.statuses.' + relationships[0].relationshipType) }}</td>
|
|
</tr>
|
|
<tr v-if="relationships[0].relationshipType === 'wooing'">
|
|
<td>{{ $t('falukant.family.spouse.progress') }}</td>
|
|
<td>
|
|
<div class="progress">
|
|
<div class="progress-inner" :style="{
|
|
width: relationships[0].progress + '%',
|
|
backgroundColor: progressColor(relationships[0].progress)
|
|
}"></div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr v-if="relationships[0].relationshipType === 'engaged'" colspan="2">
|
|
<button @click="jumpToPartyForm">{{ $t('falukant.family.spouse.jumpToPartyForm')
|
|
}}</button>
|
|
</tr>
|
|
</table>
|
|
<ul>
|
|
<li v-for="trait in relationships[0].character2.traits" :key="trait.id">
|
|
{{ $t(`falukant.character.${trait.tr}`) }}
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div v-if="relationships[0].relationshipType === 'wooing'">
|
|
<h3>{{ $t('falukant.family.spouse.wooing.gifts') }}</h3>
|
|
<table class="spouse-table">
|
|
<thead>
|
|
<tr>
|
|
<th></th>
|
|
<th>{{ $t('falukant.family.spouse.wooing.gift') }}</th>
|
|
<th>{{ $t('falukant.family.spouse.wooing.effect') }}</th>
|
|
<th>{{ $t('falukant.family.spouse.wooing.value') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="gift in gifts" :key="gift.id">
|
|
<td>
|
|
<input type="radio" name="gift" :value="gift.id" v-model="selectedGiftId" />
|
|
</td>
|
|
<td>{{ $t(`falukant.gifts.${gift.name}`) }}</td>
|
|
<td>{{ getEffect(gift) }}</td>
|
|
<td>{{ formatCost(gift.cost) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<div>
|
|
<button @click="sendGift" class="button">{{ $t('falukant.family.spouse.wooing.sendGift')
|
|
}}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="proposals && proposals.length > 0">
|
|
<table class="spouse-table">
|
|
<thead>
|
|
<tr>
|
|
<th>{{ $t('falukant.family.spouse.select') }}</th>
|
|
<th>{{ $t('falukant.family.spouse.name') }}</th>
|
|
<th>{{ $t('falukant.family.spouse.age') }}</th>
|
|
<th>{{ $t('falukant.family.spouse.marriagecost') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="proposal in proposals" :key="proposal.id">
|
|
<td><input type="radio" name="spouse" :value="proposal.proposedCharacterId"
|
|
v-model="selectedProposalId"></td>
|
|
<td>{{
|
|
$t(`falukant.titles.${proposal.proposedCharacterGender}.${proposal.proposedCharacterNobleTitle}`)
|
|
}} {{ proposal.proposedCharacterName }}</td>
|
|
<td>{{ proposal.proposedCharacterAge }}</td>
|
|
<td>{{ formatCost(proposal.cost) }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<div>{{ $t('falukant.family.spouse.notice') }}</div>
|
|
<div v-if="selectedProposalId">
|
|
<button @click="acceptProposal">{{ $t('falukant.family.spouse.accept') }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="children-section">
|
|
<h3>{{ $t('falukant.family.children.title') }}</h3>
|
|
<div v-if="children && children.length > 0">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>{{ $t('falukant.family.children.name') }}</th>
|
|
<th>{{ $t('falukant.family.children.age') }}</th>
|
|
<th>{{ $t('falukant.family.children.heir') }}</th>
|
|
<th>{{ $t('falukant.family.children.actions') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(child, index) in children" :key="index">
|
|
<td v-if="child.hasName">
|
|
{{ child.name }}
|
|
</td>
|
|
<td v-else>
|
|
<button @click="jumpToChurchForm">{{ $t('falukant.family.children.baptism')
|
|
}}</button>
|
|
</td>
|
|
<td>{{ child.age }}</td>
|
|
<td>
|
|
<span v-if="child.isHeir" class="heir-badge">{{ $t('falukant.family.children.isHeir') }}</span>
|
|
<button v-else-if="child.hasName" @click="setAsHeir(child)" class="set-heir-button">
|
|
{{ $t('falukant.family.children.setAsHeir') }}
|
|
</button>
|
|
</td>
|
|
<td>
|
|
<button @click="showChildDetails(child)">
|
|
{{ $t('falukant.family.children.detailButton') }}
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div v-else>
|
|
<p>{{ $t('falukant.family.children.none') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Liebhaber / Geliebte -->
|
|
<div class="lovers-section">
|
|
<h3>{{ $t('falukant.family.lovers.title') }}</h3>
|
|
<div v-if="lovers && lovers.length > 0">
|
|
<ul>
|
|
<li v-for="(lover, idx) in lovers" :key="idx">
|
|
{{ $t('falukant.titles.' + lover.gender + '.' + lover.title) }} {{ lover.name }}
|
|
({{ $t('falukant.family.lovers.affection') }}: {{ lover.affection }})
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div v-else>
|
|
<p>{{ $t('falukant.family.lovers.none') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<ChildDetailsDialog ref="childDetailsDialog" />
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import StatusBar from '@/components/falukant/StatusBar.vue'
|
|
import MessageDialog from '@/dialogues/standard/MessageDialog.vue'
|
|
import ErrorDialog from '@/dialogues/standard/ErrorDialog.vue'
|
|
import ChildDetailsDialog from '@/dialogues/falukant/ChildDetailsDialog.vue'
|
|
|
|
import apiClient from '@/utils/axios.js'
|
|
import { mapState } from 'vuex'
|
|
|
|
export default {
|
|
name: 'FamilyView',
|
|
components: {
|
|
StatusBar,
|
|
MessageDialog,
|
|
ErrorDialog,
|
|
ChildDetailsDialog
|
|
},
|
|
data() {
|
|
return {
|
|
relationships: [],
|
|
children: [],
|
|
lovers: [],
|
|
deathPartners: [],
|
|
proposals: [],
|
|
selectedProposalId: null,
|
|
gifts: [],
|
|
selectedGiftId: null,
|
|
moodAffects: [],
|
|
characterAffects: []
|
|
}
|
|
},
|
|
computed: {
|
|
...mapState(['socket'])
|
|
},
|
|
async mounted() {
|
|
await this.loadFamilyData();
|
|
await this.loadGifts();
|
|
await this.loadMoodAffects();
|
|
await this.loadCharacterAffects();
|
|
this.setupSocketEvents();
|
|
},
|
|
methods: {
|
|
setupSocketEvents() {
|
|
if (this.socket) {
|
|
this.socket.on('falukantUpdateStatus', (data) => {
|
|
this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
|
});
|
|
this.socket.on('familychanged', (data) => {
|
|
this.handleEvent({ event: 'familychanged', ...data });
|
|
});
|
|
} else {
|
|
setTimeout(() => this.setupSocketEvents(), 1000);
|
|
}
|
|
},
|
|
handleEvent(eventData) {
|
|
switch (eventData.event) {
|
|
case 'falukantUpdateStatus':
|
|
case 'familychanged':
|
|
this.loadFamilyData();
|
|
break;
|
|
}
|
|
},
|
|
async loadFamilyData() {
|
|
try {
|
|
const response = await apiClient.get('/api/falukant/family');
|
|
this.relationships = response.data.relationships;
|
|
this.children = response.data.children;
|
|
this.lovers = response.data.lovers;
|
|
this.proposals = response.data.possiblePartners;
|
|
this.deathPartners = response.data.deathPartners;
|
|
} catch (error) {
|
|
console.error('Error loading family data:', error);
|
|
}
|
|
},
|
|
|
|
showChildDetails(child) {
|
|
this.$refs.childDetailsDialog?.open(child);
|
|
},
|
|
|
|
async setAsHeir(child) {
|
|
if (!child.childCharacterId) {
|
|
console.error('Child character ID missing');
|
|
return;
|
|
}
|
|
try {
|
|
await apiClient.post('/api/falukant/family/set-heir', {
|
|
childCharacterId: child.childCharacterId
|
|
});
|
|
await this.loadFamilyData();
|
|
this.$root.$refs.messageDialog?.open('tr:falukant.family.children.heirSetSuccess');
|
|
} catch (error) {
|
|
console.error('Error setting heir:', error);
|
|
this.$root.$refs.errorDialog?.open('tr:falukant.family.children.heirSetError');
|
|
}
|
|
},
|
|
|
|
formatCost(value) {
|
|
return new Intl.NumberFormat(navigator.language, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value);
|
|
},
|
|
|
|
getEffect(gift) {
|
|
const relationship = this.relationships[0];
|
|
const partner = relationship.character2;
|
|
const currentMoodId = partner.mood?.id ?? partner.mood_id;
|
|
const moodEntry = gift.moodsAffects.find(ma => ma.mood_id === currentMoodId);
|
|
const moodValue = moodEntry ? moodEntry.suitability : 0;
|
|
let highestCharacterValue = 0;
|
|
for (const trait of partner.characterTrait) {
|
|
const charEntry = gift.charactersAffects.find(ca => ca.trait_id === trait.id);
|
|
if (charEntry && charEntry.suitability > highestCharacterValue) {
|
|
highestCharacterValue = charEntry.suitability;
|
|
}
|
|
}
|
|
return Math.round((moodValue + highestCharacterValue) / 2);
|
|
},
|
|
|
|
async acceptProposal() {
|
|
const response = await apiClient.post('/api/falukant/family/acceptmarriageproposal'
|
|
, { proposalId: this.selectedProposalId });
|
|
this.loadFamilyData();
|
|
},
|
|
|
|
async loadGifts() {
|
|
try {
|
|
const response = await apiClient.get('/api/falukant/family/gifts');
|
|
this.gifts = response.data || [];
|
|
} catch (error) {
|
|
console.error('Error loading gifts:', error);
|
|
this.gifts = []; // Leeres Array bei Fehler
|
|
}
|
|
},
|
|
|
|
async sendGift() {
|
|
if (!this.selectedGiftId) {
|
|
this.$root.$refs.errorDialog.open(`tr:falukant.family.sendgift.error.nogiftselected`);
|
|
return;
|
|
}
|
|
try {
|
|
await apiClient.post('/api/falukant/family/gift'
|
|
, { giftId: this.selectedGiftId });
|
|
this.loadFamilyData();
|
|
this.$root.$refs.messageDialog.open('tr:falukant.family.sendgift.success');
|
|
} catch (error) {
|
|
console.log(error.response);
|
|
if (error.response.status === 412) {
|
|
this.$root.$refs.errorDialog.open(`tr:falukant.family.sendgift.error.${error.response.data.error}`);
|
|
} else {
|
|
this.$root.$refs.errorDialog.open(`tr:falukant.family.sendgift.error.generic`);
|
|
}
|
|
}
|
|
},
|
|
|
|
async loadMoodAffects() {
|
|
try {
|
|
const response = await apiClient.get('/api/falukant/mood/affect');
|
|
this.moodAffects = response.data;
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
},
|
|
|
|
async loadCharacterAffects() {
|
|
try {
|
|
const response = await apiClient.get('/api/falukant/character/affect');
|
|
this.characterAffects = response.data;
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
},
|
|
|
|
progressColor(p) {
|
|
const pct = Math.max(0, Math.min(100, p)) / 100;
|
|
const red = Math.round(255 * (1 - pct));
|
|
const green = Math.round(255 * pct);
|
|
return `rgb(${red}, ${green}, 0)`;
|
|
},
|
|
|
|
jumpToPartyForm() {
|
|
this.$router.push({
|
|
name: 'ReputationView',
|
|
query: { tab: 'party' }
|
|
});
|
|
},
|
|
|
|
jumpToChurchForm() {
|
|
this.$router.push({
|
|
name: 'ChurchView',
|
|
});
|
|
},
|
|
|
|
handleDaemonMessage(event) {
|
|
if (event.data === 'ping') {
|
|
return;
|
|
}
|
|
const message = JSON.parse(event.data);
|
|
if (message.event === 'children_update') {
|
|
this.loadFamilyData();
|
|
}
|
|
},
|
|
|
|
getEffect(gift) {
|
|
// aktueller Partner
|
|
const partner = this.relationships[0].character2;
|
|
// seine aktuelle Mood-ID
|
|
const moodId = partner.mood?.id ?? partner.mood_id;
|
|
|
|
// 1) Mood-Eintrag finden
|
|
const moodEntry = gift.moodsAffects.find(ma => ma.mood_id === moodId);
|
|
const moodValue = moodEntry ? moodEntry.suitability : 0;
|
|
|
|
// 2) Trait-Einträge matchen
|
|
let highestTraitValue = 0;
|
|
for (const trait of partner.traits) {
|
|
const charEntry = gift.charactersAffects.find(ca => ca.trait_id === trait.id);
|
|
if (charEntry && charEntry.suitability > highestTraitValue) {
|
|
highestTraitValue = charEntry.suitability;
|
|
}
|
|
}
|
|
|
|
// Durchschnitt, gerundet
|
|
return Math.round((moodValue + highestTraitValue) / 2);
|
|
},
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.spouse-section,
|
|
.children-section,
|
|
.lovers-section {
|
|
border: 1px solid #ccc;
|
|
margin: 10px 0;
|
|
border-radius: 4px;
|
|
padding: 10px;
|
|
}
|
|
|
|
.spouse-section table,
|
|
.children-section table {
|
|
margin-top: 10px;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.spouse-section th,
|
|
.spouse-section td,
|
|
.children-section th,
|
|
.children-section td {
|
|
border: 1px solid #ddd;
|
|
padding: 8px;
|
|
text-align: left;
|
|
}
|
|
|
|
.spouse-section th {
|
|
background-color: #f2f2f2;
|
|
}
|
|
|
|
.children-section th {
|
|
background-color: #f9f9f9;
|
|
}
|
|
|
|
.lovers-section {
|
|
ul {
|
|
list-style: none;
|
|
padding-left: 0;
|
|
}
|
|
|
|
li {
|
|
padding: 4px 0;
|
|
}
|
|
}
|
|
|
|
.spouse-table th,
|
|
.spouse-table td {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.spouse-table th:first-child,
|
|
.spouse-table td:first-child {
|
|
width: 20px;
|
|
}
|
|
|
|
.spouse-table th:nth-child(3),
|
|
.spouse-table td:nth-child(3) {
|
|
width: 30px;
|
|
}
|
|
|
|
.spouse-table th:nth-child(4),
|
|
.spouse-table td:nth-child(4) {
|
|
width: 50px;
|
|
}
|
|
|
|
h2 {
|
|
padding-top: 20px;
|
|
}
|
|
|
|
.relationship>table,
|
|
.relationship>ul {
|
|
display: inline-block;
|
|
margin-right: 1em;
|
|
vertical-align: top;
|
|
}
|
|
|
|
.relationship>ul {
|
|
list-style: none;
|
|
}
|
|
|
|
.progress {
|
|
width: 100%;
|
|
background-color: #e5e7eb;
|
|
border-radius: 0.25rem;
|
|
overflow: hidden;
|
|
height: 1rem;
|
|
}
|
|
|
|
.progress-inner {
|
|
height: 100%;
|
|
transition: width 0.3s ease, background-color 0.3s ease;
|
|
}
|
|
|
|
.heir-badge {
|
|
display: inline-block;
|
|
padding: 4px 8px;
|
|
background-color: #4CAF50;
|
|
color: white;
|
|
border-radius: 4px;
|
|
font-size: 0.9em;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.set-heir-button {
|
|
padding: 4px 8px;
|
|
background-color: #28a745;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
.set-heir-button:hover {
|
|
background-color: #218838;
|
|
}
|
|
</style> |