Spiel erweitert
@@ -20,6 +20,7 @@ class FalukantController {
|
|||||||
this.getDirectorProposals = this.getDirectorProposals.bind(this);
|
this.getDirectorProposals = this.getDirectorProposals.bind(this);
|
||||||
this.convertProposalToDirector = this.convertProposalToDirector.bind(this);
|
this.convertProposalToDirector = this.convertProposalToDirector.bind(this);
|
||||||
this.getDirectorForBranch = this.getDirectorForBranch.bind(this);
|
this.getDirectorForBranch = this.getDirectorForBranch.bind(this);
|
||||||
|
this.getAllDirectors = this.getAllDirectors.bind(this);
|
||||||
this.setSetting = this.setSetting.bind(this);
|
this.setSetting = this.setSetting.bind(this);
|
||||||
this.getFamily = this.getFamily.bind(this);
|
this.getFamily = this.getFamily.bind(this);
|
||||||
this.acceptMarriageProposal = this.acceptMarriageProposal.bind(this);
|
this.acceptMarriageProposal = this.acceptMarriageProposal.bind(this);
|
||||||
@@ -32,6 +33,21 @@ class FalukantController {
|
|||||||
this.getUserHouse = this.getUserHouse.bind(this);
|
this.getUserHouse = this.getUserHouse.bind(this);
|
||||||
this.getBuyableHouses = this.getBuyableHouses.bind(this);
|
this.getBuyableHouses = this.getBuyableHouses.bind(this);
|
||||||
this.buyUserHouse = this.buyUserHouse.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) {
|
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) {
|
async setSetting(req, res) {
|
||||||
try {
|
try {
|
||||||
const { userid: hashedUserId } = req.headers;
|
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) {
|
async sendGift(req, res) {
|
||||||
try {
|
try {
|
||||||
const { userid: hashedUserId } = req.headers;
|
const { userid: hashedUserId } = req.headers;
|
||||||
@@ -464,6 +512,165 @@ class FalukantController {
|
|||||||
console.log(error);
|
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;
|
export default FalukantController;
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ const menuStructure = {
|
|||||||
visible: ["hasfalukantaccount"],
|
visible: ["hasfalukantaccount"],
|
||||||
path: "/falukant/nobility"
|
path: "/falukant/nobility"
|
||||||
},
|
},
|
||||||
|
church: {
|
||||||
|
visible: ["hasfalukantaccount"],
|
||||||
|
path: "/falukant/church"
|
||||||
|
},
|
||||||
politics: {
|
politics: {
|
||||||
visible: ["hasfalukantaccount"],
|
visible: ["hasfalukantaccount"],
|
||||||
path: "/falukant/politics"
|
path: "/falukant/politics"
|
||||||
|
|||||||
@@ -65,6 +65,26 @@ import PromotionalGiftLog from './falukant/log/promotional_gift.js';
|
|||||||
import HouseType from './falukant/type/house.js';
|
import HouseType from './falukant/type/house.js';
|
||||||
import BuyableHouse from './falukant/data/buyable_house.js';
|
import BuyableHouse from './falukant/data/buyable_house.js';
|
||||||
import UserHouse from './falukant/data/user_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() {
|
export default function setupAssociations() {
|
||||||
// UserParam related associations
|
// UserParam related associations
|
||||||
@@ -315,31 +335,31 @@ export default function setupAssociations() {
|
|||||||
Notification.belongsTo(FalukantUser, { foreignKey: 'userId', as: 'user' });
|
Notification.belongsTo(FalukantUser, { foreignKey: 'userId', as: 'user' });
|
||||||
FalukantUser.hasMany(Notification, { foreignKey: 'userId', as: 'notifications' });
|
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' });
|
FalukantCharacter.hasMany(MarriageProposal, { foreignKey: 'requesterCharacterId', as: 'initiatedProposals' });
|
||||||
|
|
||||||
MarriageProposal.belongsTo(FalukantCharacter, {foreignKey: 'proposedCharacterId', as: 'proposedCharacter', });
|
MarriageProposal.belongsTo(FalukantCharacter, { foreignKey: 'proposedCharacterId', as: 'proposedCharacter', });
|
||||||
FalukantCharacter.hasMany(MarriageProposal, {foreignKey: 'proposedCharacterId', as: 'receivedProposals' });
|
FalukantCharacter.hasMany(MarriageProposal, { foreignKey: 'proposedCharacterId', as: 'receivedProposals' });
|
||||||
|
|
||||||
FalukantCharacter.belongsToMany(CharacterTrait, {through: FalukantCharacterTrait, foreignKey: 'character_id', as: 'traits', });
|
FalukantCharacter.belongsToMany(CharacterTrait, { through: FalukantCharacterTrait, foreignKey: 'character_id', as: 'traits', });
|
||||||
CharacterTrait.belongsToMany(FalukantCharacter, {through: FalukantCharacterTrait, foreignKey: 'trait_id', as: 'characters', });
|
CharacterTrait.belongsToMany(FalukantCharacter, { through: FalukantCharacterTrait, foreignKey: 'trait_id', as: 'characters', });
|
||||||
|
|
||||||
Mood.hasMany(FalukantCharacter, {foreignKey: 'mood_id', as: 'moods'});
|
Mood.hasMany(FalukantCharacter, { foreignKey: 'mood_id', as: 'moods' });
|
||||||
FalukantCharacter.belongsTo(Mood, {foreignKey: 'mood_id', as: 'mood'});
|
FalukantCharacter.belongsTo(Mood, { foreignKey: 'mood_id', as: 'mood' });
|
||||||
|
|
||||||
PromotionalGift.belongsToMany(CharacterTrait, {through: PromotionalGiftCharacterTrait, foreignKey: 'gift_id', as: 'traits',});
|
PromotionalGift.belongsToMany(CharacterTrait, { through: PromotionalGiftCharacterTrait, foreignKey: 'gift_id', as: 'traits', });
|
||||||
CharacterTrait.belongsToMany(PromotionalGift, {through: PromotionalGiftCharacterTrait, foreignKey: 'trait_id', as: 'gifts',});
|
CharacterTrait.belongsToMany(PromotionalGift, { through: PromotionalGiftCharacterTrait, foreignKey: 'trait_id', as: 'gifts', });
|
||||||
|
|
||||||
PromotionalGift.belongsToMany(Mood, {through: PromotionalGiftMood, foreignKey: 'gift_id', as: 'moods',});
|
PromotionalGift.belongsToMany(Mood, { through: PromotionalGiftMood, foreignKey: 'gift_id', as: 'moods', });
|
||||||
Mood.belongsToMany(PromotionalGift, {through: PromotionalGiftMood, foreignKey: 'mood_id', as: 'gifts',});
|
Mood.belongsToMany(PromotionalGift, { through: PromotionalGiftMood, foreignKey: 'mood_id', as: 'gifts', });
|
||||||
|
|
||||||
Relationship.belongsTo(RelationshipType, { foreignKey: 'relationshipTypeId', as: 'relationshipType' });
|
Relationship.belongsTo(RelationshipType, { foreignKey: 'relationshipTypeId', as: 'relationshipType' });
|
||||||
RelationshipType.hasMany(Relationship, { foreignKey: 'relationshipTypeId', as: 'relationships' });
|
RelationshipType.hasMany(Relationship, { foreignKey: 'relationshipTypeId', as: 'relationships' });
|
||||||
|
|
||||||
Relationship.belongsTo(FalukantCharacter, {foreignKey: 'character1Id', as: 'character1', });
|
Relationship.belongsTo(FalukantCharacter, { foreignKey: 'character1Id', as: 'character1', });
|
||||||
Relationship.belongsTo(FalukantCharacter, {foreignKey: 'character2Id', as: 'character2', });
|
Relationship.belongsTo(FalukantCharacter, { foreignKey: 'character2Id', as: 'character2', });
|
||||||
FalukantCharacter.hasMany(Relationship, {foreignKey: 'character1Id', as: 'relationshipsAsCharacter1', });
|
FalukantCharacter.hasMany(Relationship, { foreignKey: 'character1Id', as: 'relationshipsAsCharacter1', });
|
||||||
FalukantCharacter.hasMany(Relationship, {foreignKey: 'character2Id', as: 'relationshipsAsCharacter2', });
|
FalukantCharacter.hasMany(Relationship, { foreignKey: 'character2Id', as: 'relationshipsAsCharacter2', });
|
||||||
|
|
||||||
PromotionalGiftLog.belongsTo(PromotionalGift, { foreignKey: 'giftId', as: 'gift' });
|
PromotionalGiftLog.belongsTo(PromotionalGift, { foreignKey: 'giftId', as: 'gift' });
|
||||||
PromotionalGift.hasMany(PromotionalGiftLog, { foreignKey: 'giftId', as: 'logs' });
|
PromotionalGift.hasMany(PromotionalGiftLog, { foreignKey: 'giftId', as: 'logs' });
|
||||||
@@ -367,4 +387,262 @@ export default function setupAssociations() {
|
|||||||
|
|
||||||
TitleOfNobility.hasMany(HouseType, { foreignKey: 'minimumNobleTitle', as: 'houseTypes' });
|
TitleOfNobility.hasMany(HouseType, { foreignKey: 'minimumNobleTitle', as: 'houseTypes' });
|
||||||
HouseType.belongsTo(TitleOfNobility, { foreignKey: 'minimumNobleTitle', as: 'titleOfNobility' });
|
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,
|
type: DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
|
level: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
sequelize,
|
sequelize,
|
||||||
modelName: 'Title',
|
modelName: 'Title',
|
||||||
|
|||||||
@@ -4,12 +4,16 @@ import { sequelize } from '../../../utils/sequelize.js';
|
|||||||
class TitleRequirement extends Model { }
|
class TitleRequirement extends Model { }
|
||||||
|
|
||||||
TitleRequirement.init({
|
TitleRequirement.init({
|
||||||
titleId: {
|
id : {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
primaryKey: true,
|
primaryKey: true,
|
||||||
autoIncrement: true,
|
autoIncrement: true,
|
||||||
},
|
},
|
||||||
|
titleId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
requirementType: {
|
requirementType: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
@@ -25,6 +29,13 @@ TitleRequirement.init({
|
|||||||
schema: 'falukant_type',
|
schema: 'falukant_type',
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
underscored: true,
|
underscored: true,
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
unique: true,
|
||||||
|
fields: ['title_id', 'requirement_type'],
|
||||||
|
name: 'title_requirement_titleid_reqtype_unique'
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
export default TitleRequirement;
|
export default TitleRequirement;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// models/index.js
|
||||||
|
|
||||||
import SettingsType from './type/settings.js';
|
import SettingsType from './type/settings.js';
|
||||||
import UserParamValue from './type/user_param_value.js';
|
import UserParamValue from './type/user_param_value.js';
|
||||||
import UserParamType from './type/user_param.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 MessageImage from './forum/message_image.js';
|
||||||
import ForumForumPermission from './forum/forum_forum_permission.js';
|
import ForumForumPermission from './forum/forum_forum_permission.js';
|
||||||
import Friendship from './community/friendship.js';
|
import Friendship from './community/friendship.js';
|
||||||
|
|
||||||
import FalukantUser from './falukant/data/user.js';
|
import FalukantUser from './falukant/data/user.js';
|
||||||
import RegionType from './falukant/type/region.js';
|
import RegionType from './falukant/type/region.js';
|
||||||
import RegionData from './falukant/data/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 Notification from './falukant/log/notification.js';
|
||||||
import MarriageProposal from './falukant/data/marriage_proposal.js';
|
import MarriageProposal from './falukant/data/marriage_proposal.js';
|
||||||
import RelationshipType from './falukant/type/relationship.js';
|
import RelationshipType from './falukant/type/relationship.js';
|
||||||
|
import Relationship from './falukant/data/relationship.js';
|
||||||
import CharacterTrait from './falukant/type/character_trait.js';
|
import CharacterTrait from './falukant/type/character_trait.js';
|
||||||
import FalukantCharacterTrait from './falukant/data/falukant_character_trait.js';
|
import FalukantCharacterTrait from './falukant/data/falukant_character_trait.js';
|
||||||
import Mood from './falukant/type/mood.js';
|
import Mood from './falukant/type/mood.js';
|
||||||
import PromotionalGift from './falukant/type/promotional_gift.js';
|
import PromotionalGift from './falukant/type/promotional_gift.js';
|
||||||
import PromotionalGiftCharacterTrait from './falukant/predefine/promotional_gift_character_trait.js';
|
import PromotionalGiftCharacterTrait from './falukant/predefine/promotional_gift_character_trait.js';
|
||||||
import PromotionalGiftMood from './falukant/predefine/promotional_gift_mood.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 PromotionalGiftLog from './falukant/log/promotional_gift.js';
|
||||||
import HouseType from './falukant/type/house.js';
|
import HouseType from './falukant/type/house.js';
|
||||||
import BuyableHouse from './falukant/data/buyable_house.js';
|
import BuyableHouse from './falukant/data/buyable_house.js';
|
||||||
import UserHouse from './falukant/data/user_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 = {
|
const models = {
|
||||||
SettingsType,
|
SettingsType,
|
||||||
UserParamValue,
|
UserParamValue,
|
||||||
UserParamType,
|
UserParamType,
|
||||||
UserRightType,
|
UserRightType,
|
||||||
User,
|
User,
|
||||||
UserParam,
|
UserParam,
|
||||||
Login,
|
Login,
|
||||||
UserRight,
|
UserRight,
|
||||||
InterestType,
|
InterestType,
|
||||||
InterestTranslationType,
|
InterestTranslationType,
|
||||||
Interest,
|
Interest,
|
||||||
ContactMessage,
|
ContactMessage,
|
||||||
UserParamVisibilityType,
|
UserParamVisibilityType,
|
||||||
UserParamVisibility,
|
UserParamVisibility,
|
||||||
Folder,
|
Folder,
|
||||||
Image,
|
Image,
|
||||||
ImageVisibilityType,
|
ImageVisibilityType,
|
||||||
ImageVisibilityUser,
|
ImageVisibilityUser,
|
||||||
FolderImageVisibility,
|
FolderImageVisibility,
|
||||||
ImageImageVisibility,
|
ImageImageVisibility,
|
||||||
FolderVisibilityUser,
|
FolderVisibilityUser,
|
||||||
GuestbookEntry,
|
GuestbookEntry,
|
||||||
DiaryHistory,
|
DiaryHistory,
|
||||||
Diary,
|
Diary,
|
||||||
Forum,
|
Forum,
|
||||||
ForumPermission,
|
ForumPermission,
|
||||||
ForumForumPermission,
|
ForumForumPermission,
|
||||||
ForumUserPermission,
|
ForumUserPermission,
|
||||||
Title,
|
Title,
|
||||||
TitleHistory,
|
TitleHistory,
|
||||||
Message,
|
Message,
|
||||||
MessageHistory,
|
MessageHistory,
|
||||||
MessageImage,
|
MessageImage,
|
||||||
Friendship,
|
Friendship,
|
||||||
RegionType,
|
|
||||||
RegionData,
|
// Falukant core
|
||||||
FalukantUser,
|
RegionType,
|
||||||
FalukantPredefineFirstname,
|
RegionData,
|
||||||
FalukantPredefineLastname,
|
FalukantUser,
|
||||||
FalukantCharacter,
|
FalukantPredefineFirstname,
|
||||||
FalukantStock,
|
FalukantPredefineLastname,
|
||||||
FalukantStockType,
|
FalukantCharacter,
|
||||||
ProductType,
|
FalukantStock,
|
||||||
Knowledge,
|
FalukantStockType,
|
||||||
TitleOfNobility,
|
ProductType,
|
||||||
TitleRequirement,
|
Knowledge,
|
||||||
BranchType,
|
TitleOfNobility,
|
||||||
Branch,
|
TitleRequirement,
|
||||||
Production,
|
BranchType,
|
||||||
Inventory,
|
Branch,
|
||||||
BuyableStock,
|
Production,
|
||||||
MoneyFlow,
|
Inventory,
|
||||||
Director,
|
BuyableStock,
|
||||||
DirectorProposal,
|
MoneyFlow,
|
||||||
TownProductWorth,
|
Director,
|
||||||
DayProduction,
|
DirectorProposal,
|
||||||
DaySell,
|
TownProductWorth,
|
||||||
Notification,
|
DayProduction,
|
||||||
MarriageProposal,
|
DaySell,
|
||||||
RelationshipType,
|
Notification,
|
||||||
Relationship,
|
MarriageProposal,
|
||||||
CharacterTrait,
|
RelationshipType,
|
||||||
FalukantCharacterTrait,
|
Relationship,
|
||||||
Mood,
|
CharacterTrait,
|
||||||
PromotionalGift,
|
FalukantCharacterTrait,
|
||||||
PromotionalGiftCharacterTrait,
|
Mood,
|
||||||
PromotionalGiftMood,
|
PromotionalGift,
|
||||||
PromotionalGiftLog,
|
PromotionalGiftCharacterTrait,
|
||||||
HouseType,
|
PromotionalGiftMood,
|
||||||
BuyableHouse,
|
PromotionalGiftLog,
|
||||||
UserHouse,
|
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;
|
export default models;
|
||||||
|
|||||||
@@ -180,6 +180,67 @@ export async function createTriggers() {
|
|||||||
$function$;
|
$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 {
|
try {
|
||||||
await sequelize.query(createTriggerFunction);
|
await sequelize.query(createTriggerFunction);
|
||||||
await sequelize.query(createInsertTrigger);
|
await sequelize.query(createInsertTrigger);
|
||||||
@@ -193,6 +254,9 @@ export async function createTriggers() {
|
|||||||
await sequelize.query(createKnowledgeTriggerMethod);
|
await sequelize.query(createKnowledgeTriggerMethod);
|
||||||
await sequelize.query(createKnowledgeTrigger);
|
await sequelize.query(createKnowledgeTrigger);
|
||||||
await sequelize.query(updateMoney);
|
await sequelize.query(updateMoney);
|
||||||
|
await sequelize.query(createChildRelationNameFunction);
|
||||||
|
await sequelize.query(createChildRelationNameTrigger);
|
||||||
|
await sequelize.query(createRandomMoodUpdateMethod);
|
||||||
await initializeCharacterTraitTrigger();
|
await initializeCharacterTraitTrigger();
|
||||||
|
|
||||||
console.log('Triggers created successfully');
|
console.log('Triggers created successfully');
|
||||||
@@ -250,4 +314,3 @@ export const initializeCharacterTraitTrigger = async () => {
|
|||||||
console.error('❌ Fehler beim Erstellen des Triggers:', error);
|
console.error('❌ Fehler beim Erstellen des Triggers:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6,6 +6,8 @@ const falukantController = new FalukantController();
|
|||||||
|
|
||||||
router.get('/user', falukantController.getUser);
|
router.get('/user', falukantController.getUser);
|
||||||
router.post('/user', falukantController.createUser);
|
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/randomfirstname/:gender', falukantController.randomFirstName);
|
||||||
router.get('/name/randomlastname', falukantController.randomLastName);
|
router.get('/name/randomlastname', falukantController.randomLastName);
|
||||||
router.get('/info', falukantController.getInfo);
|
router.get('/info', falukantController.getInfo);
|
||||||
@@ -30,15 +32,30 @@ router.post('/director/proposal', falukantController.getDirectorProposals);
|
|||||||
router.post('/director/convertproposal', falukantController.convertProposalToDirector);
|
router.post('/director/convertproposal', falukantController.convertProposalToDirector);
|
||||||
router.post('/director/settings', falukantController.setSetting);
|
router.post('/director/settings', falukantController.setSetting);
|
||||||
router.get('/director/:branchId', falukantController.getDirectorForBranch);
|
router.get('/director/:branchId', falukantController.getDirectorForBranch);
|
||||||
|
router.get('/directors', falukantController.getAllDirectors);
|
||||||
|
router.post('/directors', falukantController.updateDirector);
|
||||||
router.post('/family/acceptmarriageproposal', falukantController.acceptMarriageProposal);
|
router.post('/family/acceptmarriageproposal', falukantController.acceptMarriageProposal);
|
||||||
router.get('/family/gifts', falukantController.getGifts);
|
router.get('/family/gifts', falukantController.getGifts);
|
||||||
|
router.get('/family/children', falukantController.getChildren);
|
||||||
router.post('/family/gift', falukantController.sendGift);
|
router.post('/family/gift', falukantController.sendGift);
|
||||||
router.get('/family', falukantController.getFamily);
|
router.get('/family', falukantController.getFamily);
|
||||||
router.get('/nobility/titels', falukantController.getTitelsOfNobility);
|
router.get('/nobility/titels', falukantController.getTitelsOfNobility);
|
||||||
router.get('/houses/types', falukantController.getHouseTypes);
|
router.get('/houses/types', falukantController.getHouseTypes);
|
||||||
router.get('/houses/buyable', falukantController.getBuyableHouses);
|
router.get('/houses/buyable', falukantController.getBuyableHouses);
|
||||||
router.get('/mood/affect', falukantController.getMoodAffect);
|
|
||||||
router.get('/character/affect', falukantController.getCharacterAffect);
|
|
||||||
router.get('/houses', falukantController.getUserHouse);
|
router.get('/houses', falukantController.getUserHouse);
|
||||||
router.post('/houses', falukantController.buyUserHouse);
|
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;
|
export default router;
|
||||||
|
|||||||
@@ -267,73 +267,81 @@ async function initializeFalukantProducts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function initializeFalukantTitles() {
|
async function initializeFalukantTitles() {
|
||||||
await TitleOfNobility.bulkCreate([
|
try {
|
||||||
{ labelTr: "noncivil" },
|
await TitleOfNobility.bulkCreate([
|
||||||
{ labelTr: "civil" },
|
{ labelTr: "noncivil", level: 1 },
|
||||||
{ labelTr: "sir" },
|
{ labelTr: "civil", level: 2 },
|
||||||
{ labelTr: "townlord" },
|
{ labelTr: "sir", level: 3 },
|
||||||
{ labelTr: "by" },
|
{ labelTr: "townlord", level: 4 },
|
||||||
{ labelTr: "landlord" },
|
{ labelTr: "by", level: 5 },
|
||||||
{ labelTr: "knight" },
|
{ labelTr: "landlord", level: 6 },
|
||||||
{ labelTr: "baron" },
|
{ labelTr: "knight", level: 7 },
|
||||||
{ labelTr: "count" },
|
{ labelTr: "baron", level: 8 },
|
||||||
{ labelTr: "palsgrave" },
|
{ labelTr: "count", level: 9 },
|
||||||
{ labelTr: "margrave" },
|
{ labelTr: "palsgrave", level: 10 },
|
||||||
{ labelTr: "landgrave" },
|
{ labelTr: "margrave", level: 11 },
|
||||||
{ labelTr: "ruler" },
|
{ labelTr: "landgrave", level: 12 },
|
||||||
{ labelTr: "elector" },
|
{ labelTr: "ruler", level: 13 },
|
||||||
{ labelTr: "imperial-prince" },
|
{ labelTr: "elector", level: 14 },
|
||||||
{ labelTr: "duke" },
|
{ labelTr: "imperial-prince", level: 15 },
|
||||||
{ labelTr: "grand-duke" },
|
{ labelTr: "duke", level: 16 },
|
||||||
{ labelTr: "prince-regent" },
|
{ labelTr: "grand-duke", level: 17 },
|
||||||
{ labelTr: "king" },
|
{ labelTr: "prince-regent", level: 18 },
|
||||||
], {
|
{ labelTr: "king", level: 19 },
|
||||||
updateOnDuplicate: ['labelTr'],
|
], {
|
||||||
});
|
updateOnDuplicate: ['labelTr'],
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing Falukant titles:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initializeFalukantTitleRequirements() {
|
async function initializeFalukantTitleRequirements() {
|
||||||
const titleRequirements = [
|
const titleRequirements = [
|
||||||
{ labelTr: "civil", requirements: [{ type: "money", value: 500 }] },
|
{ labelTr: "civil", requirements: [{ type: "money", value: 5000 }, { type: "cost", value: 500 }] },
|
||||||
{ labelTr: "sir", requirements: [{ type: "branches", value: 2 }] },
|
{ labelTr: "sir", requirements: [{ type: "branches", value: 2 }, { type: "cost", value: 1000 }] },
|
||||||
{ labelTr: "townlord", requirements: [] },
|
{ labelTr: "townlord", requirements: [{ type: "cost", value: 3000 }] },
|
||||||
{ labelTr: "by", requirements: [] },
|
{ labelTr: "by", requirements: [{ type: "cost", value: 6000 }] },
|
||||||
{ labelTr: "landlord", requirements: [] },
|
{ labelTr: "landlord", requirements: [{ type: "cost", value: 9000 }] },
|
||||||
{ labelTr: "knight", requirements: [] },
|
{ labelTr: "knight", requirements: [{ type: "cost", value: 11000 }] },
|
||||||
{ labelTr: "baron", requirements: [{ type: "branches", value: 4 }] },
|
{ labelTr: "baron", requirements: [{ type: "branches", value: 4 }, { type: "cost", value: 15000 }] },
|
||||||
{ labelTr: "count", requirements: [] },
|
{ labelTr: "count", requirements: [{ type: "cost", value: 19000 }] },
|
||||||
{ labelTr: "palsgrave", requirements: [] },
|
{ labelTr: "palsgrave", requirements: [{ type: "cost", value: 25000 }] },
|
||||||
{ labelTr: "margrave", requirements: [] },
|
{ labelTr: "margrave", requirements: [{ type: "cost", value: 33000 }] },
|
||||||
{ labelTr: "landgrave", requirements: [] },
|
{ labelTr: "landgrave", requirements: [{ type: "cost", value: 47000 }] },
|
||||||
{ labelTr: "ruler", requirements: [] },
|
{ labelTr: "ruler", requirements: [{ type: "cost", value: 66000 }] },
|
||||||
{ labelTr: "elector", requirements: [] },
|
{ labelTr: "elector", requirements: [{ type: "cost", value: 79000 }] },
|
||||||
{ labelTr: "imperial-prince", requirements: [] },
|
{ labelTr: "imperial-prince", requirements: [{ type: "cost", value: 99999 }] },
|
||||||
{ labelTr: "duke", requirements: [] },
|
{ labelTr: "duke", requirements: [{ type: "cost", value: 130000 }] },
|
||||||
{ labelTr: "grand-duke", requirements: [] },
|
{ labelTr: "grand-duke",requirements: [{ type: "cost", value: 170000 }] },
|
||||||
{ labelTr: "prince-regent", requirements: [] },
|
{ labelTr: "prince-regent", requirements: [{ type: "cost", value: 270000 }] },
|
||||||
{ labelTr: "king", requirements: [] },
|
{ labelTr: "king", requirements: [{ type: "cost", value: 500000 }] },
|
||||||
];
|
];
|
||||||
|
|
||||||
const titles = await TitleOfNobility.findAll();
|
const titles = await TitleOfNobility.findAll();
|
||||||
const requirementsToInsert = [];
|
const requirementsToInsert = [];
|
||||||
|
|
||||||
for (let i = 0; i < titleRequirements.length; i++) {
|
for (let i = 0; i < titleRequirements.length; i++) {
|
||||||
const titleRequirement = titleRequirements[i];
|
const titleReq = titleRequirements[i];
|
||||||
const title = titles.find(t => t.labelTr === titleRequirement.labelTr);
|
const title = titles.find(t => t.labelTr === titleReq.labelTr);
|
||||||
if (!title) continue;
|
if (!title) continue;
|
||||||
|
|
||||||
if (i > 1) {
|
if (i > 1) {
|
||||||
const moneyRequirement = {
|
titleReq.requirements.push({
|
||||||
type: "money",
|
type: "money",
|
||||||
value: 5000 * Math.pow(3, i - 1),
|
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({
|
requirementsToInsert.push({
|
||||||
titleId: title.id,
|
titleId: title.id,
|
||||||
requirementType: requirement.type,
|
requirementType: req.type,
|
||||||
requirementValue: requirement.value,
|
requirementValue: req.value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await TitleRequirement.bulkCreate(requirementsToInsert, { ignoreDuplicates: true });
|
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 PromotionalGiftMood from "../../models/falukant/predefine/promotional_gift_mood.js";
|
||||||
import HouseType from '../../models/falukant/type/house.js';
|
import HouseType from '../../models/falukant/type/house.js';
|
||||||
import TitleOfNobility from "../../models/falukant/type/title_of_nobility.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 () => {
|
export const initializeFalukantTypes = async () => {
|
||||||
await initializeFalukantTypeRegions();
|
await initializeFalukantTypeRegions();
|
||||||
@@ -17,6 +21,10 @@ export const initializeFalukantTypes = async () => {
|
|||||||
await initializeFalukantPromotionalGifts();
|
await initializeFalukantPromotionalGifts();
|
||||||
await initializePromotionalGiftMoodLinks();
|
await initializePromotionalGiftMoodLinks();
|
||||||
await initializeFalukantHouseTypes();
|
await initializeFalukantHouseTypes();
|
||||||
|
await initializeFalukantPartyTypes();
|
||||||
|
await initializeFalukantMusicTypes();
|
||||||
|
await initializeFalukantBanquetteTypes();
|
||||||
|
await initializeLearnerTypes();
|
||||||
};
|
};
|
||||||
|
|
||||||
const regionTypes = [];
|
const regionTypes = [];
|
||||||
@@ -208,15 +216,44 @@ const promotionalGiftMoodLinks = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const houseTypes = [
|
const houseTypes = [
|
||||||
{ labelTr: 'Unter der Brücke', abbr: 'under_bridge', cost: 10, position: 1, minimumTitle: 'noncivil' },
|
{ labelTr: 'Unter der Brücke', abbr: 'under_bridge', cost: 0, position: 1, minimumTitle: 'noncivil' },
|
||||||
{ labelTr: 'Strohhütte', abbr: 'straw_hut', cost: 20, position: 2, minimumTitle: 'noncivil' },
|
{ labelTr: 'Strohhütte', abbr: 'straw_hut', cost: 100, position: 2, minimumTitle: 'noncivil' },
|
||||||
{ labelTr: 'Holzhaus', abbr: 'wooden_house', cost: 50, position: 3, minimumTitle: 'civil' },
|
{ labelTr: 'Holzhaus', abbr: 'wooden_house', cost: 5000, position: 3, minimumTitle: 'civil' },
|
||||||
{ labelTr: 'Hinterhofzimmer', abbr: 'backyard_room', cost: 5, position: 4, minimumTitle: 'civil' },
|
{ labelTr: 'Hinterhofzimmer', abbr: 'backyard_room', cost: 75000, position: 4, minimumTitle: 'civil' },
|
||||||
{ labelTr: 'Kleines Familienhaus', abbr: 'family_house', cost: 100, position: 5, minimumTitle: 'sir' },
|
{ labelTr: 'Kleines Familienhaus', abbr: 'family_house', cost: 273000, position: 5, minimumTitle: 'sir' },
|
||||||
{ labelTr: 'Stadthaus', abbr: 'townhouse', cost: 200, position: 6, minimumTitle: 'townlord' },
|
{ labelTr: 'Stadthaus', abbr: 'townhouse', cost: 719432, position: 6, minimumTitle: 'townlord' },
|
||||||
{ labelTr: 'Villa', abbr: 'villa', cost: 500, position: 7, minimumTitle: 'knight' },
|
{ labelTr: 'Villa', abbr: 'villa', cost: 3500000, position: 7, minimumTitle: 'knight' },
|
||||||
{ labelTr: 'Herrenhaus', abbr: 'mansion', cost: 1000, position: 8, minimumTitle: 'ruler' },
|
{ labelTr: 'Herrenhaus', abbr: 'mansion', cost: 18000000, position: 8, minimumTitle: 'ruler' },
|
||||||
{ labelTr: 'Schloss', abbr: 'castle', cost: 5000, position: 9, minimumTitle: 'prince-regent' },
|
{ 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>
|
<template>
|
||||||
<div class="statusbar">
|
<div class="statusbar">
|
||||||
<template v-for="item in statusItems" :key="item.key">
|
<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>
|
<span class="status-icon">{{ item.icon }}: {{ item.value }}</span>
|
||||||
</div>
|
</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>
|
</template>
|
||||||
<span v-if="statusItems.length > 0">
|
<span v-if="statusItems.length > 0">
|
||||||
<template v-for="(menuItem, key) in menu.falukant.children" :key="menuItem.id" >
|
<template v-for="(menuItem, key) in menu.falukant.children" :key="menuItem.id" >
|
||||||
@@ -23,9 +26,10 @@ export default {
|
|||||||
return {
|
return {
|
||||||
statusItems: [
|
statusItems: [
|
||||||
{ key: "age", icon: "👶", value: 0 },
|
{ key: "age", icon: "👶", value: 0 },
|
||||||
|
{ key: "relationship", icon: "💑", image: null },
|
||||||
{ key: "wealth", icon: "💰", value: 0 },
|
{ key: "wealth", icon: "💰", value: 0 },
|
||||||
{ key: "health", icon: "❤️", value: "Good" },
|
{ 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 response = await apiClient.get("/api/falukant/info");
|
||||||
const { money, character, events } = response.data;
|
const { money, character, events } = response.data;
|
||||||
const { age, health } = character;
|
const { age, health } = character;
|
||||||
|
const relationship = response.data.character.relationshipsAsCharacter1[0]?.relationshipType?.tr
|
||||||
|
|| response.data.character.relationshipsAsCharacter2[0]?.relationshipType?.tr
|
||||||
|
|| null;
|
||||||
let healthStatus = '';
|
let healthStatus = '';
|
||||||
if (health > 90) {
|
if (health > 90) {
|
||||||
healthStatus = this.$t("falukant.health.amazing");
|
healthStatus = this.$t("falukant.health.amazing");
|
||||||
@@ -70,9 +77,10 @@ export default {
|
|||||||
}
|
}
|
||||||
this.statusItems = [
|
this.statusItems = [
|
||||||
{ key: "age", icon: "👶", value: age },
|
{ 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: "wealth", icon: "💰", value: Intl.NumberFormat(navigator.language, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(money) },
|
||||||
{ key: "health", icon: "❤️", value: healthStatus },
|
{ key: "health", icon: "❤️", value: healthStatus },
|
||||||
{ key: "events", icon: "📰", value: events || null },
|
{ key: "events", icon: "📰", value: events || null, image: null },
|
||||||
];
|
];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching status:", error);
|
console.error("Error fetching status:", error);
|
||||||
@@ -112,7 +120,7 @@ export default {
|
|||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
width: calc(100% + 40px);
|
width: calc(100% + 40px);
|
||||||
gap: 2em;
|
gap: 1.2em;
|
||||||
margin: -21px -20px 1.5em -20px;
|
margin: -21px -20px 1.5em -20px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
}
|
}
|
||||||
@@ -120,6 +128,8 @@ export default {
|
|||||||
.status-item {
|
.status-item {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-icon {
|
.status-icon {
|
||||||
@@ -132,4 +142,9 @@ export default {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 4px 2px 0 0;
|
padding: 4px 2px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.relationship-icon {
|
||||||
|
max-width: 24px;
|
||||||
|
max-height: 24px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -226,7 +226,8 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
"mood": "Stimmung",
|
"mood": "Stimmung",
|
||||||
"progress": "Zuneigung"
|
"progress": "Zuneigung",
|
||||||
|
"jumpToPartyForm": "Hochzeitsfeier veranstalten (Nötig für Hochzeit und Kinder)"
|
||||||
},
|
},
|
||||||
"relationships": {
|
"relationships": {
|
||||||
"name": "Name"
|
"name": "Name"
|
||||||
@@ -238,7 +239,8 @@
|
|||||||
"actions": "Aktionen",
|
"actions": "Aktionen",
|
||||||
"none": "Keine Kinder vorhanden.",
|
"none": "Keine Kinder vorhanden.",
|
||||||
"detailButton": "Details anzeigen",
|
"detailButton": "Details anzeigen",
|
||||||
"addChild": "Kind hinzufügen"
|
"addChild": "Kind hinzufügen",
|
||||||
|
"baptism": "Taufen"
|
||||||
},
|
},
|
||||||
"lovers": {
|
"lovers": {
|
||||||
"title": "Liebhaber",
|
"title": "Liebhaber",
|
||||||
@@ -407,12 +409,192 @@
|
|||||||
"price": "Kaufpreis",
|
"price": "Kaufpreis",
|
||||||
"worth": "Restwert",
|
"worth": "Restwert",
|
||||||
"sell": "Verkaufen",
|
"sell": "Verkaufen",
|
||||||
|
"renovate": "Renovieren",
|
||||||
|
"renovateAll": "Komplett renovieren",
|
||||||
"status": {
|
"status": {
|
||||||
"roofCondition": "Dach",
|
"roofCondition": "Dach",
|
||||||
"wallCondition": "Wände",
|
"wallCondition": "Wände",
|
||||||
"floorCondition": "Böden",
|
"floorCondition": "Böden",
|
||||||
"windowCondition": "Fenster"
|
"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",
|
"politics": "Politik",
|
||||||
"education": "Bildung",
|
"education": "Bildung",
|
||||||
"health": "Gesundheit",
|
"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 MoneyHistoryView from '../views/falukant/MoneyHistoryView.vue';
|
||||||
import FamilyView from '../views/falukant/FamilyView.vue';
|
import FamilyView from '../views/falukant/FamilyView.vue';
|
||||||
import HouseView from '../views/falukant/HouseView.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 = [
|
const falukantRoutes = [
|
||||||
{
|
{
|
||||||
@@ -42,6 +49,48 @@ const falukantRoutes = [
|
|||||||
component: HouseView,
|
component: HouseView,
|
||||||
meta: { requiresAuth: true },
|
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;
|
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",
|
"falukantUpdateStatus",
|
||||||
"falukantBranchUpdate",
|
"falukantBranchUpdate",
|
||||||
];
|
];
|
||||||
|
if (this.daemonSocket) {
|
||||||
|
this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
||||||
|
}
|
||||||
events.forEach(eventName => {
|
events.forEach(eventName => {
|
||||||
if (this.socket) {
|
if (this.socket) {
|
||||||
this.socket.on(eventName, (data) => {
|
this.socket.on(eventName, (data) => {
|
||||||
this.handleEvent({ event: eventName, ...data });
|
this.handleEvent({ event: eventName, ...data });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this.daemonSocket) {
|
|
||||||
this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr v-if="relationships[0].relationshipType === 'engaged'" colspan="2">
|
||||||
|
<button @click="jumpToPartyForm">{{ $t('falukant.family.spouse.jumpToPartyForm')
|
||||||
|
}}</button>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="characteristic in relationships[0].character2.characterTrait"
|
<li v-for="characteristic in relationships[0].character2.characterTrait"
|
||||||
@@ -115,10 +119,12 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(child, index) in children" :key="index">
|
<tr v-for="(child, index) in children" :key="index">
|
||||||
<td>
|
<td v-if="child.hasName">
|
||||||
{{ $t('falukant.titles.' + child.gender + '.' + child.title) }}
|
|
||||||
{{ child.name }}
|
{{ child.name }}
|
||||||
</td>
|
</td>
|
||||||
|
<td v-else>
|
||||||
|
<button @click="jumpToChurchForm">{{ $t('falukant.family.children.baptism') }}</button>
|
||||||
|
</td>
|
||||||
<td>{{ child.age }}</td>
|
<td>{{ child.age }}</td>
|
||||||
<td>
|
<td>
|
||||||
<button @click="showChildDetails(child)">
|
<button @click="showChildDetails(child)">
|
||||||
@@ -191,6 +197,9 @@ export default {
|
|||||||
await this.loadGifts();
|
await this.loadGifts();
|
||||||
await this.loadMoodAffects();
|
await this.loadMoodAffects();
|
||||||
await this.loadCharacterAffects();
|
await this.loadCharacterAffects();
|
||||||
|
if (this.daemonSocket) {
|
||||||
|
this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async loadFamilyData() {
|
async loadFamilyData() {
|
||||||
@@ -247,10 +256,10 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await apiClient.post('/api/falukant/family/gift'
|
await apiClient.post('/api/falukant/family/gift'
|
||||||
, { giftId: this.selectedGiftId });
|
, { giftId: this.selectedGiftId });
|
||||||
this.loadFamilyData();
|
this.loadFamilyData();
|
||||||
this.$root.$refs.messageDialog.open('tr:falukant.family.sendgift.success');
|
this.$root.$refs.messageDialog.open('tr:falukant.family.sendgift.success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error.response);
|
console.log(error.response);
|
||||||
if (error.response.status === 412) {
|
if (error.response.status === 412) {
|
||||||
@@ -285,6 +294,26 @@ export default {
|
|||||||
const green = Math.round(255 * pct);
|
const green = Math.round(255 * pct);
|
||||||
return `rgb(${red}, ${green}, 0)`;
|
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>
|
</script>
|
||||||
@@ -369,15 +398,15 @@ h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #e5e7eb;
|
background-color: #e5e7eb;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-inner {
|
.progress-inner {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
transition: width 0.3s ease, background-color 0.3s ease;
|
transition: width 0.3s ease, background-color 0.3s ease;
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
<template>
|
||||||
<div class="houseView">
|
<div class="house-view">
|
||||||
<StatusBar />
|
<StatusBar />
|
||||||
<h2>{{ $t('falukant.house.title') }}</h2>
|
<h2>{{ $t('falukant.house.title') }}</h2>
|
||||||
<div class="existingHouse">
|
<div class="existing-house">
|
||||||
<div :style="houseStyle(picturePosition)" class="house"></div>
|
<div :style="houseType ? houseStyle(houseType.position, 341) : {}" class="house"></div>
|
||||||
<div class="statusreport">
|
<div class="status-panel">
|
||||||
<h3>{{ $t('falukant.house.statusreport') }}</h3>
|
<h3>{{ $t('falukant.house.statusreport') }}</h3>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@@ -15,46 +15,54 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="status, index in status">
|
<tr v-for="(value, key) in status" :key="key">
|
||||||
<td>{{ $t(`falukant.house.status.${index}`) }}</td>
|
<td>{{ $t(`falukant.house.status.${key}`) }}</td>
|
||||||
<td>{{ status }} %</td>
|
<td>{{ value }}%</td>
|
||||||
<td><button v-if="status < 100">{{ $t('falukant.house.renovate') }} ({{
|
<td>
|
||||||
$t('falukant.house.cost') }}: {{ getRenovationCost(index, status) }}</button></td>
|
<button v-if="value < 100" @click="renovate(key)">
|
||||||
|
{{ $t('falukant.house.renovate') }} ({{ getRenovationCost(key, value) }})
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $t('falukant.house.worth') }}</td>
|
<td>{{ $t('falukant.house.worth') }}</td>
|
||||||
<td>{{ getWorth(status) }}</td>
|
<td>{{ getWorth() }} {{ currency }}</td>
|
||||||
<td><button @click="sellHouse">{{ $t('falukant.house.sell') }}</button></td>
|
<td>
|
||||||
|
<button @click="renovateAll" :disabled="allRenovated">
|
||||||
|
{{ $t('falukant.house.renovateAll') }} ({{ getAllRenovationCost() }})
|
||||||
|
</button>
|
||||||
|
<button @click="sellHouse">
|
||||||
|
{{ $t('falukant.house.sell') }}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="buyablehouses">
|
|
||||||
|
<div class="buyable-houses">
|
||||||
<h3>{{ $t('falukant.house.buyablehouses') }}</h3>
|
<h3>{{ $t('falukant.house.buyablehouses') }}</h3>
|
||||||
<div style="overflow:auto">
|
<div class="houses-list">
|
||||||
<div style="display: flex; flex-direction: row" v-for="house in buyableHouses">
|
<div v-for="house in buyableHouses" :key="house.id" class="house-item">
|
||||||
<div style="width:100px; height:100px; display: hidden;">
|
<div :style="house.houseType ? houseStyle(house.houseType.position, 114) : {}" class="house-preview"></div>
|
||||||
<div :style="houseStyle(house.houseType.position)" class="housePreview buyableHouseInfo"></div>
|
<div class="house-info">
|
||||||
</div>
|
<h4>{{ $t(`falukant.house.type.${house.houseType.labelTr}`) }}</h4>
|
||||||
<div class="buyableHouseInfo">
|
|
||||||
<h4 style="display: inline;">{{ $t('falukant.house.statusreport') }}</h4>
|
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<template v-for="value, key in house">
|
<tr v-for="(val, prop) in house" :key="prop"
|
||||||
<tr v-if="key != 'houseType' && key != 'id'">
|
v-if="['roofCondition','wallCondition','floorCondition','windowCondition'].includes(prop)">
|
||||||
<td>{{ $t(`falukant.house.status.${key}`) }}</td>
|
<td>{{ $t(`falukant.house.status.${prop}`) }}</td>
|
||||||
<td>{{ value }} %</td>
|
<td>{{ val }}%</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
<div>
|
||||||
<div>
|
{{ $t('falukant.house.price') }}: {{ buyCost(house) }}
|
||||||
{{ $t('falukant.house.price') }}: {{ buyCost(house) }}
|
</div>
|
||||||
</div>
|
<button @click="buyHouse(house.id)">
|
||||||
<div>
|
{{ $t('falukant.house.buy') }}
|
||||||
<button @click="buyHouse(house.id)">{{ $t('falukant.house.buy') }}</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,195 +73,209 @@
|
|||||||
<script>
|
<script>
|
||||||
import StatusBar from '@/components/falukant/StatusBar.vue';
|
import StatusBar from '@/components/falukant/StatusBar.vue';
|
||||||
import apiClient from '@/utils/axios.js';
|
import apiClient from '@/utils/axios.js';
|
||||||
import { mapState } from "vuex";
|
import { mapState } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HouseView',
|
name: 'HouseView',
|
||||||
components: {
|
components: { StatusBar },
|
||||||
StatusBar
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
houseTypes: [],
|
userHouse: null,
|
||||||
userHouse: {},
|
|
||||||
houseType: {},
|
houseType: {},
|
||||||
status: {},
|
status: {},
|
||||||
buyableHouses: [],
|
buyableHouses: [],
|
||||||
picturePosition: 0,
|
currency: '€'
|
||||||
}
|
};
|
||||||
},
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['socket', 'daemonSocket']),
|
...mapState(['socket', 'daemonSocket']),
|
||||||
getHouseStyle() {
|
allRenovated() {
|
||||||
if (!this.userHouse || this.userHouse.position === undefined || this.userHouse.position === null) {
|
return Object.values(this.status).every(v => v >= 100);
|
||||||
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;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
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() {
|
async mounted() {
|
||||||
this.loadHouseTypes();
|
await this.loadData();
|
||||||
await this.getHouseData();
|
if (this.socket) this.socket.on('falukantHouseUpdate', this.loadData);
|
||||||
if (this.socket) {
|
if (this.daemonSocket) this.daemonSocket.addEventListener('message', this.handleDaemonMessage);
|
||||||
this.socket.on("falukantHouseUpdate", this.getHouseData);
|
|
||||||
}
|
|
||||||
if (this.daemonSocket) {
|
|
||||||
this.daemonSocket.addEventListener("message", this.handleDaemonSocketMessage);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
if (this.socket) {
|
if (this.socket) this.socket.off('falukantHouseUpdate', this.loadData);
|
||||||
this.socket.off("falukantHouseUpdate", this.fetchStatus);
|
if (this.daemonSocket) this.daemonSocket.removeEventListener('message', this.handleDaemonMessage);
|
||||||
}
|
|
||||||
if (this.daemonSocket) {
|
|
||||||
this.daemonSocket.removeEventListener("message", this.handleDaemonSocketMessage);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
<style scoped>
|
||||||
|
.house-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
|
margin: 0 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.existingHouse {
|
.existing-house {
|
||||||
display: block;
|
display: flex;
|
||||||
width: auto;
|
gap: 20px;
|
||||||
height: 255px;
|
|
||||||
}
|
|
||||||
|
|
||||||
Element {
|
|
||||||
background-position: 71px 54px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.house {
|
.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;
|
width: 341px;
|
||||||
height: 341px;
|
height: 341px;
|
||||||
transform-origin: top left;
|
background-repeat: no-repeat;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.houseView {
|
.status-panel {
|
||||||
display: flex;
|
flex: 1;
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.buyablehouses {
|
.buyable-houses {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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>
|
</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>
|
||||||