Spiel erweitert
@@ -20,6 +20,7 @@ class FalukantController {
|
||||
this.getDirectorProposals = this.getDirectorProposals.bind(this);
|
||||
this.convertProposalToDirector = this.convertProposalToDirector.bind(this);
|
||||
this.getDirectorForBranch = this.getDirectorForBranch.bind(this);
|
||||
this.getAllDirectors = this.getAllDirectors.bind(this);
|
||||
this.setSetting = this.setSetting.bind(this);
|
||||
this.getFamily = this.getFamily.bind(this);
|
||||
this.acceptMarriageProposal = this.acceptMarriageProposal.bind(this);
|
||||
@@ -32,6 +33,21 @@ class FalukantController {
|
||||
this.getUserHouse = this.getUserHouse.bind(this);
|
||||
this.getBuyableHouses = this.getBuyableHouses.bind(this);
|
||||
this.buyUserHouse = this.buyUserHouse.bind(this);
|
||||
this.getPartyTypes = this.getPartyTypes.bind(this);
|
||||
this.createParty = this.createParty.bind(this);
|
||||
this.getParties = this.getParties.bind(this);
|
||||
this.getNotBaptisedChildren = this.getNotBaptisedChildren.bind(this);
|
||||
this.baptise = this.baptise.bind(this);
|
||||
this.getEducation = this.getEducation.bind(this);
|
||||
this.getChildren = this.getChildren.bind(this);
|
||||
this.sendToSchool = this.sendToSchool.bind(this);
|
||||
this.getBankOverview = this.getBankOverview.bind(this);
|
||||
this.getBankCredits = this.getBankCredits.bind(this);
|
||||
this.takeBankCredits = this.takeBankCredits.bind(this);
|
||||
this.getNobility = this.getNobility.bind(this);
|
||||
this.advanceNobility = this.advanceNobility.bind(this);
|
||||
this.getHealth = this.getHealth.bind(this);
|
||||
this.healthActivity = this.healthActivity.bind(this);
|
||||
}
|
||||
|
||||
async getUser(req, res) {
|
||||
@@ -324,6 +340,27 @@ class FalukantController {
|
||||
}
|
||||
}
|
||||
|
||||
async getAllDirectors(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const result = await FalukantService.getAllDirectors(hashedUserId);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async updateDirector(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const { directorId, income } = req.body;
|
||||
const result = await FalukantService.updateDirector(hashedUserId, directorId, income);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async setSetting(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
@@ -373,6 +410,17 @@ class FalukantController {
|
||||
}
|
||||
}
|
||||
|
||||
async getChildren(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const result = await FalukantService.getChildren(hashedUserId);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async sendGift(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
@@ -464,6 +512,165 @@ class FalukantController {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getPartyTypes(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const result = await FalukantService.getPartyTypes(hashedUserId);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async createParty(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const { partyTypeId, musicId, banquetteId, nobilityIds, servantRatio } = req.body;
|
||||
const result = await FalukantService.createParty(hashedUserId, partyTypeId, musicId, banquetteId, nobilityIds, servantRatio);
|
||||
res.status(201).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getParties(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const result = await FalukantService.getParties(hashedUserId);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getNotBaptisedChildren(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const result = await FalukantService.getNotBaptisedChildren(hashedUserId);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async baptise(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const { characterId: childId, firstName } = req.body;
|
||||
const result = await FalukantService.baptise(hashedUserId, childId, firstName);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getEducation(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const result = await FalukantService.getEducation(hashedUserId);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async sendToSchool(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const { item, student, studentId } = req.body;
|
||||
const result = await FalukantService.sendToSchool(hashedUserId, item, student, studentId);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getBankOverview(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const result = await FalukantService.getBankOverview(hashedUserId);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getBankCredits(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const result = await FalukantService.getBankCredits(hashedUserId);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async takeBankCredits(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const { height } = req.body;
|
||||
const result = await FalukantService.takeBankCredits(hashedUserId, height);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getNobility(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const result = await FalukantService.getNobility(hashedUserId);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async advanceNobility(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const result = await FalukantService.advanceNobility(hashedUserId);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async getHealth(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const result = await FalukantService.getHealth(hashedUserId);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
async healthActivity(req, res) {
|
||||
try {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const { measureTr: activity } = req.body;
|
||||
const result = await FalukantService.healthActivity(hashedUserId, activity);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FalukantController;
|
||||
|
||||
@@ -105,6 +105,10 @@ const menuStructure = {
|
||||
visible: ["hasfalukantaccount"],
|
||||
path: "/falukant/nobility"
|
||||
},
|
||||
church: {
|
||||
visible: ["hasfalukantaccount"],
|
||||
path: "/falukant/church"
|
||||
},
|
||||
politics: {
|
||||
visible: ["hasfalukantaccount"],
|
||||
path: "/falukant/politics"
|
||||
|
||||
@@ -65,6 +65,26 @@ import PromotionalGiftLog from './falukant/log/promotional_gift.js';
|
||||
import HouseType from './falukant/type/house.js';
|
||||
import BuyableHouse from './falukant/data/buyable_house.js';
|
||||
import UserHouse from './falukant/data/user_house.js';
|
||||
import PartyType from './falukant/type/party.js';
|
||||
import Party from './falukant/data/party.js';
|
||||
import MusicType from './falukant/type/music.js';
|
||||
import BanquetteType from './falukant/type/banquette.js';
|
||||
import PartyInvitedNobility from './falukant/data/partyInvitedNobility.js';
|
||||
import ChildRelation from './falukant/data/child_relation.js';
|
||||
import Learning from './falukant/data/learning.js';
|
||||
import LearnRecipient from './falukant/type/learn_recipient.js';
|
||||
import Credit from './falukant/data/credit.js';
|
||||
import DebtorsPrism from './falukant/data/debtors_prism.js';
|
||||
import HealthActivity from './falukant/log/health_activity.js';
|
||||
import Election from './falukant/data/election.js';
|
||||
import ElectionResult from './falukant/data/election_result.js';
|
||||
import Candidate from './falukant/data/candidate.js';
|
||||
import Vote from './falukant/data/vote.js';
|
||||
import PoliticalOfficeType from './falukant/type/political_office_type.js';
|
||||
import PoliticalOffice from './falukant/data/political_office.js';
|
||||
import PoliticalOfficeBenefit from './falukant/predefine/political_office_benefit.js';
|
||||
import PoliticalOfficeBenefitType from './falukant/type/political_office_benefit_type.js';
|
||||
import PoliticalOfficeRequirement from './falukant/predefine/political_office_prerequisite.js';
|
||||
|
||||
export default function setupAssociations() {
|
||||
// UserParam related associations
|
||||
@@ -315,31 +335,31 @@ export default function setupAssociations() {
|
||||
Notification.belongsTo(FalukantUser, { foreignKey: 'userId', as: 'user' });
|
||||
FalukantUser.hasMany(Notification, { foreignKey: 'userId', as: 'notifications' });
|
||||
|
||||
MarriageProposal.belongsTo(FalukantCharacter, {foreignKey: 'requesterCharacterId', as: 'requesterCharacter', });
|
||||
MarriageProposal.belongsTo(FalukantCharacter, { foreignKey: 'requesterCharacterId', as: 'requesterCharacter', });
|
||||
FalukantCharacter.hasMany(MarriageProposal, { foreignKey: 'requesterCharacterId', as: 'initiatedProposals' });
|
||||
|
||||
MarriageProposal.belongsTo(FalukantCharacter, {foreignKey: 'proposedCharacterId', as: 'proposedCharacter', });
|
||||
FalukantCharacter.hasMany(MarriageProposal, {foreignKey: 'proposedCharacterId', as: 'receivedProposals' });
|
||||
MarriageProposal.belongsTo(FalukantCharacter, { foreignKey: 'proposedCharacterId', as: 'proposedCharacter', });
|
||||
FalukantCharacter.hasMany(MarriageProposal, { foreignKey: 'proposedCharacterId', as: 'receivedProposals' });
|
||||
|
||||
FalukantCharacter.belongsToMany(CharacterTrait, {through: FalukantCharacterTrait, foreignKey: 'character_id', as: 'traits', });
|
||||
CharacterTrait.belongsToMany(FalukantCharacter, {through: FalukantCharacterTrait, foreignKey: 'trait_id', as: 'characters', });
|
||||
FalukantCharacter.belongsToMany(CharacterTrait, { through: FalukantCharacterTrait, foreignKey: 'character_id', as: 'traits', });
|
||||
CharacterTrait.belongsToMany(FalukantCharacter, { through: FalukantCharacterTrait, foreignKey: 'trait_id', as: 'characters', });
|
||||
|
||||
Mood.hasMany(FalukantCharacter, {foreignKey: 'mood_id', as: 'moods'});
|
||||
FalukantCharacter.belongsTo(Mood, {foreignKey: 'mood_id', as: 'mood'});
|
||||
Mood.hasMany(FalukantCharacter, { foreignKey: 'mood_id', as: 'moods' });
|
||||
FalukantCharacter.belongsTo(Mood, { foreignKey: 'mood_id', as: 'mood' });
|
||||
|
||||
PromotionalGift.belongsToMany(CharacterTrait, {through: PromotionalGiftCharacterTrait, foreignKey: 'gift_id', as: 'traits',});
|
||||
CharacterTrait.belongsToMany(PromotionalGift, {through: PromotionalGiftCharacterTrait, foreignKey: 'trait_id', as: 'gifts',});
|
||||
PromotionalGift.belongsToMany(CharacterTrait, { through: PromotionalGiftCharacterTrait, foreignKey: 'gift_id', as: 'traits', });
|
||||
CharacterTrait.belongsToMany(PromotionalGift, { through: PromotionalGiftCharacterTrait, foreignKey: 'trait_id', as: 'gifts', });
|
||||
|
||||
PromotionalGift.belongsToMany(Mood, {through: PromotionalGiftMood, foreignKey: 'gift_id', as: 'moods',});
|
||||
Mood.belongsToMany(PromotionalGift, {through: PromotionalGiftMood, foreignKey: 'mood_id', as: 'gifts',});
|
||||
PromotionalGift.belongsToMany(Mood, { through: PromotionalGiftMood, foreignKey: 'gift_id', as: 'moods', });
|
||||
Mood.belongsToMany(PromotionalGift, { through: PromotionalGiftMood, foreignKey: 'mood_id', as: 'gifts', });
|
||||
|
||||
Relationship.belongsTo(RelationshipType, { foreignKey: 'relationshipTypeId', as: 'relationshipType' });
|
||||
RelationshipType.hasMany(Relationship, { foreignKey: 'relationshipTypeId', as: 'relationships' });
|
||||
|
||||
Relationship.belongsTo(FalukantCharacter, {foreignKey: 'character1Id', as: 'character1', });
|
||||
Relationship.belongsTo(FalukantCharacter, {foreignKey: 'character2Id', as: 'character2', });
|
||||
FalukantCharacter.hasMany(Relationship, {foreignKey: 'character1Id', as: 'relationshipsAsCharacter1', });
|
||||
FalukantCharacter.hasMany(Relationship, {foreignKey: 'character2Id', as: 'relationshipsAsCharacter2', });
|
||||
Relationship.belongsTo(FalukantCharacter, { foreignKey: 'character1Id', as: 'character1', });
|
||||
Relationship.belongsTo(FalukantCharacter, { foreignKey: 'character2Id', as: 'character2', });
|
||||
FalukantCharacter.hasMany(Relationship, { foreignKey: 'character1Id', as: 'relationshipsAsCharacter1', });
|
||||
FalukantCharacter.hasMany(Relationship, { foreignKey: 'character2Id', as: 'relationshipsAsCharacter2', });
|
||||
|
||||
PromotionalGiftLog.belongsTo(PromotionalGift, { foreignKey: 'giftId', as: 'gift' });
|
||||
PromotionalGift.hasMany(PromotionalGiftLog, { foreignKey: 'giftId', as: 'logs' });
|
||||
@@ -367,4 +387,262 @@ export default function setupAssociations() {
|
||||
|
||||
TitleOfNobility.hasMany(HouseType, { foreignKey: 'minimumNobleTitle', as: 'houseTypes' });
|
||||
HouseType.belongsTo(TitleOfNobility, { foreignKey: 'minimumNobleTitle', as: 'titleOfNobility' });
|
||||
|
||||
PartyType.hasMany(Party, { foreignKey: 'partyTypeId', as: 'parties' });
|
||||
Party.belongsTo(PartyType, { foreignKey: 'partyTypeId', as: 'partyType' });
|
||||
|
||||
MusicType.hasMany(Party, { foreignKey: 'musicTypeId', as: 'parties' });
|
||||
Party.belongsTo(MusicType, { foreignKey: 'musicTypeId', as: 'musicType' });
|
||||
|
||||
BanquetteType.hasMany(Party, { foreignKey: 'banquetteTypeId', as: 'parties' });
|
||||
Party.belongsTo(BanquetteType, { foreignKey: 'banquetteTypeId', as: 'banquetteType' });
|
||||
|
||||
FalukantUser.hasMany(Party, { foreignKey: 'falukantUserId', as: 'parties' });
|
||||
Party.belongsTo(FalukantUser, { foreignKey: 'falukantUserId', as: 'partyUser' });
|
||||
|
||||
Party.belongsToMany(TitleOfNobility, {
|
||||
through: PartyInvitedNobility,
|
||||
foreignKey: 'party_id',
|
||||
otherKey: 'title_of_nobility_id',
|
||||
as: 'invitedNobilities',
|
||||
});
|
||||
TitleOfNobility.belongsToMany(Party, {
|
||||
through: PartyInvitedNobility,
|
||||
foreignKey: 'title_of_nobility_id',
|
||||
otherKey: 'party_id',
|
||||
as: 'partiesInvitedTo',
|
||||
});
|
||||
|
||||
ChildRelation.belongsTo(FalukantCharacter, {
|
||||
foreignKey: 'fatherCharacterId',
|
||||
as: 'father'
|
||||
});
|
||||
FalukantCharacter.hasMany(ChildRelation, {
|
||||
foreignKey: 'fatherCharacterId',
|
||||
as: 'childrenFather'
|
||||
});
|
||||
|
||||
ChildRelation.belongsTo(FalukantCharacter, {
|
||||
foreignKey: 'motherCharacterId',
|
||||
as: 'mother'
|
||||
});
|
||||
FalukantCharacter.hasMany(ChildRelation, {
|
||||
foreignKey: 'motherCharacterId',
|
||||
as: 'childrenMother'
|
||||
});
|
||||
|
||||
ChildRelation.belongsTo(FalukantCharacter, {
|
||||
foreignKey: 'childCharacterId',
|
||||
as: 'child'
|
||||
});
|
||||
FalukantCharacter.hasMany(ChildRelation, {
|
||||
foreignKey: 'childCharacterId',
|
||||
as: 'parentRelations'
|
||||
});
|
||||
|
||||
Learning.belongsTo(LearnRecipient, {
|
||||
foreignKey: 'learningRecipientId',
|
||||
as: 'recipient'
|
||||
}
|
||||
);
|
||||
|
||||
LearnRecipient.hasMany(Learning, {
|
||||
foreignKey: 'learningRecipientId',
|
||||
as: 'learnings'
|
||||
});
|
||||
|
||||
Learning.belongsTo(FalukantUser, {
|
||||
foreignKey: 'associatedFalukantUserId',
|
||||
as: 'learner'
|
||||
}
|
||||
);
|
||||
|
||||
FalukantUser.hasMany(Learning, {
|
||||
foreignKey: 'associatedFalukantUserId',
|
||||
as: 'learnings'
|
||||
});
|
||||
|
||||
Learning.belongsTo(ProductType, {
|
||||
foreignKey: 'productId',
|
||||
as: 'productType'
|
||||
});
|
||||
|
||||
ProductType.hasMany(Learning, {
|
||||
foreignKey: 'productId',
|
||||
as: 'learnings'
|
||||
});
|
||||
|
||||
Learning.belongsTo(FalukantCharacter, {
|
||||
foreignKey: 'associatedLearningCharacterId',
|
||||
as: 'learningCharacter'
|
||||
});
|
||||
|
||||
FalukantCharacter.hasMany(Learning, {
|
||||
foreignKey: 'associatedLearningCharacterId',
|
||||
as: 'learningsCharacter'
|
||||
});
|
||||
|
||||
FalukantUser.hasMany(Credit, {
|
||||
foreignKey: 'falukantUserId',
|
||||
as: 'credits'
|
||||
});
|
||||
Credit.belongsTo(FalukantUser, {
|
||||
foreignKey: 'falukantUserId',
|
||||
as: 'user'
|
||||
});
|
||||
|
||||
FalukantCharacter.hasMany(DebtorsPrism, {
|
||||
foreignKey: 'character_id',
|
||||
as: 'debtorsPrisms'
|
||||
});
|
||||
DebtorsPrism.belongsTo(FalukantCharacter, {
|
||||
foreignKey: 'character_id',
|
||||
as: 'character'
|
||||
});
|
||||
|
||||
HealthActivity.belongsTo(FalukantCharacter, {
|
||||
foreignKey: 'character_id',
|
||||
as: 'character'
|
||||
});
|
||||
FalukantCharacter.hasMany(HealthActivity, {
|
||||
foreignKey: 'character_id',
|
||||
as: 'healthActivities'
|
||||
});
|
||||
|
||||
// — Political Offices —
|
||||
|
||||
// predefine requirements for office
|
||||
PoliticalOfficeRequirement.belongsTo(PoliticalOfficeType, {
|
||||
foreignKey: 'officeTypeId',
|
||||
as: 'officeType'
|
||||
});
|
||||
PoliticalOfficeType.hasMany(PoliticalOfficeRequirement, {
|
||||
foreignKey: 'officeTypeId',
|
||||
as: 'requirements'
|
||||
});
|
||||
|
||||
// predefine benefits for office
|
||||
PoliticalOfficeBenefit.belongsTo(
|
||||
PoliticalOfficeBenefitType,
|
||||
{ foreignKey: 'benefitTypeId', as: 'benefitDefinition' }
|
||||
);
|
||||
PoliticalOfficeBenefitType.hasMany(
|
||||
PoliticalOfficeBenefit,
|
||||
{ foreignKey: 'benefitTypeId', as: 'benefitDefinitions' }
|
||||
);
|
||||
|
||||
// tie benefits back to office type
|
||||
PoliticalOfficeBenefit.belongsTo(PoliticalOfficeType, {
|
||||
foreignKey: 'officeTypeId',
|
||||
as: 'officeType'
|
||||
});
|
||||
PoliticalOfficeType.hasMany(PoliticalOfficeBenefit, {
|
||||
foreignKey: 'officeTypeId',
|
||||
as: 'benefits'
|
||||
});
|
||||
|
||||
// actual office holdings
|
||||
PoliticalOffice.belongsTo(PoliticalOfficeType, {
|
||||
foreignKey: 'officeTypeId',
|
||||
as: 'type'
|
||||
});
|
||||
PoliticalOfficeType.hasMany(PoliticalOffice, {
|
||||
foreignKey: 'officeTypeId',
|
||||
as: 'offices'
|
||||
});
|
||||
|
||||
PoliticalOffice.belongsTo(FalukantCharacter, {
|
||||
foreignKey: 'characterId',
|
||||
as: 'holder'
|
||||
});
|
||||
FalukantCharacter.hasMany(PoliticalOffice, {
|
||||
foreignKey: 'characterId',
|
||||
as: 'heldOffices'
|
||||
});
|
||||
|
||||
// elections
|
||||
Election.belongsTo(PoliticalOfficeType, {
|
||||
foreignKey: 'officeTypeId',
|
||||
as: 'officeType'
|
||||
});
|
||||
PoliticalOfficeType.hasMany(Election, {
|
||||
foreignKey: 'officeTypeId',
|
||||
as: 'elections'
|
||||
});
|
||||
|
||||
// candidates in an election
|
||||
Candidate.belongsTo(Election, {
|
||||
foreignKey: 'electionId',
|
||||
as: 'election'
|
||||
});
|
||||
Election.hasMany(Candidate, {
|
||||
foreignKey: 'electionId',
|
||||
as: 'candidates'
|
||||
});
|
||||
|
||||
Candidate.belongsTo(FalukantCharacter, {
|
||||
foreignKey: 'characterId',
|
||||
as: 'character'
|
||||
});
|
||||
FalukantCharacter.hasMany(Candidate, {
|
||||
foreignKey: 'characterId',
|
||||
as: 'candidacies'
|
||||
});
|
||||
|
||||
// votes cast
|
||||
Vote.belongsTo(Election, {
|
||||
foreignKey: 'electionId',
|
||||
as: 'election'
|
||||
});
|
||||
Election.hasMany(Vote, {
|
||||
foreignKey: 'electionId',
|
||||
as: 'votes'
|
||||
});
|
||||
|
||||
Vote.belongsTo(Candidate, {
|
||||
foreignKey: 'candidateId',
|
||||
as: 'candidate'
|
||||
});
|
||||
Candidate.hasMany(Vote, {
|
||||
foreignKey: 'candidateId',
|
||||
as: 'votes'
|
||||
});
|
||||
|
||||
Vote.belongsTo(FalukantCharacter, {
|
||||
foreignKey: 'voterCharacterId',
|
||||
as: 'voter'
|
||||
});
|
||||
FalukantCharacter.hasMany(Vote, {
|
||||
foreignKey: 'voterCharacterId',
|
||||
as: 'votesCast'
|
||||
});
|
||||
|
||||
// election results
|
||||
ElectionResult.belongsTo(Election, {
|
||||
foreignKey: 'electionId',
|
||||
as: 'election'
|
||||
});
|
||||
Election.hasMany(ElectionResult, {
|
||||
foreignKey: 'electionId',
|
||||
as: 'results'
|
||||
});
|
||||
|
||||
ElectionResult.belongsTo(Candidate, {
|
||||
foreignKey: 'candidateId',
|
||||
as: 'candidate'
|
||||
});
|
||||
Candidate.hasMany(ElectionResult, {
|
||||
foreignKey: 'candidateId',
|
||||
as: 'results'
|
||||
});
|
||||
|
||||
PoliticalOffice.belongsTo(RegionData, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'region'
|
||||
});
|
||||
RegionData.hasMany(PoliticalOffice, {
|
||||
foreignKey: 'regionId',
|
||||
as: 'offices'
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
30
backend/models/falukant/data/candidate.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// falukant/data/candidate.js
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class Candidate extends Model {}
|
||||
|
||||
Candidate.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
election_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
character_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Candidate',
|
||||
tableName: 'candidate',
|
||||
schema: 'falukant_data',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default Candidate;
|
||||
45
backend/models/falukant/data/child_relation.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class ChildRelation extends Model {}
|
||||
|
||||
ChildRelation.init(
|
||||
{
|
||||
fatherCharacterId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
motherCharacterId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
childCharacterId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
fatherName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
motherName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
nameSet: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
modelName: 'ChildRelation',
|
||||
tableName: 'child_relation', // exakter Tabellenname
|
||||
schema: 'falukant_data', // exaktes Schema
|
||||
freezeTableName: true, // keine Pluralisierung
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
}
|
||||
);
|
||||
|
||||
export default ChildRelation;
|
||||
37
backend/models/falukant/data/credit.js
Normal file
@@ -0,0 +1,37 @@
|
||||
// models/falukant/data/credit.js
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class Credit extends Model {}
|
||||
|
||||
Credit.init({
|
||||
// aufgenommener Kredit-Betrag
|
||||
amount: {
|
||||
type: DataTypes.DECIMAL(14,2),
|
||||
allowNull: false,
|
||||
},
|
||||
// noch offener Kreditbetrag
|
||||
remainingAmount: {
|
||||
type: DataTypes.DECIMAL(14,2),
|
||||
allowNull: false,
|
||||
},
|
||||
// Zinssatz als Prozentsatz (z.B. 3.5 für 3.5%)
|
||||
interestRate: {
|
||||
type: DataTypes.DECIMAL(5,2),
|
||||
allowNull: false,
|
||||
},
|
||||
// Verknüpfung auf FalukantUser
|
||||
falukantUserId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Credit',
|
||||
tableName: 'credit',
|
||||
schema: 'falukant_data',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default Credit;
|
||||
21
backend/models/falukant/data/debtors_prism.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class DebtorsPrism extends Model {}
|
||||
|
||||
DebtorsPrism.init({
|
||||
// Verknüpfung auf FalukantCharacter
|
||||
characterId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'DebtorsPrism',
|
||||
tableName: 'debtors_prism',
|
||||
schema: 'falukant_data',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default DebtorsPrism;
|
||||
34
backend/models/falukant/data/election.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// falukant/data/election.js
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class Election extends Model {}
|
||||
|
||||
Election.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
political_office_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
date: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
posts_to_fill: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Election',
|
||||
tableName: 'election',
|
||||
schema: 'falukant_data',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default Election;
|
||||
34
backend/models/falukant/data/election_result.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// falukant/data/election_result.js
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class ElectionResult extends Model {}
|
||||
|
||||
ElectionResult.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
election_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
candidate_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
votes_received: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'ElectionResult',
|
||||
tableName: 'election_result',
|
||||
schema: 'falukant_data',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default ElectionResult;
|
||||
48
backend/models/falukant/data/learning.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class Learning extends Model {}
|
||||
|
||||
Learning.init(
|
||||
{
|
||||
learningRecipientId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
productId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
learnAllProducts: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
associatedFalukantUserId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
associatedLearningCharacterId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: null,
|
||||
},
|
||||
learningIsExecuted: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
}
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
modelName: 'Learning',
|
||||
tableName: 'learning',
|
||||
schema: 'falukant_data',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
}
|
||||
);
|
||||
|
||||
export default Learning;
|
||||
30
backend/models/falukant/data/occupied_political_office.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// falukant/data/occupied_political_office.js
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class OccupiedPoliticalOffice extends Model {}
|
||||
|
||||
OccupiedPoliticalOffice.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
political_office_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
character_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'OccupiedPoliticalOffice',
|
||||
tableName: 'occupied_political_office',
|
||||
schema: 'falukant_data',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default OccupiedPoliticalOffice;
|
||||
46
backend/models/falukant/data/party.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class Party extends Model {}
|
||||
|
||||
Party.init({
|
||||
partyTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'party_type_id'
|
||||
},
|
||||
falukantUserId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'falukant_user_id'
|
||||
},
|
||||
musicTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'music_type'
|
||||
},
|
||||
banquetteTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'banquette_type'
|
||||
},
|
||||
servantRatio: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'servant_ratio'
|
||||
},
|
||||
cost: {
|
||||
type: DataTypes.FLOAT,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Party',
|
||||
tableName: 'party',
|
||||
schema: 'falukant_data',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default Party;
|
||||
26
backend/models/falukant/data/partyInvitedNobility.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Model, DataTypes } from 'sequelize'
|
||||
import { sequelize } from '../../../utils/sequelize.js'
|
||||
|
||||
class PartyInvitedNobility extends Model {}
|
||||
|
||||
PartyInvitedNobility.init({
|
||||
partyId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'party_id'
|
||||
},
|
||||
titleOfNobilityId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'title_of_nobility_id'
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'PartyInvitedNobility',
|
||||
tableName: 'party_invited_nobility',
|
||||
schema: 'falukant_data',
|
||||
timestamps: false,
|
||||
underscored: true
|
||||
})
|
||||
|
||||
export default PartyInvitedNobility
|
||||
34
backend/models/falukant/data/political_office.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// backend/models/falukant/data/political_office.js
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class PoliticalOffice extends Model {}
|
||||
|
||||
PoliticalOffice.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
officeTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
characterId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
regionId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'PoliticalOffice',
|
||||
tableName: 'political_office',
|
||||
schema: 'falukant_data',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default PoliticalOffice;
|
||||
39
backend/models/falukant/data/vote.js
Normal file
@@ -0,0 +1,39 @@
|
||||
// falukant/data/vote.js
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class Vote extends Model {}
|
||||
|
||||
Vote.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
election_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
voter_character_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
candidate_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
timestamp: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Vote',
|
||||
tableName: 'vote',
|
||||
schema: 'falukant_data',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default Vote;
|
||||
37
backend/models/falukant/log/health_activity.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class HealthActivity extends Model { }
|
||||
|
||||
HealthActivity.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
characterId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
activityTr: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
cost: {
|
||||
type: DataTypes.FLOAT,
|
||||
allowNull: false,
|
||||
},
|
||||
successPercentage: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'health_activity',
|
||||
tableName: 'health_activity',
|
||||
schema: 'falukant_log',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default HealthActivity;
|
||||
@@ -0,0 +1,45 @@
|
||||
// falukant/predefine/political_office_benefit.js
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
import PoliticalOfficeBenefitType from '../type/political_office_benefit_type.js';
|
||||
|
||||
class PoliticalOfficeBenefit extends Model {}
|
||||
|
||||
PoliticalOfficeBenefit.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
political_office_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
benefit_type_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
value: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'PoliticalOfficeBenefit',
|
||||
tableName: 'political_office_benefit',
|
||||
schema: 'falukant_predefine',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
// Association
|
||||
PoliticalOfficeBenefit.belongsTo(PoliticalOfficeBenefitType, {
|
||||
foreignKey: 'benefit_type_id',
|
||||
as: 'benefitType'
|
||||
});
|
||||
PoliticalOfficeBenefitType.hasMany(PoliticalOfficeBenefit, {
|
||||
foreignKey: 'benefit_type_id',
|
||||
as: 'benefits'
|
||||
});
|
||||
|
||||
export default PoliticalOfficeBenefit;
|
||||
@@ -0,0 +1,30 @@
|
||||
// falukant/predefine/political_office_prerequisite.js
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class PoliticalOfficePrerequisite extends Model {}
|
||||
|
||||
PoliticalOfficePrerequisite.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
political_office_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
prerequisite: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'PoliticalOfficePrerequisite',
|
||||
tableName: 'political_office_prerequisite',
|
||||
schema: 'falukant_predefine',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default PoliticalOfficePrerequisite;
|
||||
31
backend/models/falukant/type/banquette.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class BanquetteType extends Model {}
|
||||
|
||||
BanquetteType.init(
|
||||
{
|
||||
tr: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
cost: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
reputationGrowth: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
modelName: 'BanquetteType',
|
||||
tableName: 'banquette',
|
||||
schema: 'falukant_type',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
}
|
||||
);
|
||||
|
||||
export default BanquetteType;
|
||||
23
backend/models/falukant/type/learn_recipient.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class LearnRecipient extends Model {}
|
||||
|
||||
LearnRecipient.init(
|
||||
{
|
||||
tr: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
modelName: 'LearnRecipient',
|
||||
tableName: 'learn_recipient',
|
||||
schema: 'falukant_type',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
}
|
||||
);
|
||||
|
||||
export default LearnRecipient;
|
||||
31
backend/models/falukant/type/music.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class MusicType extends Model {}
|
||||
|
||||
MusicType.init(
|
||||
{
|
||||
tr: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
cost: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
reputationGrowth: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
modelName: 'MusicType',
|
||||
tableName: 'music',
|
||||
schema: 'falukant_type',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
}
|
||||
);
|
||||
|
||||
export default MusicType;
|
||||
36
backend/models/falukant/type/party.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class PartyType extends Model {}
|
||||
|
||||
PartyType.init(
|
||||
{
|
||||
tr: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
cost: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
forMarriage: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
reputationGrowth: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
}
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
modelName: 'PartyType',
|
||||
tableName: 'party',
|
||||
schema: 'falukant_type',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
}
|
||||
);
|
||||
|
||||
export default PartyType;
|
||||
@@ -0,0 +1,26 @@
|
||||
// falukant/type/political_office_benefit_type.js
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class PoliticalOfficeBenefitType extends Model {}
|
||||
|
||||
PoliticalOfficeBenefitType.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
tr: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'PoliticalOfficeBenefitType',
|
||||
tableName: 'political_office_benefit_type',
|
||||
schema: 'falukant_type',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default PoliticalOfficeBenefitType;
|
||||
38
backend/models/falukant/type/political_office_type.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Model, DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../../utils/sequelize.js';
|
||||
|
||||
class PoliticalOfficeType extends Model {}
|
||||
|
||||
PoliticalOfficeType.init({
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
seatsPerRegion: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
regionType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
termLength: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'PoliticalOfficeType',
|
||||
tableName: 'political_office_type',
|
||||
schema: 'falukant_type',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default PoliticalOfficeType;
|
||||
@@ -8,6 +8,11 @@ TitleOfNobility.init({
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
level: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
},
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'Title',
|
||||
|
||||
@@ -4,12 +4,16 @@ import { sequelize } from '../../../utils/sequelize.js';
|
||||
class TitleRequirement extends Model { }
|
||||
|
||||
TitleRequirement.init({
|
||||
titleId: {
|
||||
id : {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
titleId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
requirementType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
@@ -25,6 +29,13 @@ TitleRequirement.init({
|
||||
schema: 'falukant_type',
|
||||
timestamps: false,
|
||||
underscored: true,
|
||||
indexes: [
|
||||
{
|
||||
unique: true,
|
||||
fields: ['title_id', 'requirement_type'],
|
||||
name: 'title_requirement_titleid_reqtype_unique'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export default TitleRequirement;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// models/index.js
|
||||
|
||||
import SettingsType from './type/settings.js';
|
||||
import UserParamValue from './type/user_param_value.js';
|
||||
import UserParamType from './type/user_param.js';
|
||||
@@ -32,6 +34,7 @@ import MessageHistory from './forum/message_history.js';
|
||||
import MessageImage from './forum/message_image.js';
|
||||
import ForumForumPermission from './forum/forum_forum_permission.js';
|
||||
import Friendship from './community/friendship.js';
|
||||
|
||||
import FalukantUser from './falukant/data/user.js';
|
||||
import RegionType from './falukant/type/region.js';
|
||||
import RegionData from './falukant/data/region.js';
|
||||
@@ -58,90 +61,136 @@ import DaySell from './falukant/log/daysell.js';
|
||||
import Notification from './falukant/log/notification.js';
|
||||
import MarriageProposal from './falukant/data/marriage_proposal.js';
|
||||
import RelationshipType from './falukant/type/relationship.js';
|
||||
import Relationship from './falukant/data/relationship.js';
|
||||
import CharacterTrait from './falukant/type/character_trait.js';
|
||||
import FalukantCharacterTrait from './falukant/data/falukant_character_trait.js';
|
||||
import Mood from './falukant/type/mood.js';
|
||||
import PromotionalGift from './falukant/type/promotional_gift.js';
|
||||
import PromotionalGiftCharacterTrait from './falukant/predefine/promotional_gift_character_trait.js';
|
||||
import PromotionalGiftMood from './falukant/predefine/promotional_gift_mood.js';
|
||||
import Relationship from './falukant/data/relationship.js';
|
||||
import PromotionalGiftLog from './falukant/log/promotional_gift.js';
|
||||
import HouseType from './falukant/type/house.js';
|
||||
import BuyableHouse from './falukant/data/buyable_house.js';
|
||||
import UserHouse from './falukant/data/user_house.js';
|
||||
import PartyType from './falukant/type/party.js';
|
||||
import Party from './falukant/data/party.js';
|
||||
import MusicType from './falukant/type/music.js';
|
||||
import BanquetteType from './falukant/type/banquette.js';
|
||||
import PartyInvitedNobility from './falukant/data/partyInvitedNobility.js';
|
||||
import ChildRelation from './falukant/data/child_relation.js';
|
||||
import LearnRecipient from './falukant/type/learn_recipient.js';
|
||||
import Learning from './falukant/data/learning.js';
|
||||
import Credit from './falukant/data/credit.js';
|
||||
import DebtorsPrism from './falukant/data/debtors_prism.js';
|
||||
import HealthActivity from './falukant/log/health_activity.js';
|
||||
|
||||
// — Politische Ämter (Politics) —
|
||||
import PoliticalOfficeType from './falukant/type/political_office_type.js';
|
||||
import PoliticalOfficeRequirement from './falukant/predefine/political_office_prerequisite.js';
|
||||
import PoliticalOfficeBenefitType from './falukant/type/political_office_benefit_type.js';
|
||||
import PoliticalOfficeBenefit from './falukant/predefine/political_office_benefit.js';
|
||||
import PoliticalOffice from './falukant/data/political_office.js';
|
||||
import Election from './falukant/data/election.js';
|
||||
import Candidate from './falukant/data/candidate.js';
|
||||
import Vote from './falukant/data/vote.js';
|
||||
import ElectionResult from './falukant/data/election_result.js';
|
||||
|
||||
const models = {
|
||||
SettingsType,
|
||||
UserParamValue,
|
||||
UserParamType,
|
||||
UserRightType,
|
||||
User,
|
||||
UserParam,
|
||||
Login,
|
||||
UserRight,
|
||||
InterestType,
|
||||
InterestTranslationType,
|
||||
Interest,
|
||||
ContactMessage,
|
||||
UserParamVisibilityType,
|
||||
UserParamVisibility,
|
||||
Folder,
|
||||
Image,
|
||||
ImageVisibilityType,
|
||||
ImageVisibilityUser,
|
||||
FolderImageVisibility,
|
||||
ImageImageVisibility,
|
||||
FolderVisibilityUser,
|
||||
GuestbookEntry,
|
||||
DiaryHistory,
|
||||
Diary,
|
||||
Forum,
|
||||
ForumPermission,
|
||||
ForumForumPermission,
|
||||
ForumUserPermission,
|
||||
Title,
|
||||
TitleHistory,
|
||||
Message,
|
||||
MessageHistory,
|
||||
MessageImage,
|
||||
Friendship,
|
||||
RegionType,
|
||||
RegionData,
|
||||
FalukantUser,
|
||||
FalukantPredefineFirstname,
|
||||
FalukantPredefineLastname,
|
||||
FalukantCharacter,
|
||||
FalukantStock,
|
||||
FalukantStockType,
|
||||
ProductType,
|
||||
Knowledge,
|
||||
TitleOfNobility,
|
||||
TitleRequirement,
|
||||
BranchType,
|
||||
Branch,
|
||||
Production,
|
||||
Inventory,
|
||||
BuyableStock,
|
||||
MoneyFlow,
|
||||
Director,
|
||||
DirectorProposal,
|
||||
TownProductWorth,
|
||||
DayProduction,
|
||||
DaySell,
|
||||
Notification,
|
||||
MarriageProposal,
|
||||
RelationshipType,
|
||||
Relationship,
|
||||
CharacterTrait,
|
||||
FalukantCharacterTrait,
|
||||
Mood,
|
||||
PromotionalGift,
|
||||
PromotionalGiftCharacterTrait,
|
||||
PromotionalGiftMood,
|
||||
PromotionalGiftLog,
|
||||
HouseType,
|
||||
BuyableHouse,
|
||||
UserHouse,
|
||||
SettingsType,
|
||||
UserParamValue,
|
||||
UserParamType,
|
||||
UserRightType,
|
||||
User,
|
||||
UserParam,
|
||||
Login,
|
||||
UserRight,
|
||||
InterestType,
|
||||
InterestTranslationType,
|
||||
Interest,
|
||||
ContactMessage,
|
||||
UserParamVisibilityType,
|
||||
UserParamVisibility,
|
||||
Folder,
|
||||
Image,
|
||||
ImageVisibilityType,
|
||||
ImageVisibilityUser,
|
||||
FolderImageVisibility,
|
||||
ImageImageVisibility,
|
||||
FolderVisibilityUser,
|
||||
GuestbookEntry,
|
||||
DiaryHistory,
|
||||
Diary,
|
||||
Forum,
|
||||
ForumPermission,
|
||||
ForumForumPermission,
|
||||
ForumUserPermission,
|
||||
Title,
|
||||
TitleHistory,
|
||||
Message,
|
||||
MessageHistory,
|
||||
MessageImage,
|
||||
Friendship,
|
||||
|
||||
// Falukant core
|
||||
RegionType,
|
||||
RegionData,
|
||||
FalukantUser,
|
||||
FalukantPredefineFirstname,
|
||||
FalukantPredefineLastname,
|
||||
FalukantCharacter,
|
||||
FalukantStock,
|
||||
FalukantStockType,
|
||||
ProductType,
|
||||
Knowledge,
|
||||
TitleOfNobility,
|
||||
TitleRequirement,
|
||||
BranchType,
|
||||
Branch,
|
||||
Production,
|
||||
Inventory,
|
||||
BuyableStock,
|
||||
MoneyFlow,
|
||||
Director,
|
||||
DirectorProposal,
|
||||
TownProductWorth,
|
||||
DayProduction,
|
||||
DaySell,
|
||||
Notification,
|
||||
MarriageProposal,
|
||||
RelationshipType,
|
||||
Relationship,
|
||||
CharacterTrait,
|
||||
FalukantCharacterTrait,
|
||||
Mood,
|
||||
PromotionalGift,
|
||||
PromotionalGiftCharacterTrait,
|
||||
PromotionalGiftMood,
|
||||
PromotionalGiftLog,
|
||||
HouseType,
|
||||
BuyableHouse,
|
||||
UserHouse,
|
||||
PartyType,
|
||||
MusicType,
|
||||
BanquetteType,
|
||||
Party,
|
||||
PartyInvitedNobility,
|
||||
ChildRelation,
|
||||
LearnRecipient,
|
||||
Learning,
|
||||
Credit,
|
||||
DebtorsPrism,
|
||||
HealthActivity,
|
||||
|
||||
// Politics
|
||||
PoliticalOfficeType,
|
||||
PoliticalOfficeRequirement,
|
||||
PoliticalOfficeBenefitType,
|
||||
PoliticalOfficeBenefit,
|
||||
PoliticalOffice,
|
||||
Election,
|
||||
Candidate,
|
||||
Vote,
|
||||
ElectionResult,
|
||||
};
|
||||
|
||||
export default models;
|
||||
|
||||
@@ -180,6 +180,67 @@ export async function createTriggers() {
|
||||
$function$;
|
||||
`;
|
||||
|
||||
const createChildRelationNameFunction = `
|
||||
CREATE OR REPLACE FUNCTION falukant_data.populate_child_relation_names()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
v_first_name TEXT;
|
||||
v_last_name TEXT;
|
||||
v_full_father TEXT;
|
||||
v_full_mother TEXT;
|
||||
BEGIN
|
||||
-- Vaternamen holen
|
||||
SELECT pf.name, pl.name
|
||||
INTO v_first_name, v_last_name
|
||||
FROM falukant_data.character c
|
||||
JOIN falukant_predefine.firstname pf ON pf.id = c.first_name
|
||||
JOIN falukant_predefine.lastname pl ON pl.id = c.last_name
|
||||
WHERE c.id = NEW.father_character_id;
|
||||
|
||||
v_full_father := v_first_name || ' ' || v_last_name;
|
||||
|
||||
-- Mutternamen holen
|
||||
SELECT pf.name, pl.name
|
||||
INTO v_first_name, v_last_name
|
||||
FROM falukant_data.character c
|
||||
JOIN falukant_predefine.firstname pf ON pf.id = c.first_name
|
||||
JOIN falukant_predefine.lastname pl ON pl.id = c.last_name
|
||||
WHERE c.id = NEW.mother_character_id;
|
||||
|
||||
v_full_mother := v_first_name || ' ' || v_last_name;
|
||||
|
||||
-- Felder füllen
|
||||
NEW.father_name := v_full_father;
|
||||
NEW.mother_name := v_full_mother;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
`;
|
||||
|
||||
const createChildRelationNameTrigger = `
|
||||
DROP TRIGGER IF EXISTS trg_child_relation_populate_names
|
||||
ON falukant_data.child_relation;
|
||||
CREATE TRIGGER trg_child_relation_populate_names
|
||||
BEFORE INSERT ON falukant_data.child_relation
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION falukant_data.populate_child_relation_names();
|
||||
`;
|
||||
|
||||
const createRandomMoodUpdateMethod = `
|
||||
CREATE OR REPLACE FUNCTION falukant_data.get_random_mood_id()
|
||||
RETURNS INTEGER AS $$
|
||||
BEGIN
|
||||
RETURN (
|
||||
SELECT id
|
||||
FROM falukant_type.mood
|
||||
ORDER BY random()
|
||||
LIMIT 1
|
||||
);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql VOLATILE;
|
||||
`;
|
||||
|
||||
try {
|
||||
await sequelize.query(createTriggerFunction);
|
||||
await sequelize.query(createInsertTrigger);
|
||||
@@ -193,6 +254,9 @@ export async function createTriggers() {
|
||||
await sequelize.query(createKnowledgeTriggerMethod);
|
||||
await sequelize.query(createKnowledgeTrigger);
|
||||
await sequelize.query(updateMoney);
|
||||
await sequelize.query(createChildRelationNameFunction);
|
||||
await sequelize.query(createChildRelationNameTrigger);
|
||||
await sequelize.query(createRandomMoodUpdateMethod);
|
||||
await initializeCharacterTraitTrigger();
|
||||
|
||||
console.log('Triggers created successfully');
|
||||
@@ -250,4 +314,3 @@ export const initializeCharacterTraitTrigger = async () => {
|
||||
console.error('❌ Fehler beim Erstellen des Triggers:', error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,6 +6,8 @@ const falukantController = new FalukantController();
|
||||
|
||||
router.get('/user', falukantController.getUser);
|
||||
router.post('/user', falukantController.createUser);
|
||||
router.get('/mood/affect', falukantController.getMoodAffect);
|
||||
router.get('/character/affect', falukantController.getCharacterAffect);
|
||||
router.get('/name/randomfirstname/:gender', falukantController.randomFirstName);
|
||||
router.get('/name/randomlastname', falukantController.randomLastName);
|
||||
router.get('/info', falukantController.getInfo);
|
||||
@@ -30,15 +32,30 @@ router.post('/director/proposal', falukantController.getDirectorProposals);
|
||||
router.post('/director/convertproposal', falukantController.convertProposalToDirector);
|
||||
router.post('/director/settings', falukantController.setSetting);
|
||||
router.get('/director/:branchId', falukantController.getDirectorForBranch);
|
||||
router.get('/directors', falukantController.getAllDirectors);
|
||||
router.post('/directors', falukantController.updateDirector);
|
||||
router.post('/family/acceptmarriageproposal', falukantController.acceptMarriageProposal);
|
||||
router.get('/family/gifts', falukantController.getGifts);
|
||||
router.get('/family/children', falukantController.getChildren);
|
||||
router.post('/family/gift', falukantController.sendGift);
|
||||
router.get('/family', falukantController.getFamily);
|
||||
router.get('/nobility/titels', falukantController.getTitelsOfNobility);
|
||||
router.get('/houses/types', falukantController.getHouseTypes);
|
||||
router.get('/houses/buyable', falukantController.getBuyableHouses);
|
||||
router.get('/mood/affect', falukantController.getMoodAffect);
|
||||
router.get('/character/affect', falukantController.getCharacterAffect);
|
||||
router.get('/houses', falukantController.getUserHouse);
|
||||
router.post('/houses', falukantController.buyUserHouse);
|
||||
router.get('/party/types', falukantController.getPartyTypes);
|
||||
router.post('/party', falukantController.createParty);
|
||||
router.get('/party', falukantController.getParties);
|
||||
router.get('/family/notbaptised', falukantController.getNotBaptisedChildren);
|
||||
router.post('/church/baptise', falukantController.baptise);
|
||||
router.get('/education', falukantController.getEducation);
|
||||
router.post('/education', falukantController.sendToSchool);
|
||||
router.get('/bank/overview', falukantController.getBankOverview);
|
||||
router.get('/bank/credits', falukantController.getBankCredits);
|
||||
router.post('/bank/credits', falukantController.takeBankCredits);
|
||||
router.get('/nobility', falukantController.getNobility);
|
||||
router.post('/nobility', falukantController.advanceNobility);
|
||||
router.get('/health', falukantController.getHealth);
|
||||
router.post('/health', falukantController.healthActivity)
|
||||
export default router;
|
||||
|
||||
@@ -267,73 +267,81 @@ async function initializeFalukantProducts() {
|
||||
}
|
||||
|
||||
async function initializeFalukantTitles() {
|
||||
await TitleOfNobility.bulkCreate([
|
||||
{ labelTr: "noncivil" },
|
||||
{ labelTr: "civil" },
|
||||
{ labelTr: "sir" },
|
||||
{ labelTr: "townlord" },
|
||||
{ labelTr: "by" },
|
||||
{ labelTr: "landlord" },
|
||||
{ labelTr: "knight" },
|
||||
{ labelTr: "baron" },
|
||||
{ labelTr: "count" },
|
||||
{ labelTr: "palsgrave" },
|
||||
{ labelTr: "margrave" },
|
||||
{ labelTr: "landgrave" },
|
||||
{ labelTr: "ruler" },
|
||||
{ labelTr: "elector" },
|
||||
{ labelTr: "imperial-prince" },
|
||||
{ labelTr: "duke" },
|
||||
{ labelTr: "grand-duke" },
|
||||
{ labelTr: "prince-regent" },
|
||||
{ labelTr: "king" },
|
||||
], {
|
||||
updateOnDuplicate: ['labelTr'],
|
||||
});
|
||||
try {
|
||||
await TitleOfNobility.bulkCreate([
|
||||
{ labelTr: "noncivil", level: 1 },
|
||||
{ labelTr: "civil", level: 2 },
|
||||
{ labelTr: "sir", level: 3 },
|
||||
{ labelTr: "townlord", level: 4 },
|
||||
{ labelTr: "by", level: 5 },
|
||||
{ labelTr: "landlord", level: 6 },
|
||||
{ labelTr: "knight", level: 7 },
|
||||
{ labelTr: "baron", level: 8 },
|
||||
{ labelTr: "count", level: 9 },
|
||||
{ labelTr: "palsgrave", level: 10 },
|
||||
{ labelTr: "margrave", level: 11 },
|
||||
{ labelTr: "landgrave", level: 12 },
|
||||
{ labelTr: "ruler", level: 13 },
|
||||
{ labelTr: "elector", level: 14 },
|
||||
{ labelTr: "imperial-prince", level: 15 },
|
||||
{ labelTr: "duke", level: 16 },
|
||||
{ labelTr: "grand-duke", level: 17 },
|
||||
{ labelTr: "prince-regent", level: 18 },
|
||||
{ labelTr: "king", level: 19 },
|
||||
], {
|
||||
updateOnDuplicate: ['labelTr'],
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error initializing Falukant titles:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function initializeFalukantTitleRequirements() {
|
||||
const titleRequirements = [
|
||||
{ labelTr: "civil", requirements: [{ type: "money", value: 500 }] },
|
||||
{ labelTr: "sir", requirements: [{ type: "branches", value: 2 }] },
|
||||
{ labelTr: "townlord", requirements: [] },
|
||||
{ labelTr: "by", requirements: [] },
|
||||
{ labelTr: "landlord", requirements: [] },
|
||||
{ labelTr: "knight", requirements: [] },
|
||||
{ labelTr: "baron", requirements: [{ type: "branches", value: 4 }] },
|
||||
{ labelTr: "count", requirements: [] },
|
||||
{ labelTr: "palsgrave", requirements: [] },
|
||||
{ labelTr: "margrave", requirements: [] },
|
||||
{ labelTr: "landgrave", requirements: [] },
|
||||
{ labelTr: "ruler", requirements: [] },
|
||||
{ labelTr: "elector", requirements: [] },
|
||||
{ labelTr: "imperial-prince", requirements: [] },
|
||||
{ labelTr: "duke", requirements: [] },
|
||||
{ labelTr: "grand-duke", requirements: [] },
|
||||
{ labelTr: "prince-regent", requirements: [] },
|
||||
{ labelTr: "king", requirements: [] },
|
||||
{ labelTr: "civil", requirements: [{ type: "money", value: 5000 }, { type: "cost", value: 500 }] },
|
||||
{ labelTr: "sir", requirements: [{ type: "branches", value: 2 }, { type: "cost", value: 1000 }] },
|
||||
{ labelTr: "townlord", requirements: [{ type: "cost", value: 3000 }] },
|
||||
{ labelTr: "by", requirements: [{ type: "cost", value: 6000 }] },
|
||||
{ labelTr: "landlord", requirements: [{ type: "cost", value: 9000 }] },
|
||||
{ labelTr: "knight", requirements: [{ type: "cost", value: 11000 }] },
|
||||
{ labelTr: "baron", requirements: [{ type: "branches", value: 4 }, { type: "cost", value: 15000 }] },
|
||||
{ labelTr: "count", requirements: [{ type: "cost", value: 19000 }] },
|
||||
{ labelTr: "palsgrave", requirements: [{ type: "cost", value: 25000 }] },
|
||||
{ labelTr: "margrave", requirements: [{ type: "cost", value: 33000 }] },
|
||||
{ labelTr: "landgrave", requirements: [{ type: "cost", value: 47000 }] },
|
||||
{ labelTr: "ruler", requirements: [{ type: "cost", value: 66000 }] },
|
||||
{ labelTr: "elector", requirements: [{ type: "cost", value: 79000 }] },
|
||||
{ labelTr: "imperial-prince", requirements: [{ type: "cost", value: 99999 }] },
|
||||
{ labelTr: "duke", requirements: [{ type: "cost", value: 130000 }] },
|
||||
{ labelTr: "grand-duke",requirements: [{ type: "cost", value: 170000 }] },
|
||||
{ labelTr: "prince-regent", requirements: [{ type: "cost", value: 270000 }] },
|
||||
{ labelTr: "king", requirements: [{ type: "cost", value: 500000 }] },
|
||||
];
|
||||
|
||||
const titles = await TitleOfNobility.findAll();
|
||||
const requirementsToInsert = [];
|
||||
|
||||
for (let i = 0; i < titleRequirements.length; i++) {
|
||||
const titleRequirement = titleRequirements[i];
|
||||
const title = titles.find(t => t.labelTr === titleRequirement.labelTr);
|
||||
const titleReq = titleRequirements[i];
|
||||
const title = titles.find(t => t.labelTr === titleReq.labelTr);
|
||||
if (!title) continue;
|
||||
|
||||
if (i > 1) {
|
||||
const moneyRequirement = {
|
||||
type: "money",
|
||||
titleReq.requirements.push({
|
||||
type: "money",
|
||||
value: 5000 * Math.pow(3, i - 1),
|
||||
};
|
||||
titleRequirement.requirements.push(moneyRequirement);
|
||||
});
|
||||
}
|
||||
for (const requirement of titleRequirement.requirements) {
|
||||
|
||||
for (const req of titleReq.requirements) {
|
||||
requirementsToInsert.push({
|
||||
titleId: title.id,
|
||||
requirementType: requirement.type,
|
||||
requirementValue: requirement.value,
|
||||
titleId: title.id,
|
||||
requirementType: req.type,
|
||||
requirementValue: req.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await TitleRequirement.bulkCreate(requirementsToInsert, { ignoreDuplicates: true });
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@ import PromotionalGiftCharacterTrait from "../../models/falukant/predefine/promo
|
||||
import PromotionalGiftMood from "../../models/falukant/predefine/promotional_gift_mood.js";
|
||||
import HouseType from '../../models/falukant/type/house.js';
|
||||
import TitleOfNobility from "../../models/falukant/type/title_of_nobility.js";
|
||||
import PartyType from "../../models/falukant/type/party.js";
|
||||
import MusicType from "../../models/falukant/type/music.js";
|
||||
import BanquetteType from "../../models/falukant/type/banquette.js";
|
||||
import LearnRecipient from "../../models/falukant/type/learn_recipient.js";
|
||||
|
||||
export const initializeFalukantTypes = async () => {
|
||||
await initializeFalukantTypeRegions();
|
||||
@@ -17,6 +21,10 @@ export const initializeFalukantTypes = async () => {
|
||||
await initializeFalukantPromotionalGifts();
|
||||
await initializePromotionalGiftMoodLinks();
|
||||
await initializeFalukantHouseTypes();
|
||||
await initializeFalukantPartyTypes();
|
||||
await initializeFalukantMusicTypes();
|
||||
await initializeFalukantBanquetteTypes();
|
||||
await initializeLearnerTypes();
|
||||
};
|
||||
|
||||
const regionTypes = [];
|
||||
@@ -208,15 +216,44 @@ const promotionalGiftMoodLinks = [
|
||||
];
|
||||
|
||||
const houseTypes = [
|
||||
{ labelTr: 'Unter der Brücke', abbr: 'under_bridge', cost: 10, position: 1, minimumTitle: 'noncivil' },
|
||||
{ labelTr: 'Strohhütte', abbr: 'straw_hut', cost: 20, position: 2, minimumTitle: 'noncivil' },
|
||||
{ labelTr: 'Holzhaus', abbr: 'wooden_house', cost: 50, position: 3, minimumTitle: 'civil' },
|
||||
{ labelTr: 'Hinterhofzimmer', abbr: 'backyard_room', cost: 5, position: 4, minimumTitle: 'civil' },
|
||||
{ labelTr: 'Kleines Familienhaus', abbr: 'family_house', cost: 100, position: 5, minimumTitle: 'sir' },
|
||||
{ labelTr: 'Stadthaus', abbr: 'townhouse', cost: 200, position: 6, minimumTitle: 'townlord' },
|
||||
{ labelTr: 'Villa', abbr: 'villa', cost: 500, position: 7, minimumTitle: 'knight' },
|
||||
{ labelTr: 'Herrenhaus', abbr: 'mansion', cost: 1000, position: 8, minimumTitle: 'ruler' },
|
||||
{ labelTr: 'Schloss', abbr: 'castle', cost: 5000, position: 9, minimumTitle: 'prince-regent' },
|
||||
{ labelTr: 'Unter der Brücke', abbr: 'under_bridge', cost: 0, position: 1, minimumTitle: 'noncivil' },
|
||||
{ labelTr: 'Strohhütte', abbr: 'straw_hut', cost: 100, position: 2, minimumTitle: 'noncivil' },
|
||||
{ labelTr: 'Holzhaus', abbr: 'wooden_house', cost: 5000, position: 3, minimumTitle: 'civil' },
|
||||
{ labelTr: 'Hinterhofzimmer', abbr: 'backyard_room', cost: 75000, position: 4, minimumTitle: 'civil' },
|
||||
{ labelTr: 'Kleines Familienhaus', abbr: 'family_house', cost: 273000, position: 5, minimumTitle: 'sir' },
|
||||
{ labelTr: 'Stadthaus', abbr: 'townhouse', cost: 719432, position: 6, minimumTitle: 'townlord' },
|
||||
{ labelTr: 'Villa', abbr: 'villa', cost: 3500000, position: 7, minimumTitle: 'knight' },
|
||||
{ labelTr: 'Herrenhaus', abbr: 'mansion', cost: 18000000, position: 8, minimumTitle: 'ruler' },
|
||||
{ labelTr: 'Schloss', abbr: 'castle', cost: 500000000, position: 9, minimumTitle: 'prince-regent' },
|
||||
];
|
||||
|
||||
const partyTypes = [
|
||||
{ labelTr: 'wedding', cost: 50, forMarriage: true, reputationGrowth: 5 },
|
||||
{ labelTr: 'ball', cost: 250, forMarriage: false, reputationGrowth: 7 },
|
||||
{ labelTr: 'town fair', cost: 1000, forMarriage: false, reputationGrowth: 10 },
|
||||
{ labelTr: 'royal feast', cost: 50000, forMarriage: false, reputationGrowth: 25 },
|
||||
];
|
||||
|
||||
const musicTypes = [
|
||||
{ type: 'none', cost: 0, reputationGrowth: 0 },
|
||||
{ type: 'bard', cost: 100, reputationGrowth: 2 },
|
||||
{ type: 'villageBand', cost: 2500, reputationGrowth: 5 },
|
||||
{ type: 'chamberOrchestra', cost: 12000, reputationGrowth: 10 },
|
||||
{ type: 'symphonyOrchestra', cost: 37000, reputationGrowth: 15 },
|
||||
{ type: 'symphonyOrchestraWithChorusAndSolists', cost: 500000, reputationGrowth: 25 },
|
||||
];
|
||||
|
||||
const banquetteTypes = [
|
||||
{ type: 'bread', cost: 5, reputationGrowth: 0 },
|
||||
{ type: 'roastWithBeer', cost: 200, reputationGrowth: 5 },
|
||||
{ type: 'poultryWithVegetablesAndWine', cost: 5000, reputationGrowth: 10 },
|
||||
{ type: 'extensiveBuffet', cost: 100000, reputationGrowth: 20 }
|
||||
];
|
||||
|
||||
const learnerTypes = [
|
||||
{ tr: 'self', },
|
||||
{ tr: 'children', },
|
||||
{ tr: 'director', },
|
||||
];
|
||||
|
||||
{
|
||||
@@ -391,3 +428,54 @@ export const initializeFalukantHouseTypes = async () => {
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const initializeFalukantPartyTypes = async () => {
|
||||
for (const pt of partyTypes) {
|
||||
const [record, created] = await PartyType.findOrCreate({
|
||||
where: { tr: pt.labelTr },
|
||||
defaults: {
|
||||
cost: pt.cost,
|
||||
tr: pt.labelTr,
|
||||
forMarriage: pt.forMarriage,
|
||||
reputationGrowth: pt.reputationGrowth,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const initializeFalukantMusicTypes = async () => {
|
||||
for (const mt of musicTypes) {
|
||||
const [record, created] = await MusicType.findOrCreate({
|
||||
where: { tr: mt.type },
|
||||
defaults: {
|
||||
cost: mt.cost,
|
||||
tr: mt.type,
|
||||
reputationGrowth: mt.reputationGrowth,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const initializeFalukantBanquetteTypes = async () => {
|
||||
for (const bt of banquetteTypes) {
|
||||
const [record, created] = await BanquetteType.findOrCreate({
|
||||
where: { tr: bt.type },
|
||||
defaults: {
|
||||
tr: bt.type,
|
||||
cost: bt.cost,
|
||||
reputationGrowth: bt.reputationGrowth,
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const initializeLearnerTypes = async () => {
|
||||
for (const lt of learnerTypes) {
|
||||
const [record, created] = await LearnRecipient.findOrCreate({
|
||||
where: { tr: lt.tr },
|
||||
defaults: {
|
||||
tr: lt.tr,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
BIN
frontend/public/images/icons/falukant/church.jpg
Normal file
|
After Width: | Height: | Size: 339 KiB |
BIN
frontend/public/images/icons/falukant/german.zip
Normal file
BIN
frontend/public/images/icons/falukant/relationship-.png
Normal file
|
After Width: | Height: | Size: 625 KiB |
BIN
frontend/public/images/icons/falukant/relationship-engaged.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
frontend/public/images/icons/falukant/relationship-married.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
frontend/public/images/icons/falukant/relationship-widow.png
Normal file
|
After Width: | Height: | Size: 948 KiB |
BIN
frontend/public/images/icons/falukant/relationship-wooing.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
frontend/public/images/icons/falukant/relationships.png
Normal file
|
After Width: | Height: | Size: 489 KiB |
56
frontend/src/components/SimpleTabs.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="simple-tabs">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.value"
|
||||
:class="['simple-tab', { active: internalValue === tab.value }]"
|
||||
@click="$emit('update:modelValue', tab.value)"
|
||||
>
|
||||
<slot name="label" :tab="tab">
|
||||
{{ $t(tab.label) }}
|
||||
</slot>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SimpleTabs',
|
||||
props: {
|
||||
tabs: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
internalValue() {
|
||||
return this.modelValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.simple-tabs {
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.simple-tab {
|
||||
padding: 0.5rem 1rem;
|
||||
background: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.simple-tab.active {
|
||||
background: #F9A22C;
|
||||
color: #000;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<template>
|
||||
<div class="statusbar">
|
||||
<template v-for="item in statusItems" :key="item.key">
|
||||
<div class="status-item" v-if="item.value !== null" :title="$t(`falukant.statusbar.${item.key}`)">
|
||||
<div class="status-item" v-if="item.value !== null && item.image == null" :title="$t(`falukant.statusbar.${item.key}`)">
|
||||
<span class="status-icon">{{ item.icon }}: {{ item.value }}</span>
|
||||
</div>
|
||||
<div class="status-item" v-else-if="item.image !== null" :title="$t(`falukant.statusbar.${item.key}`)">
|
||||
<span class="status-icon">{{ item.icon }}:</span> <img :src="'/images/icons/falukant/relationship-' + item.image + '.png'" class="relationship-icon" />
|
||||
</div>
|
||||
</template>
|
||||
<span v-if="statusItems.length > 0">
|
||||
<template v-for="(menuItem, key) in menu.falukant.children" :key="menuItem.id" >
|
||||
@@ -23,9 +26,10 @@ export default {
|
||||
return {
|
||||
statusItems: [
|
||||
{ key: "age", icon: "👶", value: 0 },
|
||||
{ key: "relationship", icon: "💑", image: null },
|
||||
{ key: "wealth", icon: "💰", value: 0 },
|
||||
{ key: "health", icon: "❤️", value: "Good" },
|
||||
{ key: "events", icon: "📰", value: null },
|
||||
{ key: "events", icon: "📰", value: null, image: null },
|
||||
],
|
||||
};
|
||||
},
|
||||
@@ -56,6 +60,9 @@ export default {
|
||||
const response = await apiClient.get("/api/falukant/info");
|
||||
const { money, character, events } = response.data;
|
||||
const { age, health } = character;
|
||||
const relationship = response.data.character.relationshipsAsCharacter1[0]?.relationshipType?.tr
|
||||
|| response.data.character.relationshipsAsCharacter2[0]?.relationshipType?.tr
|
||||
|| null;
|
||||
let healthStatus = '';
|
||||
if (health > 90) {
|
||||
healthStatus = this.$t("falukant.health.amazing");
|
||||
@@ -70,9 +77,10 @@ export default {
|
||||
}
|
||||
this.statusItems = [
|
||||
{ key: "age", icon: "👶", value: age },
|
||||
{ key: "relationship", icon: "💑", image: relationship },
|
||||
{ key: "wealth", icon: "💰", value: Intl.NumberFormat(navigator.language, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(money) },
|
||||
{ key: "health", icon: "❤️", value: healthStatus },
|
||||
{ key: "events", icon: "📰", value: events || null },
|
||||
{ key: "events", icon: "📰", value: events || null, image: null },
|
||||
];
|
||||
} catch (error) {
|
||||
console.error("Error fetching status:", error);
|
||||
@@ -112,7 +120,7 @@ export default {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
width: calc(100% + 40px);
|
||||
gap: 2em;
|
||||
gap: 1.2em;
|
||||
margin: -21px -20px 1.5em -20px;
|
||||
position: fixed;
|
||||
}
|
||||
@@ -120,6 +128,8 @@ export default {
|
||||
.status-item {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
@@ -132,4 +142,9 @@ export default {
|
||||
cursor: pointer;
|
||||
padding: 4px 2px 0 0;
|
||||
}
|
||||
|
||||
.relationship-icon {
|
||||
max-width: 24px;
|
||||
max-height: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -226,7 +226,8 @@
|
||||
|
||||
},
|
||||
"mood": "Stimmung",
|
||||
"progress": "Zuneigung"
|
||||
"progress": "Zuneigung",
|
||||
"jumpToPartyForm": "Hochzeitsfeier veranstalten (Nötig für Hochzeit und Kinder)"
|
||||
},
|
||||
"relationships": {
|
||||
"name": "Name"
|
||||
@@ -238,7 +239,8 @@
|
||||
"actions": "Aktionen",
|
||||
"none": "Keine Kinder vorhanden.",
|
||||
"detailButton": "Details anzeigen",
|
||||
"addChild": "Kind hinzufügen"
|
||||
"addChild": "Kind hinzufügen",
|
||||
"baptism": "Taufen"
|
||||
},
|
||||
"lovers": {
|
||||
"title": "Liebhaber",
|
||||
@@ -407,12 +409,192 @@
|
||||
"price": "Kaufpreis",
|
||||
"worth": "Restwert",
|
||||
"sell": "Verkaufen",
|
||||
"renovate": "Renovieren",
|
||||
"renovateAll": "Komplett renovieren",
|
||||
"status": {
|
||||
"roofCondition": "Dach",
|
||||
"wallCondition": "Wände",
|
||||
"floorCondition": "Böden",
|
||||
"windowCondition": "Fenster"
|
||||
},
|
||||
"type": {
|
||||
"backyard_room": "Hinterhofzimmer",
|
||||
"wooden_house": "Holzhütte",
|
||||
"straw_hut": "Strohhütte"
|
||||
}
|
||||
},
|
||||
"nobility": {
|
||||
"title": "Sozialstatus",
|
||||
"tabs": {
|
||||
"overview": "Übersicht",
|
||||
"advance": "Erweitern"
|
||||
},
|
||||
"nextTitle": "Nächster möglicher Titel",
|
||||
"requirement": {
|
||||
"money": "Vermögen mindestens {amount}",
|
||||
"cost": "Kosten: {amount}",
|
||||
"branches": "Mindestens {amount} Niederlassungen"
|
||||
},
|
||||
"advance": {
|
||||
"confirm": "Aufsteigen beantragen"
|
||||
}
|
||||
},
|
||||
"reputation": {
|
||||
"title": "Reputation",
|
||||
"overview": {
|
||||
"title": "Übersicht"
|
||||
},
|
||||
"party": {
|
||||
"title": "Feste",
|
||||
"totalCost": "Gesamtkosten",
|
||||
"order": "Fest veranstalten",
|
||||
"inProgress": "Feste in Vorbereitung",
|
||||
"completed": "Abgeschlossene Feste",
|
||||
"newpartyview": {
|
||||
"open": "Neues Fest erstellen",
|
||||
"close": "Neues Fest verbergen",
|
||||
"type": "Art des Festes"
|
||||
},
|
||||
"music": {
|
||||
"label": "Musik",
|
||||
"none": "Ohne Musik",
|
||||
"bard": "Ein Barde",
|
||||
"villageBand": "Eine Dorfkapelle",
|
||||
"chamberOrchestra": "Ein Kammerorchester",
|
||||
"symphonyOrchestra": "Ein Sinfonieorchester",
|
||||
"symphonyOrchestraWithChorusAndSolists": "Ein Sinfonieorchester mit Chor und Solisten"
|
||||
},
|
||||
"banquette": {
|
||||
"label": "Essen",
|
||||
"bread": "Brot",
|
||||
"roastWithBeer": "Rostbraten mit Bier",
|
||||
"poultryWithVegetablesAndWine": "Geflügel mit Gemüse und Wein",
|
||||
"extensiveBuffet": "Festliches Essen"
|
||||
},
|
||||
"servants": {
|
||||
"label": "Ein Bediensteter pro ",
|
||||
"perPersons": " Personen"
|
||||
},
|
||||
"esteemedInvites": {
|
||||
"label": "Eingeladene Stände"
|
||||
},
|
||||
"type": "Festart",
|
||||
"cost": "Kosten",
|
||||
"date": "Datum"
|
||||
}
|
||||
},
|
||||
"party": {
|
||||
"type": {
|
||||
"ball": "Ball",
|
||||
"wedding": "Hochzeit",
|
||||
"royal feast": "Königliches Bankett",
|
||||
"town fair": "Stadtmarkt"
|
||||
}
|
||||
},
|
||||
"church": {
|
||||
"title": "Kirche",
|
||||
"baptism": {
|
||||
"title": "Taufen",
|
||||
"table": {
|
||||
"name": "Vorname",
|
||||
"gender": "Geschlecht",
|
||||
"age": "Alter",
|
||||
"baptise": "Taufen (50)",
|
||||
"newName": "Namen vorschlagen"
|
||||
},
|
||||
"gender": {
|
||||
"male": "Junge",
|
||||
"female": "Mädchen"
|
||||
},
|
||||
"success": "Das Kind wurde getauft.",
|
||||
"error": "Das Kind konnte nicht getauft werden."
|
||||
}
|
||||
},
|
||||
"education": {
|
||||
"title": "Bildung",
|
||||
"self": {
|
||||
"title": "Eigene Bildung"
|
||||
},
|
||||
"children": {
|
||||
"title": "Kinderbildung"
|
||||
},
|
||||
"director": {
|
||||
"title": "Direktoren-Ausbildung"
|
||||
},
|
||||
"table": {
|
||||
"article": "Produkt",
|
||||
"knowledge": "Wissen",
|
||||
"activity": "Aktivität"
|
||||
},
|
||||
"learn": "Weiterbilden",
|
||||
"learnAll": "In allem weiterbilden"
|
||||
},
|
||||
"bank": {
|
||||
"title": "Bank",
|
||||
"account": {
|
||||
"title": "Kontostand",
|
||||
"balance": "Kontostand",
|
||||
"totalDebt": "Ausstände",
|
||||
"maxCredit": "Maximaler Kredit",
|
||||
"availableCredit": "Verfügbarer Kredit"
|
||||
},
|
||||
"credits": {
|
||||
"title": "Kredite",
|
||||
"none": "Derzeit hast Du keinen Kredit aufgenommen.",
|
||||
"amount": "Betrag",
|
||||
"remaining": "Verbleibend",
|
||||
"interestRate": "Zinssatz",
|
||||
"table": {
|
||||
"name": "Name",
|
||||
"amount": "Betrag",
|
||||
"reason": "Grund",
|
||||
"date": "Datum"
|
||||
},
|
||||
"payoff": {
|
||||
"title": "Neuen Kredit aufnehmen",
|
||||
"height": "Kredithöhe",
|
||||
"remaining": "Verbleibende mögliche Kredithöhe",
|
||||
"fee": "Kreditzins",
|
||||
"feeHeight": "Rate (a 10 Raten)",
|
||||
"total": "Gesamtsumme",
|
||||
"confirm": "Kredit aufnehmen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"director": {
|
||||
"title": "Direktoren",
|
||||
"branch": "Niederlassung",
|
||||
"income": "Einkommen",
|
||||
"satisfaction": "Zufriedenheit",
|
||||
"name": "Name",
|
||||
"age": "Alter",
|
||||
"knowledge": {
|
||||
"title": "Wissen",
|
||||
"knowledge": "Wissen"
|
||||
},
|
||||
"product": "Produkt",
|
||||
"updateButton": "Gehalt aktualisieren",
|
||||
"wishedIncome": "Gewünschtes Einkommen"
|
||||
},
|
||||
"healthview": {
|
||||
"title": "Gesundheit",
|
||||
"age": "Alter",
|
||||
"status": "Gesundheitszustand",
|
||||
"measuresTaken": "Ergriffene Maßnahmen",
|
||||
"measure": "Maßnahme",
|
||||
"date": "Datum",
|
||||
"cost": "Kosten",
|
||||
"success": "Erfolg",
|
||||
"selectMeasure": "Maßnahme",
|
||||
"perform": "Durchführen",
|
||||
"measures": {
|
||||
"pill": "Tablette",
|
||||
"doctor": "Arztbesuch",
|
||||
"witch": "Hexe",
|
||||
"drunkOfLife": "Trunk des Lebens",
|
||||
"barber": "Barbier"
|
||||
},
|
||||
"choose": "Bitte auswählen"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,8 @@
|
||||
"politics": "Politik",
|
||||
"education": "Bildung",
|
||||
"health": "Gesundheit",
|
||||
"bank": "Bank"
|
||||
"bank": "Bank",
|
||||
"church": "Kirche"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,13 @@ import FalukantOverviewView from '../views/falukant/OverviewView.vue';
|
||||
import MoneyHistoryView from '../views/falukant/MoneyHistoryView.vue';
|
||||
import FamilyView from '../views/falukant/FamilyView.vue';
|
||||
import HouseView from '../views/falukant/HouseView.vue';
|
||||
import NobilityView from '../views/falukant/NobilityView.vue';
|
||||
import ReputationView from '../views/falukant/ReputationView.vue';
|
||||
import ChurchView from '../views/falukant/ChurchView.vue';
|
||||
import EducationView from '../views/falukant/EducationView.vue';
|
||||
import BankView from '../views/falukant/BankView.vue';
|
||||
import DirectorView from '../views/falukant/DirectorView.vue';
|
||||
import HealthView from '../views/falukant/HealthView.vue';
|
||||
|
||||
const falukantRoutes = [
|
||||
{
|
||||
@@ -42,6 +49,48 @@ const falukantRoutes = [
|
||||
component: HouseView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/falukant/nobility',
|
||||
name: 'NobilityView',
|
||||
component: NobilityView,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/falukant/reputation',
|
||||
name: 'ReputationView',
|
||||
component: ReputationView,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/falukant/church',
|
||||
name: 'ChurchView',
|
||||
component: ChurchView,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/falukant/education',
|
||||
name: 'EducationView',
|
||||
component: EducationView,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/falukant/bank',
|
||||
name: 'BankView',
|
||||
component: BankView,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/falukant/directors',
|
||||
name: 'DirectorView',
|
||||
component: DirectorView,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/falukant/health',
|
||||
name: 'HealthView',
|
||||
component: HealthView,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
];
|
||||
|
||||
export default falukantRoutes;
|
||||
|
||||
175
frontend/src/views/falukant/BankView.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<div class="contenthidden">
|
||||
<StatusBar />
|
||||
<div class="contentscroll">
|
||||
|
||||
<h2>{{ $t('falukant.bank.title') }}</h2>
|
||||
<SimpleTabs v-model="activeTab" :tabs="tabs" />
|
||||
|
||||
<!-- OVERVIEW -->
|
||||
<div v-if="activeTab === 'account'">
|
||||
<div class="account-section">
|
||||
<table>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.bank.account.balance') }}</td>
|
||||
<td>{{ formatCost(bankOverview.money) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.bank.account.totalDebt') }}</td>
|
||||
<td>{{ formatCost(bankOverview.totalDebt) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.bank.account.maxCredit') }}</td>
|
||||
<td>{{ formatCost(bankOverview.maxCredit) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.bank.account.availableCredit') }}</td>
|
||||
<td>{{ formatCost(bankOverview.availableCredit) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ACTIVE CREDITS -->
|
||||
<div v-else-if="activeTab === 'credits'">
|
||||
<div class="credits-section">
|
||||
<div v-if="bankOverview.activeCredits?.length">
|
||||
<table class="credits-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.bank.credits.amount') }}</th>
|
||||
<th>{{ $t('falukant.bank.credits.remaining') }}</th>
|
||||
<th>{{ $t('falukant.bank.credits.interestRate') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="credit in bankOverview.activeCredits" :key="credit.id">
|
||||
<td>{{ formatCost(credit.amount) }}</td>
|
||||
<td>{{ formatCost(credit.remainingAmount) }}</td>
|
||||
<td>{{ credit.interestRate }}%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>{{ $t('falukant.bank.credits.none') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PAYOFF INLINE -->
|
||||
<div v-else-if="activeTab === 'payoff'">
|
||||
<div class="payoff-section">
|
||||
<label>
|
||||
{{ $t('falukant.bank.credits.payoff.height') }}:
|
||||
<input
|
||||
type="number"
|
||||
v-model="selectedCredit"
|
||||
:min="0"
|
||||
:max="bankOverview.availableCredit"
|
||||
value="0"
|
||||
/>
|
||||
</label>
|
||||
<div v-if="selectedCredit">
|
||||
<p>{{ $t('falukant.bank.credits.payoff.remaining') }}: {{ formatCost(bankOverview.availableCredit - selectedCredit) }}</p>
|
||||
<p>{{ $t('falukant.bank.credits.payoff.fee') }}: {{ formatCost(bankOverview.fee) }}</p>
|
||||
<p>{{ $t('falukant.bank.credits.payoff.feeHeight') }}: {{ formatCost(feeRate()) }}</p>
|
||||
<p>
|
||||
<strong>{{ $t('falukant.bank.credits.payoff.total') }}: {{ formatCost(creditCost()) }}</strong>
|
||||
</p>
|
||||
<button @click="confirmPayoff" class="button" :disabled="!selectedCredit">
|
||||
{{ $t('falukant.bank.credits.payoff.confirm') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StatusBar from '@/components/falukant/StatusBar.vue';
|
||||
import SimpleTabs from '@/components/SimpleTabs.vue';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'BankView',
|
||||
components: { StatusBar, SimpleTabs },
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'account',
|
||||
tabs: [
|
||||
{ value: 'account', label: 'falukant.bank.account.title' },
|
||||
{ value: 'credits', label: 'falukant.bank.credits.title' },
|
||||
{ value: 'payoff', label: 'falukant.bank.credits.payoff.title' }
|
||||
],
|
||||
bankOverview: {
|
||||
money: 0,
|
||||
totalDebt: 0,
|
||||
maxCredit: 0,
|
||||
availableCredit: 0,
|
||||
activeCredits: []
|
||||
},
|
||||
selectedCreditId: null,
|
||||
selectedCredit: null,
|
||||
earlyPayoffFee: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['daemonSocket'])
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadBankOverview();
|
||||
if (this.daemonSocket) this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.daemonSocket) this.daemonSocket.removeEventListener('message', this.handleDaemonMessage);
|
||||
},
|
||||
methods: {
|
||||
async loadBankOverview() {
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/falukant/bank/overview');
|
||||
this.bankOverview = data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
async confirmPayoff() {
|
||||
try {
|
||||
await apiClient.post('/api/falukant/bank/credits', {
|
||||
height: this.selectedCredit
|
||||
});
|
||||
await this.loadBankOverview();
|
||||
this.selectedCredit = null;
|
||||
this.activeTab = 'credits';
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
handleDaemonMessage(msg) {
|
||||
try {
|
||||
if (['falukantUpdateStatus', 'moneyChange', 'creditChange'].includes(msg.event)) {
|
||||
this.loadBankOverview();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(evt, err);
|
||||
}
|
||||
},
|
||||
feeRate() {
|
||||
return this.bankOverview.fee * this.selectedCredit / 100 + this.selectedCredit / 10;
|
||||
},
|
||||
creditCost() {
|
||||
return this.selectedCredit + (this.bankOverview.fee * 10 * this.selectedCredit / 100);
|
||||
},
|
||||
formatCost(val) {
|
||||
return new Intl.NumberFormat(navigator.language, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
h2 { padding-top: 20px; }
|
||||
</style>
|
||||
@@ -69,15 +69,15 @@ export default {
|
||||
"falukantUpdateStatus",
|
||||
"falukantBranchUpdate",
|
||||
];
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
||||
}
|
||||
events.forEach(eventName => {
|
||||
if (this.socket) {
|
||||
this.socket.on(eventName, (data) => {
|
||||
this.handleEvent({ event: eventName, ...data });
|
||||
});
|
||||
}
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
||||
}
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
|
||||
143
frontend/src/views/falukant/ChurchView.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div class="contenthidden">
|
||||
<StatusBar />
|
||||
<div class="contentscroll">
|
||||
<h2>{{ $t('falukant.church.title') }}</h2>
|
||||
<SimpleTabs v-model="activeTab" :tabs="tabs" />
|
||||
|
||||
<div class="tab-content">
|
||||
<div v-if="activeTab === 'baptism'">
|
||||
<h3>{{ $t('falukant.church.baptism.title') }}</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.church.baptism.table.gender') }}</th>
|
||||
<th>{{ $t('falukant.church.baptism.table.name') }}</th>
|
||||
<th>{{ $t('falukant.church.baptism.table.age') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="person in baptismList" :key="person.id">
|
||||
<td>{{ $t(`falukant.church.baptism.gender.${person.gender}`) }}</td>
|
||||
<td>
|
||||
<input type="text" v-model="person.proposedFirstName" />
|
||||
<button @click="newName(person)">
|
||||
{{ $t('falukant.church.baptism.table.newName') }}
|
||||
</button>
|
||||
</td>
|
||||
<td>{{ person.age }}</td>
|
||||
<td>
|
||||
<button @click="baptise(person)">
|
||||
{{ $t('falukant.church.baptism.table.baptise') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 apiClient from '@/utils/axios.js'
|
||||
import SimpleTabs from '@/components/SimpleTabs.vue'
|
||||
|
||||
export default {
|
||||
name: 'ChurchView',
|
||||
components: {
|
||||
StatusBar,
|
||||
MessageDialog,
|
||||
ErrorDialog,
|
||||
SimpleTabs,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'baptism',
|
||||
tabs: [
|
||||
{ value: 'baptism', label: 'falukant.church.baptism.title' },
|
||||
],
|
||||
baptismList: []
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadNotBaptisedChildren()
|
||||
},
|
||||
methods: {
|
||||
async loadNotBaptisedChildren() {
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/falukant/family/notbaptised')
|
||||
this.baptismList = data
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
},
|
||||
async newName(person) {
|
||||
try {
|
||||
const { data } = await apiClient.get(
|
||||
`/api/falukant/name/randomfirstname/${person.gender}`
|
||||
)
|
||||
person.proposedFirstName = data.name ?? data
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
},
|
||||
async baptise(person) {
|
||||
try {
|
||||
await apiClient.post('/api/falukant/church/baptise', {
|
||||
characterId: person.id,
|
||||
firstName: person.proposedFirstName
|
||||
})
|
||||
this.loadNotBaptisedChildren();
|
||||
this.$root.$refs.messageDialog.open('tr:falukant.church.baptism.success')
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
this.$root.$refs.errorDialog.open('tr:falukant.church.baptism.error')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
h2 {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.simple-tabs {
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.simple-tab {
|
||||
padding: 0.5rem 1rem;
|
||||
background: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.simple-tab.active {
|
||||
background: #F9A22C;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 140px;
|
||||
margin-right: 0.5rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
258
frontend/src/views/falukant/DirectorView.vue
Normal file
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<div class="director-view">
|
||||
<StatusBar />
|
||||
<div class="content-container">
|
||||
<!-- Left: Director list -->
|
||||
<div class="list-panel">
|
||||
<h2>{{ $t('falukant.director.title') }}</h2>
|
||||
<table class="director-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.director.name') }}</th>
|
||||
<th>{{ $t('falukant.director.branch') }}</th>
|
||||
<th>{{ $t('falukant.director.age') }}</th>
|
||||
<th>{{ $t('falukant.director.satisfaction') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="dir in directors" :key="dir.id" @click="selectDirector(dir)"
|
||||
:class="{ selected: dir.id === selected?.id }" class="director-row">
|
||||
<td>
|
||||
{{ $t(`falukant.titles.${dir.character.gender}.${dir.character.nobleTitle.labelTr}`) }}
|
||||
{{ dir.character.definedFirstName.name }} {{ dir.character.definedLastName.name }}
|
||||
</td>
|
||||
<td>{{ dir.region || '-' }}</td>
|
||||
<td>{{ dir.age }}</td>
|
||||
<td>{{ dir.satisfaction }} %</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Right: Selected director detail -->
|
||||
<div class="detail-panel" v-if="selected">
|
||||
<h2>
|
||||
{{ $t(`falukant.titles.${selected.character.gender}.${selected.character.nobleTitle.labelTr}`) }}
|
||||
{{ selected.character.definedFirstName.name }} {{ selected.character.definedLastName.name }}
|
||||
</h2>
|
||||
<p>{{ $t('falukant.director.age') }}: {{ selected.age }}</p>
|
||||
<h3>{{ $t('falukant.director.knowledge.title') }}</h3>
|
||||
<div class="table-container">
|
||||
<table class="knowledge-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.director.product') }}</th>
|
||||
<th>{{ $t('falukant.director.knowledge.knowledge') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in selected.character.knowledges" :key="item.productId">
|
||||
<td>{{ $t(`falukant.product.${item.productType.labelTr}`) }}</td>
|
||||
<td>{{ item.knowledge }} %</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div>
|
||||
<label>
|
||||
{{ $t('falukant.director.satisfaction') }}:
|
||||
<span> {{ selected.satisfaction }} %</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label>
|
||||
{{ $t('falukant.director.income') }}:
|
||||
<input type="text" v-model="selected.income" />
|
||||
</label>
|
||||
<span v-if="selected.satisfaction < 100" @click="setWishedIncome" class="link">({{ $t('falukant.director.wishedIncome') }}: {{ selected.wishedIncome }})</span>
|
||||
</div>
|
||||
<div>
|
||||
<button @click="updateDirector">{{ $t('falukant.director.updateButton') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StatusBar from '@/components/falukant/StatusBar.vue';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'DirectorView',
|
||||
components: { StatusBar },
|
||||
data() {
|
||||
return {
|
||||
directors: [],
|
||||
selected: null,
|
||||
editIncome: '',
|
||||
editSatisfaction: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['daemonSocket'])
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadDirectors();
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.removeEventListener('message', this.handleDaemonMessage);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadDirectors() {
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/falukant/directors');
|
||||
this.directors = data.map(d => ({
|
||||
...d,
|
||||
branchName: d.branch?.regionName || null
|
||||
}));
|
||||
} catch (err) {
|
||||
console.error('Error loading directors', err);
|
||||
}
|
||||
},
|
||||
selectDirector(dir) {
|
||||
this.selected = { ...dir };
|
||||
this.editIncome = dir.income;
|
||||
this.editSatisfaction = dir.satisfaction;
|
||||
},
|
||||
async updateDirector() {
|
||||
try {
|
||||
await apiClient.post('/api/falukant/directors', {
|
||||
directorId: this.selected.id,
|
||||
income: this.selected.income,
|
||||
});
|
||||
await this.loadDirectors();
|
||||
this.selected = this.directors.find(d => d.id === this.selected.id);
|
||||
} catch (err) {
|
||||
console.error('Error updating director', err);
|
||||
}
|
||||
},
|
||||
handleDaemonMessage(evt) {
|
||||
try {
|
||||
if (evt.data === 'ping') {
|
||||
return;
|
||||
}
|
||||
const msg = JSON.parse(evt.data);
|
||||
if (msg.event === 'directorchanged') {
|
||||
this.loadDirectors();
|
||||
if (this.selected) {
|
||||
const updated = this.directors.find(d => d.id === this.selected.id);
|
||||
if (updated) {
|
||||
this.selected = { ...updated };
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error parsing daemon message', err, evt.data);
|
||||
}
|
||||
},
|
||||
|
||||
setWishedIncome() {
|
||||
this.selected.income = this.selected.wishedIncome;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.director-view .content-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.list-panel {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detail-panel {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.director-table,
|
||||
.knowledge-table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.director-table th,
|
||||
.director-table td,
|
||||
.knowledge-table th,
|
||||
.knowledge-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: #f0f8ff;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.actions label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
max-height: 50vh;
|
||||
/* maximal 50% der Viewport-Höhe */
|
||||
min-height: 5em;
|
||||
/* mindestens 5em hoch */
|
||||
overflow-y: auto;
|
||||
/* nur vertikales Scrollen */
|
||||
}
|
||||
|
||||
.knowledge-table {
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
/* Spalten fest verteilen */
|
||||
}
|
||||
|
||||
/* Header-Zellen kleben oben im scrollenden Container */
|
||||
.knowledge-table thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: white;
|
||||
/* Hintergrund, damit darunterliegender Inhalt nicht durchscheint */
|
||||
z-index: 1;
|
||||
/* sicherstellen, dass der Header immer oben liegt */
|
||||
padding: 0.5em;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
/* Zellen-Styles für Körper und Kopf */
|
||||
.knowledge-table th,
|
||||
.knowledge-table td {
|
||||
padding: 0.5em;
|
||||
border: 1px solid #ccc;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.director-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
300
frontend/src/views/falukant/EducationView.vue
Normal file
@@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<div class="contenthidden">
|
||||
<StatusBar />
|
||||
<div class="contentscroll">
|
||||
<h2>{{ $t('falukant.education.title') }}</h2>
|
||||
<SimpleTabs v-model="activeTab" :tabs="tabs" />
|
||||
|
||||
<!-- SELF -->
|
||||
<div v-if="activeTab === 'self'">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.education.table.article') }}</th>
|
||||
<th>{{ $t('falukant.education.table.knowledge') }}</th>
|
||||
<th>{{ $t('falukant.education.table.activity') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="product in products" :key="product.id">
|
||||
<td>{{ $t(`falukant.product.${product.labelTr}`) }}</td>
|
||||
<td>{{ product.knowledges[0].knowledge }} %</td>
|
||||
<td>
|
||||
<button
|
||||
v-if="ownRunningEducations.length === 0"
|
||||
@click="learnItem(product.id, 'self')"
|
||||
>
|
||||
{{ $t('falukant.education.learn') }}
|
||||
({{ formatCost(getSelfCost(product.knowledges[0].knowledge)) }})
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
<button
|
||||
v-if="ownRunningEducations.length === 0"
|
||||
@click="learnAll('self')"
|
||||
>
|
||||
{{ $t('falukant.education.learnAll') }}
|
||||
({{ formatCost(getSelfAllCost()) }})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CHILDREN -->
|
||||
<div v-else-if="activeTab === 'children'">
|
||||
<div>
|
||||
<select v-model="activeChild">
|
||||
<option v-for="child in children" :key="child.id" :value="child.id">
|
||||
{{ child.name }} ({{ child.age }})
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<table v-if="activeChild">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.education.table.article') }}</th>
|
||||
<th>{{ $t('falukant.education.table.knowledge') }}</th>
|
||||
<th>{{ $t('falukant.education.table.activity') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="product in products" :key="product.id">
|
||||
<td>{{ $t(`falukant.product.${product.labelTr}`) }}</td>
|
||||
<td>{{ getChildKnowledge(product.id) }} %</td>
|
||||
<td>
|
||||
<button
|
||||
v-if="childNotInLearning()"
|
||||
@click="learnItem(product.id, 'children', activeChild)"
|
||||
>
|
||||
{{ $t('falukant.education.learn') }}
|
||||
({{ formatCost(getChildCost(product.id)) }})
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
<button
|
||||
v-if="childrenRunningEducations.length === 0"
|
||||
@click="learnAll('children', activeChild)"
|
||||
>
|
||||
{{ $t('falukant.education.learnAll') }}
|
||||
({{ formatCost(getChildrenAllCost(activeChild)) }})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DIRECTOR -->
|
||||
<div v-else-if="activeTab === 'director'">
|
||||
<div>
|
||||
<select v-model="activeDirector">
|
||||
<option v-for="director in directors" :key="director.id" :value="director.id">
|
||||
{{ director.character.nobleTitle.tr }}
|
||||
{{ director.character.definedFirstName.name }}
|
||||
{{ director.character.definedLastName.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<table v-if="activeDirector">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.education.table.article') }}</th>
|
||||
<th>{{ $t('falukant.education.table.knowledge') }}</th>
|
||||
<th>{{ $t('falukant.education.table.activity') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="product in products" :key="product.id">
|
||||
<td>{{ $t(`falukant.product.${product.labelTr}`) }}</td>
|
||||
<td>{{ getDirectorKnowledge(product.id) }} %</td>
|
||||
<td>
|
||||
<button
|
||||
v-if="directorNotInLearning()"
|
||||
@click="learnItem(product.id, 'director', getDirectorCharacterId())"
|
||||
>
|
||||
{{ $t('falukant.education.learn') }}
|
||||
({{ formatCost(getDirectorCost(product.id)) }})
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-if="activeDirector">
|
||||
<button
|
||||
v-if="directorNotInLearning()"
|
||||
@click="learnAll('director', getDirectorCharacterId())"
|
||||
>
|
||||
{{ $t('falukant.education.learnAll') }}
|
||||
({{ formatCost(getDirectorAllCost(getDirectorCharacterId())) }})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</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 apiClient from '@/utils/axios.js';
|
||||
import SimpleTabs from '@/components/SimpleTabs.vue'
|
||||
|
||||
const KNOWLEDGE_MAX = 99;
|
||||
|
||||
const COST_CONFIG = {
|
||||
one: { min: 50, max: 5000 },
|
||||
all: { min: 400, max: 40000 }
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'EducationView',
|
||||
components: { StatusBar, MessageDialog, ErrorDialog, SimpleTabs },
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'self',
|
||||
tabs: [
|
||||
{ value: 'self', label: 'falukant.education.self.title' },
|
||||
{ value: 'children', label: 'falukant.education.children.title' },
|
||||
{ value: 'director', label: 'falukant.education.director.title' }
|
||||
],
|
||||
products: [],
|
||||
ownRunningEducations: [],
|
||||
childrenRunningEducations: [],
|
||||
directorRunningEducations: [],
|
||||
directors: [],
|
||||
activeDirector: null,
|
||||
children: [],
|
||||
activeChild: null,
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadProducts();
|
||||
await this.loadEducations();
|
||||
await this.loadDirectors();
|
||||
await this.loadChildren();
|
||||
},
|
||||
methods: {
|
||||
// Basis-Funktion: lineare Interpolation
|
||||
computeCost(knowledgePercent, type = 'one') {
|
||||
const cfg = COST_CONFIG[type];
|
||||
const f = Math.min(Math.max(knowledgePercent, 0), KNOWLEDGE_MAX) / KNOWLEDGE_MAX;
|
||||
return cfg.min + (cfg.max - cfg.min) * f;
|
||||
},
|
||||
formatCost(value) {
|
||||
return Math.round(value).toLocaleString(this.$i18n.locale || 'de-DE');
|
||||
},
|
||||
|
||||
// SELF
|
||||
getSelfCost(knowledge) {
|
||||
return this.computeCost(knowledge, 'one');
|
||||
},
|
||||
getSelfAllCost() {
|
||||
const avg = this.products.reduce((sum, p) => sum + (p.knowledges[0].knowledge||0), 0) / this.products.length;
|
||||
return this.computeCost(avg, 'all');
|
||||
},
|
||||
|
||||
// CHILD
|
||||
getChildKnowledge(productId) {
|
||||
const child = this.children.find(c => c.id === this.activeChild);
|
||||
if (!child?.knowledge) return 0;
|
||||
const e = child.knowledge.find(k => k.id === productId);
|
||||
return e ? e.knowledge : 0;
|
||||
},
|
||||
getChildCost(productId) {
|
||||
return this.computeCost(this.getChildKnowledge(productId), 'one');
|
||||
},
|
||||
getChildrenAllCost(childId) {
|
||||
const child = this.children.find(c => c.id === childId);
|
||||
const avg = (child.knowledge || []).reduce((s,k) => s + k.knowledge, 0) / (child.knowledge?.length||1);
|
||||
return this.computeCost(avg, 'all');
|
||||
},
|
||||
childNotInLearning() {
|
||||
const child = this.children.find(c => c.id === this.activeChild);
|
||||
return !this.childrenRunningEducations.some(e => e.learningCharacter.id === child.id);
|
||||
},
|
||||
|
||||
// DIRECTOR
|
||||
getDirectorKnowledge(productId) {
|
||||
const dir = this.directors.find(d => d.id === this.activeDirector);
|
||||
const know = dir?.character?.knowledges?.find(k => k.productId === productId);
|
||||
return know ? know.knowledge : 0;
|
||||
},
|
||||
getDirectorCost(productId) {
|
||||
return this.computeCost(this.getDirectorKnowledge(productId), 'one');
|
||||
},
|
||||
getDirectorAllCost(dirCharId) {
|
||||
const dir = this.directors.find(d => d.character.id === dirCharId);
|
||||
const avg = (dir.character.knowledges || []).reduce((s,k) => s + k.knowledge, 0) / (dir.character.knowledges.length||1);
|
||||
return this.computeCost(avg, 'all');
|
||||
},
|
||||
getDirectorCharacterId() {
|
||||
return this.directors.find(d => d.id === this.activeDirector)?.character?.id;
|
||||
},
|
||||
directorNotInLearning() {
|
||||
const dirCharId = this.getDirectorCharacterId();
|
||||
return !this.directorRunningEducations.some(e => e.learningCharacter.id === dirCharId);
|
||||
},
|
||||
|
||||
// Laden & Aktionen
|
||||
async loadProducts() {
|
||||
const r = await apiClient.get('/api/falukant/products');
|
||||
this.products = r.data;
|
||||
},
|
||||
async loadEducations() {
|
||||
const r = await apiClient.get('/api/falukant/education');
|
||||
this.ownRunningEducations = r.data.filter(e => e.recipient.tr === 'self');
|
||||
this.childrenRunningEducations = r.data.filter(e => e.recipient.tr === 'children');
|
||||
this.directorRunningEducations = r.data.filter(e => e.recipient.tr === 'director');
|
||||
},
|
||||
async loadDirectors() {
|
||||
const r = await apiClient.get('/api/falukant/directors');
|
||||
this.directors = r.data;
|
||||
this.activeDirector = this.directors[0]?.id;
|
||||
},
|
||||
async loadChildren() {
|
||||
const r = await apiClient.get('/api/falukant/family/children');
|
||||
this.children = r.data;
|
||||
this.activeChild = this.children[0]?.id;
|
||||
},
|
||||
|
||||
async learnItem(item, student, studentId) {
|
||||
await apiClient.post('/api/falukant/education', { item, student, studentId });
|
||||
await this.loadEducations();
|
||||
},
|
||||
async learnAll(student, studentId) {
|
||||
await apiClient.post('/api/falukant/education', { item: 'all', student, studentId });
|
||||
await this.loadEducations();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
h2 {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.simple-tabs {
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.simple-tab {
|
||||
padding: 0.5rem 1rem;
|
||||
background: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.simple-tab.active {
|
||||
background: #F9A22C;
|
||||
color: #000;
|
||||
}
|
||||
.tab-content {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -41,6 +41,10 @@
|
||||
</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="characteristic in relationships[0].character2.characterTrait"
|
||||
@@ -115,10 +119,12 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(child, index) in children" :key="index">
|
||||
<td>
|
||||
{{ $t('falukant.titles.' + child.gender + '.' + child.title) }}
|
||||
<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>
|
||||
<button @click="showChildDetails(child)">
|
||||
@@ -191,6 +197,9 @@ export default {
|
||||
await this.loadGifts();
|
||||
await this.loadMoodAffects();
|
||||
await this.loadCharacterAffects();
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadFamilyData() {
|
||||
@@ -247,10 +256,10 @@ export default {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await apiClient.post('/api/falukant/family/gift'
|
||||
, { giftId: this.selectedGiftId });
|
||||
this.loadFamilyData();
|
||||
this.$root.$refs.messageDialog.open('tr:falukant.family.sendgift.success');
|
||||
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) {
|
||||
@@ -285,6 +294,26 @@ export default {
|
||||
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() {
|
||||
const message = JSON.parse(event.data);
|
||||
if (message.event === 'children_update') {
|
||||
this.loadFamilyData();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -369,15 +398,15 @@ h2 {
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 100%;
|
||||
background-color: #e5e7eb;
|
||||
border-radius: 0.25rem;
|
||||
overflow: hidden;
|
||||
height: 1rem;
|
||||
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;
|
||||
height: 100%;
|
||||
transition: width 0.3s ease, background-color 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
186
frontend/src/views/falukant/HealthView.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<div>
|
||||
<StatusBar />
|
||||
<h2>{{ $t('falukant.healthview.title') }}</h2>
|
||||
<div class="content-container">
|
||||
<div class="info-panel">
|
||||
<p>{{ $t('falukant.healthview.age') }}: {{ age }}</p>
|
||||
<p>{{ $t('falukant.healthview.status') }}: {{ healthState }}</p>
|
||||
</div>
|
||||
|
||||
<div class="measures-panel">
|
||||
<h3>{{ $t('falukant.healthview.measuresTaken') }}</h3>
|
||||
<table class="measures-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.healthview.measure') }}</th>
|
||||
<th>{{ $t('falukant.healthview.date') }}</th>
|
||||
<th>{{ $t('falukant.healthview.success') }}</th>
|
||||
<th>{{ $t('falukant.healthview.cost') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="entry in measuresTaken" :key="entry.id">
|
||||
<td>{{ $t(`falukant.healthview.measures.${entry.tr}`) }}</td>
|
||||
<td>{{ formatDate(entry.createdAt) }}</td>
|
||||
<td>{{ entry.success }}</td>
|
||||
<td>{{ formatPrice(entry.cost) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="actions">
|
||||
<label>
|
||||
{{ $t('falukant.healthview.selectMeasure') }}:
|
||||
<select v-model="selectedTr">
|
||||
<option value="" disabled>{{ $t('falukant.healthview.choose') }}</option>
|
||||
<option v-for="m in availableMeasures" :key="m.tr" :value="m.tr">
|
||||
{{ $t(`falukant.healthview.measures.${m.tr}`) }} ({{ formatPrice(m.cost) }})
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<button @click="performMeasure" :disabled="!selectedMeasure">
|
||||
{{ $t('falukant.healthview.perform') }}
|
||||
<span v-if="selectedMeasure"> ({{ formatPrice(selectedMeasure.cost) }})</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StatusBar from '@/components/falukant/StatusBar.vue';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'HealthView',
|
||||
components: { StatusBar },
|
||||
data() {
|
||||
return {
|
||||
age: 0,
|
||||
healthStatus: 0,
|
||||
measuresTaken: [],
|
||||
availableMeasures: [],
|
||||
selectedTr: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['daemonSocket']),
|
||||
/**
|
||||
* Selected measure object based on selectedTr
|
||||
*/
|
||||
selectedMeasure() {
|
||||
return this.availableMeasures.find(m => m.tr === this.selectedTr) || null;
|
||||
},
|
||||
/**
|
||||
* Health state translation key based on status
|
||||
*/
|
||||
healthState() {
|
||||
if (this.healthStatus > 90) return this.$t('falukant.health.amazing');
|
||||
if (this.healthStatus > 75) return this.$t('falukant.health.good');
|
||||
if (this.healthStatus > 50) return this.$t('falukant.health.normal');
|
||||
if (this.healthStatus > 25) return this.$t('falukant.health.bad');
|
||||
return this.$t('falukant.health.very_bad');
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadHealthData();
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.removeEventListener('message', this.handleDaemonMessage);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadHealthData() {
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/falukant/health');
|
||||
this.age = data.age;
|
||||
this.healthStatus = data.status;
|
||||
this.measuresTaken = data.history;
|
||||
this.availableMeasures = data.healthActivities;
|
||||
} catch (err) {
|
||||
console.error('Error loading health data', err);
|
||||
}
|
||||
},
|
||||
formatDate(dateStr) {
|
||||
const d = new Date(dateStr);
|
||||
return d.toLocaleDateString();
|
||||
},
|
||||
async performMeasure() {
|
||||
if (!this.selectedMeasure) return;
|
||||
try {
|
||||
await apiClient.post('/api/falukant/health', {
|
||||
measureTr: this.selectedTr
|
||||
});
|
||||
await this.loadHealthData();
|
||||
this.selectedTr = '';
|
||||
} catch (err) {
|
||||
console.error('Error performing measure', err);
|
||||
}
|
||||
},
|
||||
handleDaemonMessage(evt) {
|
||||
if (evt.data === 'ping') return;
|
||||
const msg = JSON.parse(evt.data);
|
||||
if (msg.event === 'healthupdated') {
|
||||
this.loadHealthData();
|
||||
}
|
||||
},
|
||||
formatPrice(value) {
|
||||
return new Intl.NumberFormat('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
h2 {
|
||||
padding-top: 20px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.info-panel {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.measures-panel {
|
||||
flex: 2;
|
||||
padding: 10px;
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.measures-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.measures-table th,
|
||||
.measures-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="houseView">
|
||||
<div class="house-view">
|
||||
<StatusBar />
|
||||
<h2>{{ $t('falukant.house.title') }}</h2>
|
||||
<div class="existingHouse">
|
||||
<div :style="houseStyle(picturePosition)" class="house"></div>
|
||||
<div class="statusreport">
|
||||
<div class="existing-house">
|
||||
<div :style="houseType ? houseStyle(houseType.position, 341) : {}" class="house"></div>
|
||||
<div class="status-panel">
|
||||
<h3>{{ $t('falukant.house.statusreport') }}</h3>
|
||||
<table>
|
||||
<thead>
|
||||
@@ -15,46 +15,54 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="status, index in status">
|
||||
<td>{{ $t(`falukant.house.status.${index}`) }}</td>
|
||||
<td>{{ status }} %</td>
|
||||
<td><button v-if="status < 100">{{ $t('falukant.house.renovate') }} ({{
|
||||
$t('falukant.house.cost') }}: {{ getRenovationCost(index, status) }}</button></td>
|
||||
<tr v-for="(value, key) in status" :key="key">
|
||||
<td>{{ $t(`falukant.house.status.${key}`) }}</td>
|
||||
<td>{{ value }}%</td>
|
||||
<td>
|
||||
<button v-if="value < 100" @click="renovate(key)">
|
||||
{{ $t('falukant.house.renovate') }} ({{ getRenovationCost(key, value) }})
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.house.worth') }}</td>
|
||||
<td>{{ getWorth(status) }}</td>
|
||||
<td><button @click="sellHouse">{{ $t('falukant.house.sell') }}</button></td>
|
||||
<td>{{ getWorth() }} {{ currency }}</td>
|
||||
<td>
|
||||
<button @click="renovateAll" :disabled="allRenovated">
|
||||
{{ $t('falukant.house.renovateAll') }} ({{ getAllRenovationCost() }})
|
||||
</button>
|
||||
<button @click="sellHouse">
|
||||
{{ $t('falukant.house.sell') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buyablehouses">
|
||||
|
||||
<div class="buyable-houses">
|
||||
<h3>{{ $t('falukant.house.buyablehouses') }}</h3>
|
||||
<div style="overflow:auto">
|
||||
<div style="display: flex; flex-direction: row" v-for="house in buyableHouses">
|
||||
<div style="width:100px; height:100px; display: hidden;">
|
||||
<div :style="houseStyle(house.houseType.position)" class="housePreview buyableHouseInfo"></div>
|
||||
</div>
|
||||
<div class="buyableHouseInfo">
|
||||
<h4 style="display: inline;">{{ $t('falukant.house.statusreport') }}</h4>
|
||||
<div class="houses-list">
|
||||
<div v-for="house in buyableHouses" :key="house.id" class="house-item">
|
||||
<div :style="house.houseType ? houseStyle(house.houseType.position, 114) : {}" class="house-preview"></div>
|
||||
<div class="house-info">
|
||||
<h4>{{ $t(`falukant.house.type.${house.houseType.labelTr}`) }}</h4>
|
||||
<table>
|
||||
<tbody>
|
||||
<template v-for="value, key in house">
|
||||
<tr v-if="key != 'houseType' && key != 'id'">
|
||||
<td>{{ $t(`falukant.house.status.${key}`) }}</td>
|
||||
<td>{{ value }} %</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-for="(val, prop) in house" :key="prop"
|
||||
v-if="['roofCondition','wallCondition','floorCondition','windowCondition'].includes(prop)">
|
||||
<td>{{ $t(`falukant.house.status.${prop}`) }}</td>
|
||||
<td>{{ val }}%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
{{ $t('falukant.house.price') }}: {{ buyCost(house) }}
|
||||
</div>
|
||||
<div>
|
||||
<button @click="buyHouse(house.id)">{{ $t('falukant.house.buy') }}</button>
|
||||
<div>
|
||||
{{ $t('falukant.house.price') }}: {{ buyCost(house) }}
|
||||
</div>
|
||||
<button @click="buyHouse(house.id)">
|
||||
{{ $t('falukant.house.buy') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,195 +73,209 @@
|
||||
<script>
|
||||
import StatusBar from '@/components/falukant/StatusBar.vue';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { mapState } from "vuex";
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'HouseView',
|
||||
components: {
|
||||
StatusBar
|
||||
},
|
||||
components: { StatusBar },
|
||||
data() {
|
||||
return {
|
||||
houseTypes: [],
|
||||
userHouse: {},
|
||||
userHouse: null,
|
||||
houseType: {},
|
||||
status: {},
|
||||
buyableHouses: [],
|
||||
picturePosition: 0,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadHouseTypes() {
|
||||
try {
|
||||
const houseTypesResult = await apiClient.get('/api/falukant/houses/types');
|
||||
this.houseTypes = houseTypesResult.data;
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
},
|
||||
async loadUserHouse() {
|
||||
try {
|
||||
const userHouseResult = await apiClient.get('/api/falukant/houses');
|
||||
Object.assign(this.userHouse, userHouseResult.data);
|
||||
const { houseType, ...houseStatus } = this.userHouse;
|
||||
this.status = houseStatus;
|
||||
this.picturePosition = parseInt(houseType.position);
|
||||
this.houseType = houseType;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden des Hauses:', error);
|
||||
this.userHouse = null;
|
||||
this.status = null;
|
||||
}
|
||||
},
|
||||
async loadBuyableHouses() {
|
||||
try {
|
||||
const buyableHousesResult = await apiClient.get('/api/falukant/houses/buyable');
|
||||
this.buyableHouses = buyableHousesResult.data;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der kaufbaren Häuser:', error);
|
||||
}
|
||||
},
|
||||
houseStyle(housePosition) {
|
||||
const columns = 3;
|
||||
const spriteSize = 341; // Breite & Höhe eines einzelnen Hauses
|
||||
let calculatePosition = Math.max(housePosition - 1, 0);
|
||||
const x = (calculatePosition % columns) * spriteSize;
|
||||
const y = Math.floor(calculatePosition / columns) * spriteSize;
|
||||
|
||||
return {
|
||||
backgroundImage: 'url("/images/falukant/houses.png")',
|
||||
backgroundPosition: `-${x}px -${y}px`,
|
||||
backgroundSize: `${columns * spriteSize}px auto`, // z.B. 1023px auto
|
||||
};
|
||||
},
|
||||
buyCost(house) {
|
||||
const houseQuality = (house.roofCondition + house.windowCondition + house.floorCondition + house.wallCondition) / 4;
|
||||
return (house.houseType.cost / 100 * houseQuality).toFixed(2);
|
||||
},
|
||||
getWorth() {
|
||||
const house = {...this.userHouse, houseType: this.houseType};
|
||||
const buyWorth = this.buyCost(house);
|
||||
return (buyWorth * 0.8).toFixed(2);
|
||||
},
|
||||
async buyHouse(houseId) {
|
||||
try {
|
||||
const response = await apiClient.post('/api/falukant/houses',
|
||||
{
|
||||
houseId: houseId,
|
||||
}
|
||||
);
|
||||
this.$router.push({ name: 'HouseView' });
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Kaufen des Hauses:', error);
|
||||
}
|
||||
},
|
||||
async getHouseData() {
|
||||
await this.loadUserHouse();
|
||||
await this.loadBuyableHouses();
|
||||
}
|
||||
currency: '€'
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['socket', 'daemonSocket']),
|
||||
getHouseStyle() {
|
||||
if (!this.userHouse || this.userHouse.position === undefined || this.userHouse.position === null) {
|
||||
return {};
|
||||
}
|
||||
return this.houseStyle(this.userHouse.position);
|
||||
},
|
||||
|
||||
getHouseType(position) {
|
||||
const houseType = this.houseTypes[position];
|
||||
return houseType;
|
||||
},
|
||||
getHouseStatus(position) {
|
||||
const houseStatus = this.houseStatuses[position];
|
||||
return houseStatus;
|
||||
},
|
||||
getRenovationCost(index, status) {
|
||||
const houseType = this.houseTypes[position];
|
||||
const renovationCost = houseType.renovationCosts[status];
|
||||
return renovationCost;
|
||||
},
|
||||
allRenovated() {
|
||||
return Object.values(this.status).every(v => v >= 100);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
methods: {
|
||||
async loadData() {
|
||||
try {
|
||||
const userRes = await apiClient.get('/api/falukant/houses');
|
||||
this.userHouse = userRes.data;
|
||||
this.houseType = this.userHouse.houseType;
|
||||
const { roofCondition, wallCondition, floorCondition, windowCondition } = this.userHouse;
|
||||
this.status = { roofCondition, wallCondition, floorCondition, windowCondition };
|
||||
|
||||
const buyRes = await apiClient.get('/api/falukant/houses/buyable');
|
||||
this.buyableHouses = buyRes.data;
|
||||
} catch (err) {
|
||||
console.error('Error loading house data', err);
|
||||
}
|
||||
},
|
||||
houseStyle(position, picSize) {
|
||||
const columns = 3;
|
||||
const size = picSize;
|
||||
const index = position - 1;
|
||||
const x = (index % columns) * size;
|
||||
const y = Math.floor(index / columns) * size;
|
||||
return {
|
||||
backgroundImage: 'url("/images/falukant/houses.png")',
|
||||
backgroundPosition: `-${x}px -${y}px`,
|
||||
backgroundSize: `${columns * size}px auto`
|
||||
};
|
||||
},
|
||||
formatPrice(value) {
|
||||
return new Intl.NumberFormat('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value);
|
||||
},
|
||||
getRenovationCost(key, value) {
|
||||
const base = this.userHouse.houseType.cost || 0;
|
||||
const weights = { roofCondition: 0.25, wallCondition: 0.25, floorCondition: 0.25, windowCondition: 0.25 };
|
||||
const weight = weights[key] || 0;
|
||||
const missing = 100 - value;
|
||||
const cost = (missing / 100) * base * weight;
|
||||
return this.formatPrice(cost);
|
||||
},
|
||||
getAllRenovationCost() {
|
||||
const total = Object.keys(this.status).reduce((sum, k) => {
|
||||
const raw = parseFloat(this.getRenovationCost(k, this.status[k]).replace(/\./g, '').replace(',', '.'));
|
||||
return sum + (isNaN(raw) ? 0 : raw);
|
||||
}, 0);
|
||||
return this.formatPrice(total * 0.8);
|
||||
},
|
||||
getWorth() {
|
||||
const vals = Object.values(this.status);
|
||||
if (!vals.length) return this.formatPrice(0);
|
||||
const avg = vals.reduce((s, v) => s + v, 0) / vals.length;
|
||||
const price = this.houseType.cost || 0;
|
||||
return this.formatPrice(price * avg / 100 * 0.8);
|
||||
},
|
||||
buyCost(house) {
|
||||
const avg = (house.roofCondition + house.wallCondition + house.floorCondition + house.windowCondition) / 4;
|
||||
return this.formatPrice(house.houseType.cost * avg / 100);
|
||||
},
|
||||
async renovate(key) {
|
||||
try {
|
||||
await apiClient.post('/api/falukant/houses/renovate', { element: key });
|
||||
await this.loadData();
|
||||
} catch (err) {
|
||||
console.error('Error renovating', err);
|
||||
}
|
||||
},
|
||||
async renovateAll() {
|
||||
try {
|
||||
await apiClient.post('/api/falukant/houses/renovate-all');
|
||||
await this.loadData();
|
||||
} catch (err) {
|
||||
console.error('Error renovating all', err);
|
||||
}
|
||||
},
|
||||
async sellHouse() {
|
||||
try {
|
||||
await apiClient.post('/api/falukant/houses/sell');
|
||||
await this.loadData();
|
||||
} catch (err) {
|
||||
console.error('Error selling house', err);
|
||||
}
|
||||
},
|
||||
async buyHouse(id) {
|
||||
try {
|
||||
await apiClient.post('/api/falukant/houses', { houseId: id });
|
||||
await this.loadData();
|
||||
} catch (err) {
|
||||
console.error('Error buying house', err);
|
||||
}
|
||||
},
|
||||
handleDaemonMessage(evt) {
|
||||
try {
|
||||
const msg = JSON.parse(evt.data);
|
||||
if (msg.event === 'houseupdated') this.loadData();
|
||||
} catch {}
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.loadHouseTypes();
|
||||
await this.getHouseData();
|
||||
if (this.socket) {
|
||||
this.socket.on("falukantHouseUpdate", this.getHouseData);
|
||||
}
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.addEventListener("message", this.handleDaemonSocketMessage);
|
||||
}
|
||||
await this.loadData();
|
||||
if (this.socket) this.socket.on('falukantHouseUpdate', this.loadData);
|
||||
if (this.daemonSocket) this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.socket) {
|
||||
this.socket.off("falukantHouseUpdate", this.fetchStatus);
|
||||
}
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.removeEventListener("message", this.handleDaemonSocketMessage);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
if (this.socket) this.socket.off('falukantHouseUpdate', this.loadData);
|
||||
if (this.daemonSocket) this.daemonSocket.removeEventListener('message', this.handleDaemonMessage);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
<style scoped>
|
||||
.house-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-top: 20px;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.existingHouse {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: 255px;
|
||||
}
|
||||
|
||||
Element {
|
||||
background-position: 71px 54px;
|
||||
.existing-house {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.house {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
background-repeat: no-repeat;
|
||||
image-rendering: crisp-edges;
|
||||
transform: scale(0.7);
|
||||
transform-origin: top left;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
width: 341px;
|
||||
height: 341px;
|
||||
}
|
||||
|
||||
.statusreport {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.buyableHouseInfo {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.housePreview {
|
||||
transform: scale(0.2);
|
||||
width: 341px;
|
||||
height: 341px;
|
||||
transform-origin: top left;
|
||||
background-repeat: no-repeat;
|
||||
image-rendering: crisp-edges;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.houseView {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
.status-panel {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.buyablehouses {
|
||||
.buyable-houses {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.houses-list {
|
||||
display: flex;
|
||||
flex-direction: column; /* vertical list */
|
||||
gap: 20px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto; /* vertical scroll if needed */
|
||||
}
|
||||
|
||||
.house-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.house-preview {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-repeat: no-repeat;
|
||||
image-rendering: crisp-edges;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
background-size: contain; /* scale image to container */
|
||||
background-position: center; /* center sprite */
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table th,
|
||||
table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
130
frontend/src/views/falukant/NobilityView.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div class="contenthidden">
|
||||
<StatusBar />
|
||||
<div class="contentscroll">
|
||||
<h2>{{ $t('falukant.nobility.title') }}</h2>
|
||||
<SimpleTabs v-model="activeTab" :tabs="tabs" />
|
||||
|
||||
<!-- OVERVIEW -->
|
||||
<div v-if="activeTab === 'overview'">
|
||||
<div class="nobility-section">
|
||||
<p>
|
||||
<strong>
|
||||
{{ $t(`falukant.titles.${gender}.${current.labelTr}`) }}
|
||||
</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ADVANCE -->
|
||||
<div v-else-if="activeTab === 'advance'">
|
||||
<div class="advance-section">
|
||||
<p>
|
||||
{{ $t('falukant.nobility.nextTitle') }}:
|
||||
<strong>{{ $t(`falukant.titles.${gender}.${next.labelTr}`) }}</strong>
|
||||
</p>
|
||||
<ul class="prerequisites">
|
||||
<li v-for="req in next.requirements" :key="req.titleId">
|
||||
{{ $t(`falukant.nobility.requirement.${req.requirementType}`, { amount: formatCost(req.requirementValue) }) }}
|
||||
</li>
|
||||
</ul>
|
||||
<button @click="applyAdvance" class="button" :disabled="!canAdvance || isAdvancing">
|
||||
<span v-if="!isAdvancing">{{ $t('falukant.nobility.advance.confirm') }}</span>
|
||||
<span v-else>{{ $t('falukant.nobility.advance.processing') }}</span>
|
||||
</button>
|
||||
<span>->{{ canAdvance }}, {{ isAdvancing }}<-</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StatusBar from '@/components/falukant/StatusBar.vue';
|
||||
import SimpleTabs from '@/components/SimpleTabs.vue';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'NobilityView',
|
||||
components: { StatusBar, SimpleTabs },
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'overview',
|
||||
tabs: [
|
||||
{ value: 'overview', label: 'falukant.nobility.tabs.overview' },
|
||||
{ value: 'advance', label: 'falukant.nobility.tabs.advance' }
|
||||
],
|
||||
current: { labelTr: '', requirements: [], charactersWithNobleTitle: [] },
|
||||
next: { labelTr: '', requirements: [] },
|
||||
isAdvancing: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(['daemonSocket', 'falukantData']),
|
||||
gender() {
|
||||
return this.current.charactersWithNobleTitle[0]?.gender || 'male';
|
||||
},
|
||||
canAdvance() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadNobility();
|
||||
if (this.daemonSocket) this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.daemonSocket) this.daemonSocket.removeEventListener('message', this.handleDaemonMessage);
|
||||
},
|
||||
methods: {
|
||||
async loadNobility() {
|
||||
try {
|
||||
const { data } = await apiClient.get('/api/falukant/nobility');
|
||||
this.current = data.current;
|
||||
this.next = data.next;
|
||||
} catch (err) {
|
||||
console.error('Error loading nobility:', err);
|
||||
}
|
||||
},
|
||||
async applyAdvance() {
|
||||
if (!this.canAdvance || this.isAdvancing) return;
|
||||
this.isAdvancing = true;
|
||||
try {
|
||||
await apiClient.post('/api/falukant/nobility/advance');
|
||||
await this.loadNobility();
|
||||
} catch (err) {
|
||||
console.error('Error advancing nobility:', err);
|
||||
} finally {
|
||||
this.isAdvancing = false;
|
||||
}
|
||||
},
|
||||
handleDaemonMessage(evt) {
|
||||
if (evt.data === 'ping') return;
|
||||
const msg = JSON.parse(evt.data);
|
||||
if (['nobilityChange', 'moneyChange'].includes(msg.event)) this.loadNobility();
|
||||
},
|
||||
formatCost(val) {
|
||||
return new Intl.NumberFormat(navigator.language, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(val);
|
||||
},
|
||||
async applyAdvance() {
|
||||
await apiClient.post('/api/falukant/nobility');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
h2 { padding-top: 20px; }
|
||||
.nobility-section,
|
||||
.advance-section {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
}
|
||||
.prerequisites {
|
||||
list-style: disc inside;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
301
frontend/src/views/falukant/ReputationView.vue
Normal file
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<div class="reputation-view">
|
||||
<StatusBar />
|
||||
<h2>{{ $t('falukant.reputation.title') }}</h2>
|
||||
|
||||
<div class="simple-tabs">
|
||||
<button v-for="tab in tabs" :key="tab.value" :class="['simple-tab', { active: activeTab === tab.value }]"
|
||||
@click="activeTab = tab.value">
|
||||
{{ $t(tab.label) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="tab-content">
|
||||
<div v-if="activeTab === 'overview'">
|
||||
<p>Deine aktuelle Reputation: …</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="activeTab === 'party'">
|
||||
<button @click="toggleNewPartyView">
|
||||
{{ $t('falukant.reputation.party.newpartyview.' + (newPartyView ? 'close' : 'open')) }}
|
||||
</button>
|
||||
|
||||
<div v-if="newPartyView" class="new-party-form">
|
||||
<label>
|
||||
{{ $t('falukant.reputation.party.newpartyview.type') }}:
|
||||
<select v-model.number="newPartyTypeId">
|
||||
<option v-for="type in partyTypes" :key="type.id" :value="type.id">
|
||||
{{ $t('falukant.party.type.' + type.tr) }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div v-if="newPartyTypeId" class="party-options">
|
||||
<label>
|
||||
{{ $t('falukant.reputation.party.music.label') }}:
|
||||
<select v-model.number="musicId">
|
||||
<option v-for="m in musicTypes" :key="m.id" :value="m.id">
|
||||
{{ $t(`falukant.reputation.party.music.${m.tr}`) }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
{{ $t('falukant.reputation.party.banquette.label') }}:
|
||||
<select v-model.number="banquetteId">
|
||||
<option v-for="b in banquetteTypes" :key="b.id" :value="b.id">
|
||||
{{ $t(`falukant.reputation.party.banquette.${b.tr}`) }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
{{ $t('falukant.reputation.party.servants.label') }}:
|
||||
<input type="number" v-model.number="servantRatio" min="1" max="50" />
|
||||
{{ $t('falukant.reputation.party.servants.perPersons') }}
|
||||
</label>
|
||||
|
||||
<label>
|
||||
{{ $t('falukant.reputation.party.esteemedInvites.label') }}:
|
||||
<multiselect v-model="selectedNobilityIds" :options="nobilityTitles" :multiple="true"
|
||||
track-by="id" label="labelTr" :close-on-select="false" :preserve-search="true"
|
||||
placeholder="">
|
||||
<template #option="{ option }">
|
||||
{{ $t('falukant.titles.male.' + option.labelTr) }}
|
||||
</template>
|
||||
<template #tag="{ option, remove }">
|
||||
<span class="multiselect__tag">
|
||||
{{ $t('falukant.titles.male.' + option.labelTr) }}
|
||||
<i @click="remove(option.id)" class="multiselect__tag-icon"></i>
|
||||
</span>
|
||||
</template>
|
||||
</multiselect>
|
||||
</label>
|
||||
|
||||
<p class="total-cost">
|
||||
{{ $t('falukant.reputation.party.totalCost') }}:
|
||||
{{ formattedCost }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button @click="orderParty()">
|
||||
{{ $t('falukant.reputation.party.order') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- In-Progress Parties -->
|
||||
<div class="separator-class">
|
||||
<h3>{{ $t('falukant.reputation.party.inProgress') }}</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.reputation.party.type') }}</th>
|
||||
<th>{{ $t('falukant.reputation.party.music.label') }}</th>
|
||||
<th>{{ $t('falukant.reputation.party.banquette.label') }}</th>
|
||||
<th>{{ $t('falukant.reputation.party.servants.label') }}</th>
|
||||
<th>{{ $t('falukant.reputation.party.cost') }}</th>
|
||||
<th>{{ $t('falukant.reputation.party.date') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="party in inProgressParties" :key="party.id">
|
||||
<td>{{ $t('falukant.party.type.' + party.partyType.tr) }}</td>
|
||||
<td>{{ $t('falukant.reputation.party.music.' + party.musicType.tr) }}</td>
|
||||
<td>{{ $t('falukant.reputation.party.banquette.' + party.banquetteType.tr) }}</td>
|
||||
<td>{{ party.servantRatio }}</td>
|
||||
<td>{{ party.cost.toLocaleString($i18n.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</td>
|
||||
<td>{{ new Date(party.createdAt).toLocaleString() }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Completed Parties -->
|
||||
<div class="separator-class">
|
||||
<h3>{{ $t('falukant.reputation.party.completed') }}</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.reputation.party.type') }}</th>
|
||||
<th>{{ $t('falukant.reputation.party.music.label') }}</th>
|
||||
<th>{{ $t('falukant.reputation.party.banquette.label') }}</th>
|
||||
<th>{{ $t('falukant.reputation.party.servants.label') }}</th>
|
||||
<th>{{ $t('falukant.reputation.party.cost') }}</th>
|
||||
<th>{{ $t('falukant.reputation.party.date') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="party in completedParties" :key="party.id">
|
||||
<td>{{ $t('falukant.party.type.' + party.partyType.tr) }}</td>
|
||||
<td>{{ $t('falukant.reputation.party.music.' + party.musicType.tr) }}</td>
|
||||
<td>{{ $t('falukant.reputation.party.banquette.' + party.banquetteType.tr) }}</td>
|
||||
<td>{{ party.servantRatio }}</td>
|
||||
<td>{{ party.cost.toLocaleString($i18n.locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }}</td>
|
||||
<td>{{ new Date(party.createdAt).toLocaleString() }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StatusBar from '@/components/falukant/StatusBar.vue'
|
||||
import apiClient from '@/utils/axios.js'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
|
||||
export default {
|
||||
name: 'ReputationView',
|
||||
components: { StatusBar, Multiselect },
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'overview',
|
||||
tabs: [
|
||||
{ value: 'overview', label: 'falukant.reputation.overview.title' },
|
||||
{ value: 'party', label: 'falukant.reputation.party.title' }
|
||||
],
|
||||
newPartyView: false,
|
||||
newPartyTypeId: null,
|
||||
partyTypes: [],
|
||||
musicId: null,
|
||||
musicTypes: [],
|
||||
banquetteId: null,
|
||||
banquetteTypes: [],
|
||||
nobilityTitles: [],
|
||||
selectedNobilityIds: [],
|
||||
servantRatio: 50,
|
||||
inProgressParties: [],
|
||||
completedParties: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleNewPartyView() {
|
||||
this.newPartyView = !this.newPartyView
|
||||
},
|
||||
async loadPartyTypes() {
|
||||
const { data } = await apiClient.get('/api/falukant/party/types');
|
||||
this.partyTypes = data.partyTypes;
|
||||
this.musicTypes = data.musicTypes;
|
||||
this.banquetteTypes = data.banquetteTypes;
|
||||
this.musicId = this.musicTypes[0]?.id;
|
||||
this.banquetteId = this.banquetteTypes[0]?.id;
|
||||
},
|
||||
async loadParties() {
|
||||
const { data } = await apiClient.get('/api/falukant/party');
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
this.inProgressParties = data.filter(party => {
|
||||
const partyDate = new Date(party.createdAt);
|
||||
return partyDate > yesterday;
|
||||
});
|
||||
this.completedParties = data.filter(party => {
|
||||
const partyDate = new Date(party.createdAt);
|
||||
return partyDate <= yesterday;
|
||||
});
|
||||
},
|
||||
async loadNobilityTitles() {
|
||||
this.nobilityTitles = await apiClient.get('/api/falukant/nobility/titels').then(r => r.data)
|
||||
},
|
||||
async orderParty() {
|
||||
await apiClient.post('/api/falukant/party', {
|
||||
partyTypeId: this.newPartyTypeId,
|
||||
musicId: this.musicId,
|
||||
banquetteId: this.banquetteId,
|
||||
nobilityIds: this.selectedNobilityIds.map(n => n.id ?? n),
|
||||
servantRatio: this.servantRatio
|
||||
});
|
||||
this.toggleNewPartyView();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
formattedCost() {
|
||||
const type = this.partyTypes.find(t => t.id === this.newPartyTypeId) || {};
|
||||
const music = this.musicTypes.find(m => m.id === this.musicId) || {};
|
||||
const banquette = this.banquetteTypes.find(b => b.id === this.banquetteId) || {};
|
||||
let cost = (type.cost || 0) + (music.cost || 0) + (banquette.cost || 0);
|
||||
cost += (50 / this.servantRatio - 1) * 1000;
|
||||
let nobilityCost = this.selectedNobilityIds.reduce((sum, id) => {
|
||||
const nob = this.nobilityTitles.find(n => n.id === id)
|
||||
return sum + ((nob?.id ^ 5) * 1000)
|
||||
}, 0);
|
||||
cost += nobilityCost;
|
||||
const locale = this.$i18n?.locale || 'de-DE';
|
||||
return cost.toLocaleString(locale, {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
const tabFromQuery = this.$route?.query?.tab;
|
||||
if (['overview','party'].includes(tabFromQuery)) {
|
||||
this.activeTab = tabFromQuery;
|
||||
}
|
||||
await this.loadPartyTypes();
|
||||
await this.loadNobilityTitles();
|
||||
await this.loadParties();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
h2 {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.simple-tabs {
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.simple-tab {
|
||||
padding: 0.5rem 1rem;
|
||||
background: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.simple-tab.active {
|
||||
background: #F9A22C;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.new-party-form {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.party-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.total-cost {
|
||||
font-weight: bold;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
display: inline-block !important;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.separator-class {
|
||||
border-top: 1px solid #ccc;
|
||||
margin-top: 1em;
|
||||
}
|
||||
</style>
|
||||