Add heir selection functionality in Falukant module
- Implemented getPotentialHeirs and selectHeir methods in FalukantService to allow users to retrieve and select potential heirs based on specific criteria. - Updated FalukantController to wrap new methods with user authentication and added corresponding routes in FalukantRouter. - Enhanced OverviewView component to display heir selection UI when no character is present, including loading states and error handling. - Added translations for heir selection messages in both German and English locales to improve user experience.
This commit is contained in:
@@ -93,6 +93,8 @@ class FalukantController {
|
|||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
this.setHeir = this._wrapWithUser((userId, req) => this.service.setHeir(userId, req.body.childCharacterId));
|
this.setHeir = this._wrapWithUser((userId, req) => this.service.setHeir(userId, req.body.childCharacterId));
|
||||||
|
this.getPotentialHeirs = this._wrapWithUser((userId) => this.service.getPotentialHeirs(userId));
|
||||||
|
this.selectHeir = this._wrapWithUser((userId, req) => this.service.selectHeir(userId, req.body.heirId));
|
||||||
this.acceptMarriageProposal = this._wrapWithUser((userId, req) => this.service.acceptMarriageProposal(userId, req.body.proposalId));
|
this.acceptMarriageProposal = this._wrapWithUser((userId, req) => this.service.acceptMarriageProposal(userId, req.body.proposalId));
|
||||||
this.getGifts = this._wrapWithUser((userId) => {
|
this.getGifts = this._wrapWithUser((userId) => {
|
||||||
console.log('🔍 getGifts called with userId:', userId);
|
console.log('🔍 getGifts called with userId:', userId);
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ router.get('/directors', falukantController.getAllDirectors);
|
|||||||
router.post('/directors', falukantController.updateDirector);
|
router.post('/directors', falukantController.updateDirector);
|
||||||
router.post('/family/acceptmarriageproposal', falukantController.acceptMarriageProposal);
|
router.post('/family/acceptmarriageproposal', falukantController.acceptMarriageProposal);
|
||||||
router.post('/family/set-heir', falukantController.setHeir);
|
router.post('/family/set-heir', falukantController.setHeir);
|
||||||
|
router.get('/heirs/potential', falukantController.getPotentialHeirs);
|
||||||
|
router.post('/heirs/select', falukantController.selectHeir);
|
||||||
router.get('/family/gifts', falukantController.getGifts);
|
router.get('/family/gifts', falukantController.getGifts);
|
||||||
router.get('/family/children', falukantController.getChildren);
|
router.get('/family/children', falukantController.getChildren);
|
||||||
router.post('/family/gift', falukantController.sendGift);
|
router.post('/family/gift', falukantController.sendGift);
|
||||||
|
|||||||
@@ -2925,6 +2925,107 @@ class FalukantService extends BaseService {
|
|||||||
return { success: true, childCharacterId };
|
return { success: true, childCharacterId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getPotentialHeirs(hashedUserId) {
|
||||||
|
const user = await getFalukantUserOrFail(hashedUserId);
|
||||||
|
|
||||||
|
// Prüfe, ob der User bereits einen Charakter hat
|
||||||
|
const existingCharacter = await FalukantCharacter.findOne({ where: { userId: user.id } });
|
||||||
|
if (existingCharacter) {
|
||||||
|
throw new Error('User already has a character');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user.mainBranchRegionId) {
|
||||||
|
throw new Error('User has no main branch region');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hole den noncivil Titel
|
||||||
|
const noncivilTitle = await TitleOfNobility.findOne({ where: { labelTr: 'noncivil' } });
|
||||||
|
if (!noncivilTitle) {
|
||||||
|
throw new Error('Noncivil title not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Berechne das Datum für 10-14 Jahre alt (in Tagen)
|
||||||
|
const now = new Date();
|
||||||
|
now.setHours(0, 0, 0, 0);
|
||||||
|
const minDate = new Date(now);
|
||||||
|
minDate.setDate(minDate.getDate() - 14); // 14 Tage = 14 Jahre
|
||||||
|
const maxDate = new Date(now);
|
||||||
|
maxDate.setDate(maxDate.getDate() - 10); // 10 Tage = 10 Jahre
|
||||||
|
|
||||||
|
// Hole zufällige Charaktere aus der Hauptregion, die 10-14 Jahre alt sind
|
||||||
|
// und keinen userId haben (also noch keinem User zugeordnet sind)
|
||||||
|
// und den noncivil Titel haben
|
||||||
|
const potentialHeirs = await FalukantCharacter.findAll({
|
||||||
|
where: {
|
||||||
|
regionId: user.mainBranchRegionId,
|
||||||
|
userId: null,
|
||||||
|
titleOfNobility: noncivilTitle.id,
|
||||||
|
birthdate: {
|
||||||
|
[Op.between]: [minDate, maxDate]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: [
|
||||||
|
{ model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] },
|
||||||
|
{ model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] }
|
||||||
|
],
|
||||||
|
order: Sequelize.fn('RANDOM'),
|
||||||
|
limit: 5
|
||||||
|
});
|
||||||
|
|
||||||
|
// Berechne das Alter für jeden Charakter
|
||||||
|
return potentialHeirs.map(heir => {
|
||||||
|
const age = calcAge(heir.birthdate);
|
||||||
|
return {
|
||||||
|
id: heir.id,
|
||||||
|
definedFirstName: heir.definedFirstName,
|
||||||
|
definedLastName: heir.definedLastName,
|
||||||
|
gender: heir.gender,
|
||||||
|
age: age
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectHeir(hashedUserId, heirId) {
|
||||||
|
const user = await getFalukantUserOrFail(hashedUserId);
|
||||||
|
|
||||||
|
// Prüfe, ob der User bereits einen Charakter hat
|
||||||
|
const existingCharacter = await FalukantCharacter.findOne({ where: { userId: user.id } });
|
||||||
|
if (existingCharacter) {
|
||||||
|
throw new Error('User already has a character');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hole den Erben-Charakter
|
||||||
|
const heir = await FalukantCharacter.findOne({
|
||||||
|
where: {
|
||||||
|
id: heirId,
|
||||||
|
userId: null // Stelle sicher, dass er noch keinem User zugeordnet ist
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!heir) {
|
||||||
|
throw new Error('Heir not found or already assigned');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfe, ob der Erbe in der Hauptregion ist
|
||||||
|
if (heir.regionId !== user.mainBranchRegionId) {
|
||||||
|
throw new Error('Heir is not in main branch region');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfe das Alter (10-14 Jahre)
|
||||||
|
const age = calcAge(heir.birthdate);
|
||||||
|
if (age < 10 || age > 14) {
|
||||||
|
throw new Error('Heir age is not between 10 and 14');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weise den Charakter dem User zu
|
||||||
|
await heir.update({ userId: user.id });
|
||||||
|
|
||||||
|
// Benachrichtige den User
|
||||||
|
notifyUser(hashedUserId, 'falukantUserUpdated', {});
|
||||||
|
|
||||||
|
return { success: true, characterId: heir.id };
|
||||||
|
}
|
||||||
|
|
||||||
async getPossiblePartners(requestingCharacterId) {
|
async getPossiblePartners(requestingCharacterId) {
|
||||||
const proposals = await MarriageProposal.findAll({
|
const proposals = await MarriageProposal.findAll({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -135,6 +135,14 @@
|
|||||||
"store": "Verkauf",
|
"store": "Verkauf",
|
||||||
"fullstack": "Produktion mit Verkauf"
|
"fullstack": "Produktion mit Verkauf"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"heirSelection": {
|
||||||
|
"title": "Charakter verloren - Erben auswählen",
|
||||||
|
"description": "Dein Charakter wurde durch einen Fehler verloren. Bitte wähle einen Erben aus deiner Hauptregion aus, um fortzufahren.",
|
||||||
|
"loading": "Lade mögliche Erben...",
|
||||||
|
"noHeirs": "Es wurden keine passenden Erben gefunden.",
|
||||||
|
"select": "Als Erben wählen",
|
||||||
|
"error": "Fehler beim Auswählen des Erben."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"titles": {
|
"titles": {
|
||||||
|
|||||||
@@ -206,6 +206,39 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"overview": {
|
||||||
|
"title": "Falukant - Overview",
|
||||||
|
"metadata": {
|
||||||
|
"title": "Personal",
|
||||||
|
"name": "Name",
|
||||||
|
"money": "Wealth",
|
||||||
|
"age": "Age",
|
||||||
|
"mainbranch": "Home City",
|
||||||
|
"nobleTitle": "Status"
|
||||||
|
},
|
||||||
|
"productions": {
|
||||||
|
"title": "Productions"
|
||||||
|
},
|
||||||
|
"stock": {
|
||||||
|
"title": "Stock"
|
||||||
|
},
|
||||||
|
"branches": {
|
||||||
|
"title": "Branches",
|
||||||
|
"level": {
|
||||||
|
"production": "Production",
|
||||||
|
"store": "Store",
|
||||||
|
"fullstack": "Production with Store"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"heirSelection": {
|
||||||
|
"title": "Character Lost - Select Heir",
|
||||||
|
"description": "Your character was lost due to an error. Please select an heir from your main region to continue.",
|
||||||
|
"loading": "Loading potential heirs...",
|
||||||
|
"noHeirs": "No suitable heirs were found.",
|
||||||
|
"select": "Select as Heir",
|
||||||
|
"error": "Error selecting heir."
|
||||||
|
}
|
||||||
|
},
|
||||||
"nobility": {
|
"nobility": {
|
||||||
"cooldown": "You can only advance again on {date}."
|
"cooldown": "You can only advance again on {date}."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,19 +2,45 @@
|
|||||||
<div>
|
<div>
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
<h2>{{ $t('falukant.overview.title') }}</h2>
|
<h2>{{ $t('falukant.overview.title') }}</h2>
|
||||||
<div class="overviewcontainer">
|
|
||||||
|
<!-- Erben-Auswahl wenn kein Charakter vorhanden -->
|
||||||
|
<div v-if="!falukantUser?.character" class="heir-selection-container">
|
||||||
|
<h3>{{ $t('falukant.overview.heirSelection.title') }}</h3>
|
||||||
|
<p>{{ $t('falukant.overview.heirSelection.description') }}</p>
|
||||||
|
<div v-if="loadingHeirs" class="loading">{{ $t('falukant.overview.heirSelection.loading') }}</div>
|
||||||
|
<div v-else-if="potentialHeirs.length === 0" class="no-heirs">
|
||||||
|
{{ $t('falukant.overview.heirSelection.noHeirs') }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="heirs-list">
|
||||||
|
<div v-for="heir in potentialHeirs" :key="heir.id" class="heir-card">
|
||||||
|
<div class="heir-info">
|
||||||
|
<div class="heir-name">
|
||||||
|
{{ $t(`falukant.titles.${heir.gender}.noncivil`) }}
|
||||||
|
{{ heir.definedFirstName.name }} {{ heir.definedLastName.name }}
|
||||||
|
</div>
|
||||||
|
<div class="heir-age">{{ $t('falukant.overview.metadata.age') }}: {{ heir.age }}</div>
|
||||||
|
</div>
|
||||||
|
<button @click="selectHeir(heir.id)" class="select-heir-button">
|
||||||
|
{{ $t('falukant.overview.heirSelection.select') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Normale Übersicht wenn Charakter vorhanden -->
|
||||||
|
<div v-else class="overviewcontainer">
|
||||||
<div>
|
<div>
|
||||||
<h3>{{ $t('falukant.overview.metadata.title') }}</h3>
|
<h3>{{ $t('falukant.overview.metadata.title') }}</h3>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $t('falukant.overview.metadata.name') }}</td>
|
<td>{{ $t('falukant.overview.metadata.name') }}</td>
|
||||||
<td>{{ falukantUser?.character.definedFirstName.name }} {{
|
<td>{{ falukantUser?.character?.definedFirstName?.name }} {{
|
||||||
falukantUser?.character.definedLastName.name }}</td>
|
falukantUser?.character?.definedLastName?.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $t('falukant.overview.metadata.nobleTitle') }}</td>
|
<td>{{ $t('falukant.overview.metadata.nobleTitle') }}</td>
|
||||||
<td>{{ $t('falukant.titles.' + falukantUser?.character.gender + '.' +
|
<td>{{ $t('falukant.titles.' + falukantUser?.character?.gender + '.' +
|
||||||
falukantUser?.character.nobleTitle.labelTr) }}</td>
|
falukantUser?.character?.nobleTitle?.labelTr) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $t('falukant.overview.metadata.money') }}</td>
|
<td>{{ $t('falukant.overview.metadata.money') }}</td>
|
||||||
@@ -26,11 +52,11 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $t('falukant.overview.metadata.age') }}</td>
|
<td>{{ $t('falukant.overview.metadata.age') }}</td>
|
||||||
<td>{{ falukantUser?.character.age }}</td>
|
<td>{{ falukantUser?.character?.age }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $t('falukant.overview.metadata.mainbranch') }}</td>
|
<td>{{ $t('falukant.overview.metadata.mainbranch') }}</td>
|
||||||
<td>{{ falukantUser?.mainBranchRegion.name }}</td>
|
<td>{{ falukantUser?.mainBranchRegion?.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,7 +116,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="imagecontainer">
|
<div v-if="falukantUser?.character" class="imagecontainer">
|
||||||
<div :style="getAvatarStyle" class="avatar"></div>
|
<div :style="getAvatarStyle" class="avatar"></div>
|
||||||
<div :style="getHouseStyle" class="house"></div>
|
<div :style="getHouseStyle" class="house"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -149,12 +175,14 @@ export default {
|
|||||||
falukantUser: null,
|
falukantUser: null,
|
||||||
allStock: [],
|
allStock: [],
|
||||||
productions: [],
|
productions: [],
|
||||||
|
potentialHeirs: [],
|
||||||
|
loadingHeirs: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['socket']),
|
...mapState(['socket']),
|
||||||
getAvatarStyle() {
|
getAvatarStyle() {
|
||||||
if (!this.falukantUser) return {};
|
if (!this.falukantUser || !this.falukantUser.character) return {};
|
||||||
const { gender, age } = this.falukantUser.character;
|
const { gender, age } = this.falukantUser.character;
|
||||||
const imageUrl = `/images/falukant/avatar/${gender}.png`;
|
const imageUrl = `/images/falukant/avatar/${gender}.png`;
|
||||||
const ageGroup = this.getAgeGroup(age);
|
const ageGroup = this.getAgeGroup(age);
|
||||||
@@ -212,8 +240,12 @@ export default {
|
|||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.fetchFalukantUser();
|
await this.fetchFalukantUser();
|
||||||
await this.fetchAllStock();
|
if (!this.falukantUser?.character) {
|
||||||
await this.fetchProductions();
|
await this.fetchPotentialHeirs();
|
||||||
|
} else {
|
||||||
|
await this.fetchAllStock();
|
||||||
|
await this.fetchProductions();
|
||||||
|
}
|
||||||
// Daemon WebSocket deaktiviert - verwende Socket.io für alle Events
|
// Daemon WebSocket deaktiviert - verwende Socket.io für alle Events
|
||||||
this.setupSocketEvents();
|
this.setupSocketEvents();
|
||||||
},
|
},
|
||||||
@@ -306,6 +338,33 @@ export default {
|
|||||||
formatDate(timestamp) {
|
formatDate(timestamp) {
|
||||||
return new Date(timestamp).toLocaleString();
|
return new Date(timestamp).toLocaleString();
|
||||||
},
|
},
|
||||||
|
async fetchPotentialHeirs() {
|
||||||
|
if (!this.falukantUser?.mainBranchRegion?.id) return;
|
||||||
|
this.loadingHeirs = true;
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get('/api/falukant/heirs/potential');
|
||||||
|
this.potentialHeirs = response.data || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching potential heirs:', error);
|
||||||
|
this.potentialHeirs = [];
|
||||||
|
} finally {
|
||||||
|
this.loadingHeirs = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async selectHeir(heirId) {
|
||||||
|
try {
|
||||||
|
await apiClient.post('/api/falukant/heirs/select', { heirId });
|
||||||
|
// Lade User-Daten neu
|
||||||
|
await this.fetchFalukantUser();
|
||||||
|
if (this.falukantUser?.character) {
|
||||||
|
await this.fetchAllStock();
|
||||||
|
await this.fetchProductions();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error selecting heir:', error);
|
||||||
|
alert(this.$t('falukant.overview.heirSelection.error'));
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -348,4 +407,68 @@ export default {
|
|||||||
h2 {
|
h2 {
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.heir-selection-container {
|
||||||
|
border: 2px solid #dc3545;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
background-color: #fff3cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heir-selection-container h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heirs-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heir-card {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: white;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heir-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heir-name {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heir-age {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-heir-button {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-heir-button:hover {
|
||||||
|
background-color: #218838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading, .no-heirs {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user