Start implementation of branches, new form element tabledropdown, model improvements

This commit is contained in:
Torsten Schulz
2024-12-06 23:35:28 +01:00
parent 8c15fb7f2b
commit 1bb2bd49d5
57 changed files with 2176 additions and 170 deletions

View File

@@ -1,13 +1,69 @@
import * as falukantService from '../services/falukantService.js';
import FalukantService from '../services/falukantService.js';
class FalukantController {
constructor() {
this.exampleMethod = this.exampleMethod.bind(this);
this.getUser = this.getUser.bind(this);
this.createUser = this.createUser.bind(this);
this.randomFirstName = this.randomFirstName.bind(this);
this.randomLastName = this.randomLastName.bind(this);
this.getInfo = this.getInfo.bind(this);
}
async exampleMethod(req, res) {
async getUser(req, res) {
try {
const result = await falukantService.exampleMethod();
const { userid: hashedUserId } = req.headers;
const result = await FalukantService.getUser(hashedUserId);
res.status(200).json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
async createUser(req, res) {
try {
const { userid: hashedUserId } = req.headers;
const { gender, firstname: firstName, lastname: lastName } = req.body;
console.log(req.body);
const result = await FalukantService.createUser(hashedUserId, gender, firstName, lastName);
res.status(201).json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
async randomFirstName(req, res) {
try {
const { gender } = req.params;
const result = await FalukantService.randomFirstName(gender);
res.status(200).json({ name: result });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
async randomLastName(req, res) {
try {
const result = await FalukantService.randomLastName();
res.status(200).json({ name: result });
} catch (error) {
res.status(500).json({ error: error.message });
}
}
async getInfo(req, res) {
try {
const { userid: hashedUserId } = req.headers;
const result = await FalukantService.getInfo(hashedUserId);
res.status(200).json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
async getBranches(req, res) {
try {
const { userid: hashedUserId } = req.headers;
const result = await FalukantService.getBranches(hashedUserId);
res.status(200).json(result);
} catch (error) {
res.status(500).json({ error: error.message });

View File

@@ -87,7 +87,7 @@ const menuStructure = {
},
towns: {
visible: ["hasfalukantaccount"],
path: "/falukant/towns"
path: "/falukant/branch"
},
directors: {
visible: ["hasfalukantaccount"],
@@ -117,6 +117,10 @@ const menuStructure = {
visible: ["hasfalukantaccount"],
path: "/falukant/education"
},
health: {
visible: ["hasfalukantaccount"],
path: "/falukant/health"
},
bank: {
visible: ["hasfalukantaccount"],
path: "/falukant/bank"
@@ -238,7 +242,6 @@ class NavigationController {
}
async filterMenu(menu, rights, age, userId) {
console.log(userId);
const filteredMenu = {};
const hasFalukantAccount = await this.hasFalukantAccount(userId);
for (const [key, value] of Object.entries(menu)) {

View File

@@ -31,6 +31,18 @@ import Friendship from './community/friendship.js';
import FalukantUser from './falukant/data/user.js';
import RegionType from './falukant/type/region.js';
import RegionData from './falukant/data/region.js';
import FalukantCharacter from './falukant/data/character.js';
import FalukantPredefineFirstname from './falukant/predefine/firstname.js';
import FalukantPredefineLastname from './falukant/predefine/lastname.js';
import FalukantStock from './falukant/data/stock.js';
import FalukantStockType from './falukant/type/stock.js';
import Knowledge from './falukant/data/product_knowledge.js';
import ProductType from './falukant/type/product.js';
import TitleOfNobility from './falukant/type/title_of_nobility.js';
import TitleRequirement from './falukant/type/title_requirement.js';
import Branch from './falukant/data/branch.js';
import BranchType from './falukant/type/branch.js';
export default function setupAssociations() {
// UserParam related associations
@@ -181,4 +193,46 @@ export default function setupAssociations() {
FalukantUser.belongsTo(RegionData, { foreignKey: 'mainBranchRegionId', as: 'mainBranchRegion' });
RegionData.hasMany(FalukantUser, { foreignKey: 'mainBranchRegionId', as: 'users' });
FalukantCharacter.belongsTo(FalukantUser, { foreignKey: 'userId', as: 'user' });
FalukantUser.hasOne(FalukantCharacter, { foreignKey: 'userId', as: 'character' });
FalukantCharacter.belongsTo(FalukantPredefineFirstname, { foreignKey: 'firstName', as: 'definedFirstName' });
FalukantPredefineFirstname.hasMany(FalukantCharacter, { foreignKey: 'firstName', as: 'charactersWithFirstName' });
FalukantCharacter.belongsTo(FalukantPredefineLastname, { foreignKey: 'lastName', as: 'definedLastName' });
FalukantPredefineLastname.hasMany(FalukantCharacter, { foreignKey: 'lastName', as: 'charactersWithLastName' });
FalukantCharacter.belongsTo(TitleOfNobility, { foreignKey: 'titleOfNobility', as: 'nobleTitle' });
TitleOfNobility.hasMany(FalukantCharacter, { foreignKey: 'titleOfNobility', as: 'charactersWithNobleTitle' });
FalukantCharacter.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' });
RegionData.hasMany(FalukantCharacter, { foreignKey: 'regionId', as: 'charactersInRegion' });
FalukantStock.belongsTo(FalukantStockType, { foreignKey: 'stockTypeId', as: 'stockType' });
FalukantStockType.hasMany(FalukantStock, { foreignKey: 'stockTypeId', as: 'stocks' });
FalukantStock.belongsTo(FalukantUser, { foreignKey: 'userId', as: 'user' });
FalukantUser.hasMany(FalukantStock, { foreignKey: 'userId', as: 'stocks' });
FalukantStock.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' });
RegionData.hasMany(FalukantStock, { foreignKey: 'regionId', as: 'stocksInRegion' });
Knowledge.belongsTo(ProductType, { foreignKey: 'productTypeId', as: 'productType' });
ProductType.hasMany(Knowledge, { foreignKey: 'productTypeId', as: 'knowledges' });
Knowledge.belongsTo(FalukantCharacter, { foreignKey: 'characterId', as: 'character' });
FalukantCharacter.hasMany(Knowledge, { foreignKey: 'characterId', as: 'knowledges' });
TitleRequirement.belongsTo(TitleOfNobility, { foreignKey: 'titleId', as: 'title' });
TitleOfNobility.hasMany(TitleRequirement, { foreignKey: 'titleId', as: 'requirements' });
Branch.belongsTo(RegionData, { foreignKey: 'regionId', as: 'region' });
RegionData.hasMany(Branch, { foreignKey: 'regionId', as: 'branches' });
Branch.belongsTo(FalukantUser, { foreignKey: 'falukantUserId', as: 'user' });
FalukantUser.hasMany(Branch, { foreignKey: 'falukantUserId', as: 'branches' });
Branch.belongsTo(BranchType, { foreignKey: 'branchTypeId', as: 'branchType' });
BranchType.hasMany(Branch, { foreignKey: 'branchTypeId', as: 'branches' });
}

View File

@@ -0,0 +1,34 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class Branch extends Model { }
Branch.init({
branchTypeId: {
type: DataTypes.INTEGER,
allowNull: false,
},
regionId: {
type: DataTypes.INTEGER,
allowNull: false,
},
falukantUserId: {
type: DataTypes.INTEGER,
allowNull: false,
},
}, {
sequelize,
modelName: 'BranchType',
tableName: 'branch',
schema: 'falukant_data',
timestamps: false,
underscored: true,
indexes: [
{
unique: true,
fields: ['region_id', 'falukant_user_id']
}
],
});
export default Branch;

View File

@@ -0,0 +1,49 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class FalukantCharacter extends Model {};
FalukantCharacter.init({
userId: {
type: DataTypes.INTEGER,
allowNull: true,
},
regionId: {
type: DataTypes.INTEGER,
allowNull: false,
},
firstName: {
type: DataTypes.INTEGER,
allowNull: false,
},
lastName: {
type: DataTypes.INTEGER,
allowNull: false,
},
birthdate: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
gender: {
type: DataTypes.STRING
},
health: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 100
},
titleOfNobility: {
type: DataTypes.INTEGER,
allowNull: false,
}
}, {
sequelize,
modelName: 'FalukantCharacter',
tableName: 'character',
schema: 'falukant_data',
timestamps: true,
underscored: true,
});
export default FalukantCharacter;

View File

@@ -0,0 +1,34 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class Knowledge extends Model { }
Knowledge.init({
productId: {
type: DataTypes.INTEGER,
allowNull: false,
},
characterId: {
type: DataTypes.INTEGER,
allowNull: false,
},
knowledge: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
}
}, {
sequelize,
modelName: 'Knowledge',
tableName: 'knowledge',
schema: 'falukant_data',
timestamps: false,
underscored: true,
hooks: {
beforeCreate: (knowledge) => {
knowledge.knowledge = Math.floor(Math.random() * 61) + 20;
}
}
});
export default Knowledge;

View File

@@ -0,0 +1,32 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class FalukantStock extends Model { }
FalukantStock.init({
userId: {
type: DataTypes.INTEGER,
allowNull: false,
},
regionId: {
type: DataTypes.INTEGER,
allowNull: false,
},
stockTypeId: {
type: DataTypes.INTEGER,
allowNull: false,
},
quantity: {
type: DataTypes.INTEGER,
allowNull: false,
},
}, {
sequelize,
modelName: 'StockType',
tableName: 'stock',
schema: 'falukant_data',
timestamps: false,
underscored: true,
});
export default FalukantStock;

View File

@@ -14,7 +14,8 @@ FalukantUser.init({
schema: 'community'
},
key: 'id'
}
},
unique: true,
},
money: {
type: DataTypes.DECIMAL(10, 2),

View File

@@ -0,0 +1,26 @@
import { sequelize } from '../../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const FalukantPredefineFirstname = sequelize.define('firstname', {
name: {
type: DataTypes.STRING,
allowNull: false
},
gender: {
type: DataTypes.STRING,
allowNull: false
}
}, {
tableName: 'firstname',
schema: 'falukant_predefine',
underscored: true,
timestamps: false,
indexes: [
{
unique: true,
fields: ['name', 'gender']
}
],
});
export default FalukantPredefineFirstname;

View File

@@ -0,0 +1,23 @@
import { sequelize } from '../../../utils/sequelize.js';
import { DataTypes } from 'sequelize';
const FalukantPredefineLastname = sequelize.define('lastname', {
name: {
type: DataTypes.STRING,
length: 1,
allowNull: false
},
}, {
tableName: 'lastname',
schema: 'falukant_predefine',
underscored: true,
timestamps: false,
indexes: [
{
unique: true,
fields: ['name']
}
],
});
export default FalukantPredefineLastname;

View File

@@ -0,0 +1,30 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class BranchType extends Model { }
BranchType.init({
labelTr: {
type: DataTypes.STRING,
allowNull: false,
},
baseCost: {
type: DataTypes.INTEGER,
allowNull: false,
}
}, {
sequelize,
modelName: 'BranchType',
tableName: 'branch',
schema: 'falukant_type',
timestamps: false,
underscored: true,
indexes: [
{
unique: true,
fields: ['label_tr']
}
],
});
export default BranchType;

View File

@@ -0,0 +1,38 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class ProductType extends Model { }
ProductType.init({
labelTr: {
type: DataTypes.STRING,
allowNull: false,
},
category: {
type: DataTypes.INTEGER,
allowNull: false,
},
productionTime: {
type: DataTypes.INTEGER,
allowNull: false,
},
sellCost: {
type: DataTypes.INTEGER,
allowNull: false,
}
}, {
sequelize,
modelName: 'ProductType',
tableName: 'product',
schema: 'falukant_type',
timestamps: false,
underscored: true,
indexes: [
{
unique: true,
fields: ['label_tr']
}
],
});
export default ProductType;

View File

@@ -0,0 +1,24 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class FalukantStockType extends Model { }
FalukantStockType.init({
labelTr: {
type: DataTypes.STRING,
allowNull: false,
},
cost: {
type: DataTypes.INTEGER,
allowNull: false,
}
}, {
sequelize,
modelName: 'StockType',
tableName: 'stock',
schema: 'falukant_type',
timestamps: false,
underscored: true,
});
export default FalukantStockType;

View File

@@ -0,0 +1,20 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class TitleOfNobility extends Model { }
TitleOfNobility.init({
labelTr: {
type: DataTypes.STRING,
allowNull: false,
},
}, {
sequelize,
modelName: 'Title',
tableName: 'title',
schema: 'falukant_type',
timestamps: false,
underscored: true,
});
export default TitleOfNobility;

View File

@@ -0,0 +1,30 @@
import { Model, DataTypes } from 'sequelize';
import { sequelize } from '../../../utils/sequelize.js';
class TitleRequirement extends Model { }
TitleRequirement.init({
titleId: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
requirementType: {
type: DataTypes.STRING,
allowNull: false,
},
requirementValue: {
type: DataTypes.DECIMAL(14, 2),
allowNull: false,
},
}, {
sequelize,
modelName: 'TitleRequirement',
tableName: 'title_requirement',
schema: 'falukant_type',
timestamps: false,
underscored: true,
});
export default TitleRequirement;

View File

@@ -35,6 +35,17 @@ import Friendship from './community/friendship.js';
import FalukantUser from './falukant/data/user.js';
import RegionType from './falukant/type/region.js';
import RegionData from './falukant/data/region.js';
import FalukantPredefineFirstname from './falukant/predefine/firstname.js';
import FalukantPredefineLastname from './falukant/predefine/lastname.js';
import FalukantCharacter from './falukant/data/character.js';
import FalukantStock from './falukant/data/stock.js';
import FalukantStockType from './falukant/type/stock.js';
import ProductType from './falukant/type/product.js';
import Knowledge from './falukant/data/product_knowledge.js';
import TitleRequirement from './falukant/type/title_requirement.js';
import TitleOfNobility from './falukant/type/title_of_nobility.js';
import BranchType from './falukant/type/branch.js';
import Branch from './falukant/data/branch.js';
const models = {
SettingsType,
@@ -74,6 +85,17 @@ const models = {
RegionType,
RegionData,
FalukantUser,
FalukantPredefineFirstname,
FalukantPredefineLastname,
FalukantCharacter,
FalukantStock,
FalukantStockType,
ProductType,
Knowledge,
TitleOfNobility,
TitleRequirement,
BranchType,
Branch,
};
export default models;

View File

@@ -2,7 +2,7 @@ import { sequelize } from '../utils/sequelize.js';
export async function createTriggers() {
const createTriggerFunction = `
CREATE OR REPLACE FUNCTION create_user_param_visibility_trigger()
CREATE OR REPLACE FUNCTION community.create_user_param_visibility_trigger()
RETURNS TRIGGER AS $$
BEGIN
-- Check if UserParamVisibility already exists for this UserParam
@@ -32,7 +32,7 @@ export async function createTriggers() {
AFTER INSERT ON community.user_param
FOR EACH ROW
WHEN (NEW.id IS NOT NULL)
EXECUTE FUNCTION create_user_param_visibility_trigger();
EXECUTE FUNCTION community.create_user_param_visibility_trigger();
`;
const createUpdateTrigger = `
@@ -40,11 +40,11 @@ export async function createTriggers() {
AFTER UPDATE ON community.user_param
FOR EACH ROW
WHEN (NEW.id IS NOT NULL)
EXECUTE FUNCTION create_user_param_visibility_trigger();
EXECUTE FUNCTION community.create_user_param_visibility_trigger();
`;
const createDiaryHistoryTriggerFunction = `
CREATE OR REPLACE FUNCTION insert_diary_history()
CREATE OR REPLACE FUNCTION community.insert_diary_history()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO community.diary_history (diary_id, user_id, old_text, old_created_at, old_updated_at)
@@ -65,11 +65,11 @@ export async function createTriggers() {
BEFORE UPDATE ON community.diary
FOR EACH ROW
WHEN (OLD.id IS NOT NULL)
EXECUTE FUNCTION insert_diary_history();
EXECUTE FUNCTION community.insert_diary_history();
`;
const createTitleHistoryTriggerFunction = `
CREATE OR REPLACE FUNCTION insert_title_history()
CREATE OR REPLACE FUNCTION forum.insert_title_history()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO forum.title_history (title_id, old_title, changed_by, old_updated_at)
@@ -84,7 +84,44 @@ export async function createTriggers() {
BEFORE UPDATE ON forum.title
FOR EACH ROW
WHEN (OLD.id IS NOT NULL)
EXECUTE FUNCTION insert_title_history();
EXECUTE FUNCTION forum.insert_title_history();
`;
const createCharacterCreationTriggerMethod = `
CREATE OR REPLACE FUNCTION falukant_data.create_character_creation_trigger()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO falukant_data.knowledge (product_id, character_id)
SELECT id, NEW.id FROM falukant_type.product;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
`;
const createCharacterCreationTrigger = `
CREATE OR REPLACE TRIGGER character_creation_trigger
AFTER INSERT ON falukant_data.character
FOR EACH ROW
WHEN (NEW.id IS NOT NULL)
EXECUTE FUNCTION falukant_data.create_character_creation_trigger();
`;
const createKnowledgeTriggerMethod = `
CREATE OR REPLACE FUNCTION falukant_data.create_knowledge_trigger()
RETURNS TRIGGER AS $$
BEGIN
NEW.knowledge = random() * 61 + 20;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
`;
const createKnowledgeTrigger = `
CREATE OR REPLACE TRIGGER knowledge_trigger
BEFORE INSERT ON falukant_data.knowledge
FOR EACH ROW
WHEN (NEW.id IS NOT NULL)
EXECUTE FUNCTION falukant_data.create_knowledge_trigger();
`;
try {
@@ -95,10 +132,14 @@ export async function createTriggers() {
await sequelize.query(createDiaryHistoryTrigger);
await sequelize.query(createTitleHistoryTriggerFunction);
await sequelize.query(createTitleHistoryTrigger);
await sequelize.query(createCharacterCreationTriggerMethod);
await sequelize.query(createCharacterCreationTrigger);
await sequelize.query(createKnowledgeTriggerMethod);
await sequelize.query(createKnowledgeTrigger);
console.log('Triggers created successfully');
} catch (error) {
console.error('Error creating triggers:', error);
}
}

View File

@@ -13,6 +13,7 @@
"bcrypt": "^5.1.1",
"connect-redis": "^7.1.1",
"cors": "^2.8.5",
"date-fns": "^4.1.0",
"dompurify": "^3.1.7",
"dotenv": "^16.4.5",
"express": "^4.19.2",
@@ -1231,9 +1232,9 @@
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"dependencies": {
"path-key": "^3.1.0",
@@ -1311,6 +1312,15 @@
"node": ">=18"
}
},
"node_modules/date-fns": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/debug": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",

View File

@@ -15,6 +15,7 @@
"bcrypt": "^5.1.1",
"connect-redis": "^7.1.1",
"cors": "^2.8.5",
"date-fns": "^4.1.0",
"dompurify": "^3.1.7",
"dotenv": "^16.4.5",
"express": "^4.19.2",

View File

@@ -4,6 +4,10 @@ import FalukantController from '../controllers/falukantController.js';
const router = express.Router();
const falukantController = new FalukantController();
router.get('/example', falukantController.exampleMethod);
router.get('/user', falukantController.getUser);
router.post('/user', falukantController.createUser);
router.get('/name/randomfirstname/:gender', falukantController.randomFirstName);
router.get('/name/randomlastname', falukantController.randomLastName);
router.get('/info', falukantController.getInfo);
router.get('/branches', falukantController.getBranches);
export default router;

View File

@@ -1,7 +1,284 @@
class FalukantService {
async exampleMethod() {
// Logik für die Methode
import BaseService from './BaseService.js';
import FalukantPredefineFirstname from '../models/falukant/predefine/firstname.js';
import FalukantPredefineLastname from '../models/falukant/predefine/lastname.js';
import FalukantUser from '../models/falukant/data/user.js';
import User from '../models/community/user.js';
import FalukantCharacter from '../models/falukant/data/character.js';
import RegionData from '../models/falukant/data/region.js';
import RegionType from '../models/falukant/type/region.js';
import { Sequelize } from 'sequelize';
import FalukantStock from '../models/falukant/data/stock.js';
import FalukantStockType from '../models/falukant/type/stock.js';
import { notifyUser } from '../utils/socket.js';
import { differenceInDays } from 'date-fns';
import TitleOfNobility from '../models/falukant/type/title_of_nobility.js';
import Branch from '../models/falukant/data/branch.js';
import BranchType from '../models/falukant/type/branch.js';
class FalukantService extends BaseService {
async getFalukantUserByHashedId(hashedId) {
const falukantUser = await FalukantUser.findOne({
include: [{
model: User,
as: 'user',
attributes: ['username', 'hashedId'],
where: {
hashedId: hashedId
},
}
],
});
return falukantUser;
}
async getUser(hashedUserId) {
const falukantUser = await FalukantUser.findOne({
include: [{
model: User,
as: 'user',
attributes: ['username', 'hashedId'],
where: {
hashedId: hashedUserId
},
},
{
model: FalukantCharacter,
as: 'character',
include: [
{
model: FalukantPredefineFirstname,
as: 'definedFirstName',
attributes: ['name']
},
{
model: FalukantPredefineLastname,
as: 'definedLastName',
attributes: ['name']
},
{
model: TitleOfNobility,
as: 'nobleTitle',
attributes: ['labelTr']
}
],
attributes: ['birthdate', 'gender'],
},
{
model: RegionData,
as: 'mainBranchRegion',
include: [
{
model: RegionType,
as: 'regionType',
}
],
attributes: ['name'],
},
{
model: Branch,
as: 'branches',
include: [
{
model: BranchType,
as: 'branchType',
attributes: ['labelTr']
},
{
model: RegionData,
as: 'region',
include: [
{
model: RegionType,
as: 'regionType',
}
],
attributes: ['name'],
}
]
}
],
attributes: ['money', 'creditAmount', 'todayCreditTaken'],
});
if (!falukantUser) {
throw new Error('User not found');
}
const character = falukantUser.character;
if (character && character.birthdate) {
const birthdate = new Date(character.birthdate);
birthdate.setHours(0);
birthdate.setMinutes(0);
const currentDate = new Date();
currentDate.setHours(0);
currentDate.setMinutes(0);
const ageInDays = differenceInDays(currentDate, birthdate);
character.setDataValue('age', ageInDays);
}
return falukantUser;
}
async randomFirstName(gender) {
const names = await FalukantPredefineFirstname.findAll({ where: { gender: gender } });
return names[Math.floor(Math.random() * names.length)].name;
}
async randomLastName() {
const names = await FalukantPredefineLastname.findAll();
return names[Math.floor(Math.random() * names.length)].name;
}
async createUser(hashedUserId, gender, firstName, lastName) {
try {
const user = await this.getUserByHashedId(hashedUserId);
const userExistsCheck = await FalukantUser.findOne({ where: { userId: user.id } });
if (userExistsCheck) {
throw new Error('User already exists in Falukant.');
}
let firstNameObject = await FalukantPredefineFirstname.findOne({ where: { name: firstName } });
let lastNameObject = await FalukantPredefineLastname.findOne({ where: { name: lastName } });
if (!firstNameObject) {
firstNameObject = await FalukantPredefineFirstname.create({ name: firstName, gender: gender });
}
if (!lastNameObject) {
lastNameObject = await FalukantPredefineLastname.create({ name: lastName });
}
const randomRegion = await RegionData.findOne({
order: Sequelize.fn('RANDOM'),
limit: 1,
include: [
{
model: RegionType,
as: 'regionType',
where: {
labelTr: 'city'
}
}
]
});
if (!randomRegion) {
throw new Error('No region found with the label "city".');
}
const titleOfNobility = await TitleOfNobility.findOne({ where: { labelTr: 'noncivil' } });
if (!titleOfNobility) {
throw new Error('No title of nobility found with the label "noncivil".');
}
const falukantUser = await FalukantUser.create({
userId: user.id,
money: 50.00,
creditAmount: 0.00,
todayCreditTaken: 0.00,
creditInterestRate: 0.00,
mainBranchRegionId: randomRegion.id,
});
const fourteenDaysAgo = new Date();
fourteenDaysAgo.setDate(fourteenDaysAgo.getDate() - 14);
const character = await FalukantCharacter.create({
userId: falukantUser.id,
regionId: randomRegion.id,
firstName: firstNameObject.id,
lastName: lastNameObject.id,
gender: gender,
birthdate: fourteenDaysAgo,
titleOfNobility: titleOfNobility.id,
});
await FalukantStock.create({
userId: falukantUser.id,
regionId: randomRegion.id,
stockTypeId: (await FalukantStockType.findOne({ where: [{ label_tr: 'wood' }] })).id,
quantity: 10,
});
falukantUser['character'] = character;
const branchType = await BranchType.findOne({ where: { labelTr: 'fullstack' } });
await Branch.create({
userId: falukantUser.id,
regionId: randomRegion.id,
branchTypeId: branchType.id,
})
notifyUser(user.hashedId, 'reloadmenu', {});
return falukantUser;
} catch (error) {
console.error('Error creating character');
console.log(error);
}
}
async getInfo(hashedUserId) {
try {
const user = await User.findOne({ where: { hashedId: hashedUserId } });
if (!user) {
throw new Error('User not found');
}
const falukantUser = await FalukantUser.findOne({
include: [{
model: User,
as: 'user',
attributes: ['hashedId'],
where: {
hashedId: hashedUserId
},
},
{
model: FalukantCharacter,
as: 'character',
attributes: ['birthdate', 'health'],
},
],
attributes: ['money']
});
if (!falukantUser) {
throw new Error('User not found');
}
const character = falukantUser.character;
if (character && character.birthdate) {
const birthdate = new Date(character.birthdate);
birthdate.setHours(0);
birthdate.setMinutes(0);
const currentDate = new Date();
currentDate.setHours(0);
currentDate.setMinutes(0);
const ageInDays = differenceInDays(currentDate, birthdate);
character.setDataValue('age', ageInDays);
}
return falukantUser;
} catch (error) {
console.error('Error getting character info');
console.log(error);
}
}
async getBranches(hashedUserId) {
try {
const falukantUser = await this.getFalukantUserByHashedId(hashedUserId);
if (!falukantUser) {
throw new Error('User not found');
}
const branches = await Branch.findAll({
where: { falukantUserId: falukantUser.id },
include: [
{
model: BranchType,
as: 'branchType',
attributes: ['labelTr'],
},
{
model: RegionData,
as: 'region',
attributes: ['name'],
}
],
attributes: ['id', 'regionId'],
order: [['branchTypeId', 'ASC']],
});
const enrichedBranches = branches.map(branch => ({
...branch.toJSON(),
isMainBranch: falukantUser.mainBranchRegionId === branch.regionId,
}));
return enrichedBranches;
} catch (error) {
console.error('Error in getBranches:', error);
throw new Error('Failed to retrieve branches');
}
}
}
export default new FalukantService();

View File

@@ -0,0 +1,341 @@
import FalukantPredefineFirstname from "../../models/falukant/predefine/firstname.js";
import FalukantPredefineLastname from "../../models/falukant/predefine/lastname.js";
import BranchType from "../../models/falukant/type/branch.js";
import ProductType from "../../models/falukant/type/product.js";
import FalukantStockType from "../../models/falukant/type/stock.js";
import TitleOfNobility from "../../models/falukant/type/title_of_nobility.js";
import TitleRequirement from "../../models/falukant/type/title_requirement.js";
export const initializeFalukantPredefines = async () => {
await initializeFalukantFirstnames();
await initializeFalukantLastnames();
await initializeFalukantStockTypes();
await initializeFalukantProducts();
await initializeFalukantTitles();
await initializeFalukantTitleRequirements();
await initializeFalukantBranchTypes();
}
const initializeFalukantFirstnames = async () => {
await FalukantPredefineFirstname.bulkCreate([
{ name: "Alexander", gender: "male" },
{ name: "Ben", gender: "male" },
{ name: "Christian", gender: "male" },
{ name: "Daniel", gender: "male" },
{ name: "Elias", gender: "male" },
{ name: "Felix", gender: "male" },
{ name: "Gabriel", gender: "male" },
{ name: "Hans", gender: "male" },
{ name: "Ismail", gender: "male" },
{ name: "Jakob", gender: "male" },
{ name: "Kai", gender: "male" },
{ name: "Lukas", gender: "male" },
{ name: "Maximilian", gender: "male" },
{ name: "Niklas", gender: "male" },
{ name: "Oliver", gender: "male" },
{ name: "Paul", gender: "male" },
{ name: "Quentin", gender: "male" },
{ name: "Robert", gender: "male" },
{ name: "Sebastian", gender: "male" },
{ name: "Thomas", gender: "male" },
{ name: "Ulf", gender: "male" },
{ name: "Vincent", gender: "male" },
{ name: "Thorsten", gender: "male" },
{ name: "Ulrich", gender: "male" },
{ name: "Torben", gender: "male" },
{ name: "Torsten", gender: "male" },
{ name: "Uwe", gender: "male" },
{ name: "Viktor", gender: "male" },
{ name: "Wolfgang", gender: "male" },
{ name: "Xaver", gender: "male" },
{ name: "Yannik", gender: "male" },
{ name: "Zacharias", gender: "male" },
{ name: "Aaron", gender: "male" },
{ name: "Bruno", gender: "male" },
{ name: "Carl", gender: "male" },
{ name: "David", gender: "male" },
{ name: "Emil", gender: "male" },
{ name: "Fabian", gender: "male" },
{ name: "Georg", gender: "male" },
{ name: "Heinrich", gender: "male" },
{ name: "Ian", gender: "male" },
{ name: "Jonas", gender: "male" },
{ name: "Karl", gender: "male" },
{ name: "Leon", gender: "male" },
{ name: "Matthias", gender: "male" },
{ name: "Nils", gender: "male" },
{ name: "Oskar", gender: "male" },
{ name: "Peter", gender: "male" },
{ name: "Ralf", gender: "male" },
{ name: "Simon", gender: "male" },
{ name: "Tobias", gender: "male" },
{ name: "Ulrich", gender: "male" },
{ name: "Vince", gender: "male" },
{ name: "Walter", gender: "male" },
{ name: "Xeno", gender: "male" },
{ name: "Yves", gender: "male" },
{ name: "Zeno", gender: "male" },
{ name: "Anna", gender: "female" },
{ name: "Berit", gender: "female" },
{ name: "Charlotte", gender: "female" },
{ name: "Diana", gender: "female" },
{ name: "Emilia", gender: "female" },
{ name: "Fiona", gender: "female" },
{ name: "Greta", gender: "female" },
{ name: "Hanna", gender: "female" },
{ name: "Isabelle", gender: "female" },
{ name: "Johanna", gender: "female" },
{ name: "Katharina", gender: "female" },
{ name: "Lena", gender: "female" },
{ name: "Marie", gender: "female" },
{ name: "Nina", gender: "female" },
{ name: "Olivia", gender: "female" },
{ name: "Paula", gender: "female" },
{ name: "Quirina", gender: "female" },
{ name: "Rebecca", gender: "female" },
{ name: "Sophia", gender: "female" },
{ name: "Theresa", gender: "female" },
{ name: "Ulrike", gender: "female" },
{ name: "Valeria", gender: "female" },
{ name: "Wilma", gender: "female" },
{ name: "Xenia", gender: "female" },
{ name: "Yara", gender: "female" },
{ name: "Zoe", gender: "female" },
{ name: "Antonia", gender: "female" },
{ name: "Beate", gender: "female" },
{ name: "Carla", gender: "female" },
{ name: "Dorothea", gender: "female" },
{ name: "Elisabeth", gender: "female" },
{ name: "Franziska", gender: "female" },
{ name: "Gerda", gender: "female" },
{ name: "Helena", gender: "female" },
{ name: "Irene", gender: "female" },
{ name: "Julia", gender: "female" },
{ name: "Klara", gender: "female" },
{ name: "Leonie", gender: "female" },
{ name: "Marlene", gender: "female" },
{ name: "Nele", gender: "female" },
{ name: "Petra", gender: "female" },
{ name: "Renate", gender: "female" },
{ name: "Sandra", gender: "female" },
{ name: "Tanja", gender: "female" },
{ name: "Ursula", gender: "female" },
{ name: "Vanessa", gender: "female" },
{ name: "Waltraud", gender: "female" },
{ name: "Xaveria", gender: "female" },
{ name: "Yvonne", gender: "female" },
{ name: "Zora", gender: "female" },
], {
ignoreDuplicates: true,
});
}
const initializeFalukantLastnames = async () => {
await FalukantPredefineLastname.bulkCreate([
{ name: "Adler" },
{ name: "Bauer" },
{ name: "Becker" },
{ name: "Bergmann" },
{ name: "Braun" },
{ name: "Busch" },
{ name: "Dreyer" },
{ name: "Eberhardt" },
{ name: "Fischer" },
{ name: "Franke" },
{ name: "Friedrich" },
{ name: "Geiger" },
{ name: "Gärtner" },
{ name: "Hartmann" },
{ name: "Hoffmann" },
{ name: "Hofmann" },
{ name: "Horn" },
{ name: "Huber" },
{ name: "Jäger" },
{ name: "Jung" },
{ name: "Kaiser" },
{ name: "Keller" },
{ name: "Klein" },
{ name: "Koch" },
{ name: "König" },
{ name: "Krüger" },
{ name: "Lang" },
{ name: "Lehmann" },
{ name: "Ludwig" },
{ name: "Maier" },
{ name: "Meyer" },
{ name: "Müller" },
{ name: "Neumann" },
{ name: "Neff" },
{ name: "Obermeier" },
{ name: "Otto" },
{ name: "Peters" },
{ name: "Ritter" },
{ name: "Richter" },
{ name: "Rosen" },
{ name: "Schäfer" },
{ name: "Schmidt" },
{ name: "Schneider" },
{ name: "Schulz" },
{ name: "Schulze" },
{ name: "Schwarz" },
{ name: "Schuster" },
{ name: "Sommer" },
{ name: "Stein" },
{ name: "Tanner" },
{ name: "Thiel" },
{ name: "Ullmann" },
{ name: "Ullrich" },
{ name: "Vogel" },
{ name: "Voigt" },
{ name: "Wagner" },
{ name: "Walter" },
{ name: "Weber" },
{ name: "Weiß" },
{ name: "Winter" },
{ name: "Wolf" },
{ name: "Xaver" },
{ name: "Xavier" },
{ name: "Zimmer" },
{ name: "Zimmermann" },
{ name: "Albrecht" },
{ name: "Arnold" },
{ name: "Baumann" },
{ name: "Dietrich" },
{ name: "Engel" },
{ name: "Graf" },
{ name: "Kirsch" },
{ name: "Lenz" },
{ name: "Schirmer" },
{ name: "Vogt" },
{ name: "Ziegler" },
], {
ignoreDuplicates: true,
});
}
async function initializeFalukantStockTypes() {
await FalukantStockType.bulkCreate([
{ labelTr: 'wood', cost: 15 },
{ labelTr: 'stone', cost: 25 },
{ labelTr: 'iron', cost: 100 },
{ labelTr: 'field', cost: 5 },
]);
}
async function initializeFalukantProducts() {
await ProductType.bulkCreate([
{ labelTr: 'wheat', category: 1, productionTime: 2, sellCost: 7 },
{ labelTr: 'grain', category: 1, productionTime: 2, sellCost: 7 },
{ labelTr: 'carrot', category: 1, productionTime: 1, sellCost: 4 },
{ labelTr: 'fish', category: 1, productionTime: 2, sellCost: 7 },
{ labelTr: 'meat', category: 1, productionTime: 2, sellCost: 7 },
{ labelTr: 'leather', category: 1, productionTime: 2, sellCost: 7 },
{ labelTr: 'wood', category: 1, productionTime: 2, sellCost: 7 },
{ labelTr: 'stone', category: 1, productionTime: 2, sellCost: 7 },
{ labelTr: 'milk', category: 1, productionTime: 1, sellCost: 4 },
{ labelTr: 'cheese', category: 1, productionTime: 1, sellCost: 4 },
{ labelTr: 'bread', category: 1, productionTime: 1, sellCost: 4 },
{ labelTr: 'beer', category: 1, productionTime: 1, sellCost: 4 },
{ labelTr: 'iron', category: 2, productionTime: 4, sellCost: 15 },
{ labelTr: 'copper', category: 2, productionTime: 4, sellCost: 15 },
{ labelTr: 'spices', category: 2, productionTime: 8, sellCost: 30 },
{ labelTr: 'salt', category: 2, productionTime: 4, sellCost: 15 },
{ labelTr: 'sugar', category: 2, productionTime: 4, sellCost: 15 },
{ labelTr: 'vinegar', category: 2, productionTime: 4, sellCost: 15 },
{ labelTr: 'cotton', category: 2, productionTime: 4, sellCost: 15 },
{ labelTr: 'wine', category: 2, productionTime: 4, sellCost: 15 },
{ labelTr: 'gold', category: 3, productionTime: 4, sellCost: 30 },
{ labelTr: 'diamond', category: 3, productionTime: 4, sellCost: 30 },
{ labelTr: 'furniture', category: 3, productionTime: 4, sellCost: 30 },
{ labelTr: 'clothing', category: 3, productionTime: 4, sellCost: 30 },
{ labelTr: 'jewelry', category: 4, productionTime: 5, sellCost: 60 },
{ labelTr: 'painting', category: 4, productionTime: 5, sellCost: 60 },
{ labelTr: 'book', category: 4, productionTime: 5, sellCost: 60 },
{ labelTr: 'weapon', category: 4, productionTime: 5, sellCost: 60 },
{ labelTr: 'armor', category: 4, productionTime: 5, sellCost: 60 },
{ labelTr: 'shield', category: 4, productionTime: 5, sellCost: 60 },
{ labelTr: 'horse', category: 5, productionTime: 5, sellCost: 60 },
{ labelTr: 'ox', category: 5, productionTime: 5, sellCost: 60 },
], {
ignoreDuplicates: true,
});
}
async function initializeFalukantTitles() {
await TitleOfNobility.bulkCreate([
{ labelTr: "noncivil" },
{ labelTr: "civil" },
{ labelTr: "sir" },
{ labelTr: "townlord" },
{ labelTr: "by" },
{ labelTr: "landlord" },
{ labelTr: "knight" },
{ labelTr: "baron" },
{ labelTr: "count" },
{ labelTr: "palsgrave" },
{ labelTr: "margrave" },
{ labelTr: "landgrave" },
{ labelTr: "ruler" },
{ labelTr: "elector" },
{ labelTr: "imperial-prince" },
{ labelTr: "duke" },
{ labelTr: "grand-duke" },
{ labelTr: "prince-regent" },
{ labelTr: "king" },
], {
updateOnDuplicate: ['labelTr'],
});
}
async function initializeFalukantTitleRequirements() {
const titleRequirements = [
{ labelTr: "civil", requirements: [{ type: "money", value: 500 }] },
{ labelTr: "sir", requirements: [{ type: "branches", value: 2 }] },
{ labelTr: "townlord", requirements: [] },
{ labelTr: "by", requirements: [] },
{ labelTr: "landlord", requirements: [] },
{ labelTr: "knight", requirements: [] },
{ labelTr: "baron", requirements: [{ type: "branches", value: 4 }] },
{ labelTr: "count", requirements: [] },
{ labelTr: "palsgrave", requirements: [] },
{ labelTr: "margrave", requirements: [] },
{ labelTr: "landgrave", requirements: [] },
{ labelTr: "ruler", requirements: [] },
{ labelTr: "elector", requirements: [] },
{ labelTr: "imperial-prince", requirements: [] },
{ labelTr: "duke", requirements: [] },
{ labelTr: "grand-duke", requirements: [] },
{ labelTr: "prince-regent", requirements: [] },
{ labelTr: "king", requirements: [] },
];
const titles = await TitleOfNobility.findAll();
const requirementsToInsert = [];
for (let i = 0; i < titleRequirements.length; i++) {
const titleRequirement = titleRequirements[i];
const title = titles.find(t => t.labelTr === titleRequirement.labelTr);
if (!title) continue;
if (i > 1) {
const moneyRequirement = {
type: "money",
value: 5000 * Math.pow(3, i - 1),
};
titleRequirement.requirements.push(moneyRequirement);
}
for (const requirement of titleRequirement.requirements) {
requirementsToInsert.push({
titleId: title.id,
requirementType: requirement.type,
requirementValue: requirement.value,
});
}
}
await TitleRequirement.bulkCreate(requirementsToInsert, { ignoreDuplicates: true });
}
async function initializeFalukantBranchTypes() {
await BranchType.bulkCreate([
{ labelTr: 'production', baseCost: 3000 },
{ labelTr: 'store', baseCost: 2000 },
{ labelTr: 'fullstack', baseCost: 4500},
], { ignoreDuplicates: true });
}

View File

@@ -1,8 +1,10 @@
import { initializeFalukantTypes, initializeFalukantRegions } from './falukant/initializeFalukantTypes.js';
import { initializeFalukantPredefines } from './falukant/initializeFalukantPredefines.js';
const initializeFalukant = async () => {
await initializeFalukantTypes();
await initializeFalukantRegions();
await initializeFalukantPredefines();
}
export default initializeFalukant;

View File

@@ -19,6 +19,7 @@ const createSchemas = async () => {
await sequelize.query('CREATE SCHEMA IF NOT EXISTS forum');
await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_data');
await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_type');
await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_predefine');
};
const initializeDatabase = async () => {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -70,6 +70,9 @@ export default {
newValue.on('friendloginchanged', () => {
this.fetchFriends();
});
newValue.on('reloadmenu', () => {
this.loadMenu();
})
}
}
},
@@ -84,12 +87,10 @@ export default {
},
mounted() {
if (this.$store.getters.socket) {
console.log('connect sockets in navigation')
this.$store.getters.socket.on('forumschanged', (data) => {
this.fetchForums();
});
this.$store.getters.socket.on('friendloginchanged', () => {
console.log('update friends');
this.fetchFriends();
});
}

View File

@@ -0,0 +1,94 @@
<template>
<div class="statusbar">
<template v-for="item in statusItems" :key="item.key">
<div class="status-item" v-if="item.value !== null" :title="$t(`falukant.statusbar.${item.key}`)">
<span class="status-icon">{{ item.icon }}: {{ item.value }}</span>
</div>
</template>
</div>
</template>
<script>
import { mapState } from "vuex";
import apiClient from "@/utils/axios.js";
export default {
name: "StatusBar",
data() {
return {
statusItems: [
{ key: "age", icon: "👶", value: 0 },
{ key: "wealth", icon: "💰", value: 0 },
{ key: "health", icon: "❤️", value: "Good" },
{ key: "events", icon: "📰", value: null },
],
};
},
computed: {
...mapState(["socket"]),
},
async mounted() {
await this.fetchStatus();
if (this.socket) {
this.socket.on("falukantUpdateStatus", this.fetchStatus);
}
},
beforeUnmount() {
if (this.socket) {
this.socket.off("falukantUpdateStatus", this.fetchStatus);
}
},
methods: {
async fetchStatus() {
try {
const response = await apiClient.get("/api/falukant/info");
const { money, character, events } = response.data;
const { age, health } = character;
let healthStatus = '';
if (health > 90) {
healthStatus = this.$t("falukant.health.amazing");
} else if (health > 75) {
healthStatus = this.$t("falukant.health.good");
} else if (health > 50) {
healthStatus = this.$t("falukant.health.normal");
} else if (health > 25) {
healthStatus = this.$t("falukant.health.bad");
} else {
healthStatus = this.$t("falukant.health.very_bad");
}
this.statusItems = [
{ key: "age", icon: "👶", value: age },
{ key: "wealth", icon: "💰", value: money },
{ key: "health", icon: "❤️", value: healthStatus },
{ key: "events", icon: "📰", value: events || null },
];
} catch (error) {
console.error("Error fetching status:", error);
}
},
},
};
</script>
<style scoped>
.statusbar {
display: flex;
justify-content: center;
align-items: center;
background-color: #f4f4f4;
border: 1px solid #ccc;
border-radius: 4px;
width: calc(100% + 40px);
gap: 2em;
margin: -21px -20px 1.5em -20px;
}
.status-item {
text-align: center;
cursor: pointer;
}
.status-icon {
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,133 @@
<template>
<div class="dropdown-container">
<div class="dropdown-header" @click="toggleDropdown">
<table>
<tr>
<td v-for="(column, index) in columns" :key="column.field">
{{ selected ? selected[column.field] : index === 0 ? placeholder : '' }}
</td>
<td>{{ isOpen ? '▲' : '▼' }}</td>
</tr>
</table>
</div>
<div v-if="isOpen" class="dropdown-list">
<table>
<thead>
<tr>
<th v-for="column in columns" :key="column.field">{{ column.label }}</th>
</tr>
</thead>
<tbody>
<tr v-for="option in options" :key="option.id" @click="selectOption(option)"
:class="{ selected: option.id === selected?.id }">
<td v-for="column in columns" :key="column.field">{{ option[column.field] }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
export default {
name: "FormattedDropdown",
props: {
options: {
type: Array,
required: true,
},
columns: {
type: Array,
required: true,
},
modelValue: {
type: Object,
default: null,
},
placeholder: {
type: String,
default: "Select an option",
},
},
emits: ['update:modelValue'],
data() {
return {
isOpen: false,
selected: this.modelValue,
};
},
watch: {
modelValue(newVal) {
this.selected = newVal;
console.log("FormattedDropdown modelValue changed:", newVal);
},
},
methods: {
toggleDropdown() {
this.isOpen = !this.isOpen;
},
selectOption(option) {
this.selected = option;
this.$emit("update:modelValue", option);
this.isOpen = false;
},
},
};
</script>
<style scoped>
.dropdown-container {
position: relative;
display: inline-block;
}
.dropdown-header {
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
padding: 2px 3px;
background-color: #fff;
}
.dropdown-list {
position: absolute;
top: 100%;
left: 0;
background: white;
border: 1px solid #ccc;
border-radius: 4px;
z-index: 50;
width: auto;
min-width: 100%;
max-width: 90vw;
max-height: 300px;
overflow-y: auto;
white-space: nowrap;
padding: 2px 3px;
}
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
text-align: left;
padding: 4px;
white-space: nowrap;
}
tr.selected {
background-color: #f0f0f0;
font-weight: bold;
}
tr:hover {
background-color: #e0e0e0;
cursor: pointer;
}
</style>

View File

@@ -13,6 +13,7 @@ import enSettings from './locales/en/settings.json';
import enAdmin from './locales/en/admin.json';
import enSocialNetwork from './locales/en/socialnetwork.json';
import enFriends from './locales/en/friends.json';
import enFalukant from './locales/en/falukant.json';
import deGeneral from './locales/de/general.json';
import deHeader from './locales/de/header.json';
@@ -26,6 +27,7 @@ import deSettings from './locales/de/settings.json';
import deAdmin from './locales/de/admin.json';
import deSocialNetwork from './locales/de/socialnetwork.json';
import deFriends from './locales/de/friends.json';
import deFalukant from './locales/de/falukant.json';
const messages = {
en: {
@@ -41,6 +43,7 @@ const messages = {
...enAdmin,
...enSocialNetwork,
...enFriends,
...enFalukant,
},
de: {
'Ok': 'Ok',
@@ -56,6 +59,7 @@ const messages = {
...deAdmin,
...deSocialNetwork,
...deFriends,
...deFalukant,
}
};

View File

@@ -1,5 +1,130 @@
{
"falukant": {
"statusbar": {
"age": "Alter",
"wealth": "Vermögen",
"health": "Gesundheit",
"events": "Ereignisse"
},
"health": {
"amazing": "Super",
"good": "Gut",
"normal": "Normal",
"bad": "Schlecht",
"very_bad": "Sehr schlecht"
},
"create": {
"title": "Am Spiel teilnehmen",
"gender": "Geschlecht",
"male": "Mann",
"female": "Frau",
"firstname": "Vorname",
"lastname": "Nachname",
"random": "Zufällig",
"submit": "Teilnehmen"
},
"overview": {
"title": "Falukant - Übersicht",
"metadata": {
"title": "Persönliches",
"name": "Name",
"money": "Vermögen",
"age": "Alter",
"mainbranch": "Heimatstadt"
},
"productions": {
"title": "Produktionen"
},
"stock": {
"title": "Lager"
},
"branches": {
"title": "Filialen",
"level": {
"production": "Produktion",
"store": "Verkauf",
"fullstack": "Produktion mit Verkauf"
}
}
},
"titles": {
"male": {
"noncivil": "Leibeigener",
"civil": "Bürgerlich",
"sir": "Herr",
"townlord": "Stadtherr",
"by": "von",
"landlord": "Landherr",
"knight": "Ritter",
"baron": "Baron",
"count": "Graf",
"palsgrave": "Pfalzgraf",
"margrave": "Markgraf",
"landgrave": "Landgraf",
"ruler": "Fürst",
"elector": "Kurfürst",
"imperial-prince": "Reichsfürst",
"duke": "Herzog",
"grand-duke": "Großherzog",
"prince-regent": "Prinzregent",
"king": "König"
},
"female": {
"noncivil": "Leibeigene",
"civil": "Bürgerlich",
"sir": "Frau",
"townlord": "Stadtherrin",
"by": "zu",
"landlord": "Landherrin",
"knight": "Freifrau",
"baron": "Baronin",
"count": "Gräfin",
"palsgrave": "Pfalzgräfin",
"margrave": "Markgräfin",
"landgrave": "Landgräfin",
"ruler": "Fürstin",
"elector": "Kurfürstin",
"imperial-prince": "Reichsfürstin",
"duke": "Herzogin",
"grand-duke": "Großherzogin",
"prince-regent": "Prinzregentin",
"king": "Königin"
}
},
"branch": {
"title": "Filiale",
"selection": {
"title": "Niederlassungsauswahl",
"selected": "Ausgewählte Niederlassung",
"placeholder": "Noch keine Niederlassung ausgewählt"
},
"actions": {
"create": "Neue Niederlassung erstellen",
"upgrade": "Aktuelle Niederlassung aufwerten",
"createAlert": "Neue Niederlassung wird erstellt.",
"upgradeAlert": "Die Niederlassung mit der ID {branchId} wird aufgewertet."
},
"director": {
"title": "Direktor-Infos",
"info": "Informationen über den Direktor der Niederlassung."
},
"sale": {
"title": "Verkauf",
"info": "Hier können Produkte verkauft werden."
},
"production": {
"title": "Produktion",
"info": "Details zur Produktion in der Niederlassung."
},
"columns": {
"city": "Stadt",
"type": "Typ"
},
"types": {
"production": "Produktion",
"store": "Verkauf",
"fullstack": "Produktion mit Verkauf"
}
}
}
}

View File

@@ -55,7 +55,12 @@
"house": "Haus",
"darknet": "Untergrund",
"reputation": "Reputation",
"moneyhistory": "Geldfluss"
"moneyhistory": "Geldfluss",
"nobility": "Sozialstatus",
"politics": "Politik",
"education": "Bildung",
"health": "Gesundheit",
"bank": "Bank"
}
}
}

View File

@@ -0,0 +1,26 @@
import AdminInterestsView from '../views/admin/InterestsView.vue';
import AdminContactsView from '../views/admin/ContactsView.vue';
import ForumAdminView from '../dialogues/admin/ForumAdminView.vue';
const adminRoutes = [
{
path: '/admin/interests',
name: 'AdminInterests',
component: AdminInterestsView,
meta: { requiresAuth: true }
},
{
path: '/admin/contacts',
name: 'AdminContacts',
component: AdminContactsView,
meta: { requiresAuth: true }
},
{
path: '/admin/forum',
name: 'AdminForums',
component: ForumAdminView,
meta: { requiresAuth: true }
},
];
export default adminRoutes;

View File

@@ -0,0 +1,11 @@
import ActivateView from '../views/auth/ActivateView.vue';
const authRoutes = [
{
path: '/activate',
name: 'Activate page',
component: ActivateView
},
];
export default authRoutes;

View File

@@ -0,0 +1,26 @@
import BranchView from '../views/falukant/BranchView.vue';
import Createview from '../views/falukant/CreateView.vue';
import FalukantOverviewView from '../views/falukant/OverviewView.vue';
const falukantRoutes = [
{
path: '/falukant/create',
name: 'FalukantCreate',
component: Createview,
meta: { requiresAuth: true }
},
{
path: '/falukant/home',
name: 'FalukantOverview',
component: FalukantOverviewView,
meta: { requiresAuth: true }
},
{
path: '/falukant/branch/:branchId?',
name: 'BranchView',
component: BranchView,
meta: { requiresAuth: true },
},
];
export default falukantRoutes;

View File

@@ -1,134 +1,25 @@
import { createRouter, createWebHistory } from 'vue-router';
import store from '../store';
import HomeView from '../views/HomeView.vue';
import ActivateView from '../views/auth/ActivateView.vue';
import PeronalSettingsView from '../views/settings/PersonalView.vue';
import ViewSettingsView from '../views/settings/ViewView.vue';
import FlirtSettingsView from '../views/settings/FlirtView.vue';
import SexualitySettingsView from '../views/settings/SexualityView.vue';
import AccountSettingsView from '../views/settings/AccountView.vue';
import InterestsView from '../views/settings/InterestsView.vue';
import AdminInterestsView from '../views/admin/InterestsView.vue';
import AdminContactsView from '../views/admin/ContactsView.vue';
import SearchView from '../views/social/SearchView.vue';
import GalleryView from '../views/social/GalleryView.vue';
import GuestbookView from '../views/social/GuestbookView.vue';
import DiaryView from '../views/social/DiaryView.vue';
import ForumAdminView from '../dialogues/admin/ForumAdminView.vue';
import ForumView from '../views/social/ForumView.vue';
import ForumTopicView from '../views/social/ForumTopicView.vue';
import FriendsView from '../views/social/FriendsView.vue';
import authRoutes from './authRoutes';
import socialRoutes from './socialRoutes';
import settingsRoutes from './settingsRoutes';
import adminRoutes from './adminRoutes';
import falukantRoutes from './falukantRoutes';
const routes = [
{
path: '/',
path: '/',
name: 'Home',
component: HomeView
},
{
path: '/activate',
name: 'Activate page',
component: ActivateView
},
{
path: '/friends',
name: 'Friends',
component: FriendsView,
meta: { requiresAuth: true }
},
{
path: '/socialnetwork/guestbook',
name: 'Guestbook',
component: GuestbookView,
meta: { requiresAuth: true }
},
{
path: '/socialnetwork/search',
name: 'Search users',
component: SearchView,
meta: { requiresAuth: true }
},
{
path: '/socialnetwork/gallery',
name: 'Gallery',
component: GalleryView,
meta: { requiresAuth: true }
},
{
path: '/socialnetwork/forum/:id',
name: 'Forum',
component: ForumView,
meta: { requiresAuth: true }
},
{
path: '/socialnetwork/forumtopic/:id',
name: 'ForumTopic',
component: ForumTopicView,
meta: { requiresAuth: true }
},
{
path: '/socialnetwork/diary',
name: 'Diary',
component: DiaryView,
meta: { requiresAuth: true }
},
{
path: '/settings/personal',
name: 'Personal settings',
component: PeronalSettingsView,
meta: { requiresAuth: true }
},
{
path: '/settings/view',
name: 'View settings',
component: ViewSettingsView,
meta: { requiresAuth: true }
},
{
path: '/settings/sexuality',
name: 'Sexuality settings',
component: SexualitySettingsView,
meta: { requiresAuth: true }
},
{
path: '/settings/flirt',
name: 'Flirt settings',
component: FlirtSettingsView,
meta: { requiresAuth: true }
},
{
path: '/settings/account',
name: 'Account settings',
component: AccountSettingsView,
meta: { requiresAuth: true }
},
{
path: '/settings/interests',
name: 'Interests',
component: InterestsView,
meta: { requiresAuth: true }
},
{
path: '/admin/interests',
name: 'AdminInterests',
component: AdminInterestsView,
meta: { requiresAuth: true }
},
{
path: '/admin/contacts',
name: 'AdminContacts',
component: AdminContactsView,
meta: { requiresAuth: true }
},
{
path: '/admin/forum',
name: 'AdminForums',
component: ForumAdminView,
meta: { requiresAuth: true }
}
...authRoutes,
...socialRoutes,
...settingsRoutes,
...adminRoutes,
...falukantRoutes,
];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
@@ -149,4 +40,3 @@ router.beforeEach((to, from, next) => {
});
export default router;

View File

@@ -0,0 +1,47 @@
import PeronalSettingsView from '../views/settings/PersonalView.vue';
import ViewSettingsView from '../views/settings/ViewView.vue';
import FlirtSettingsView from '../views/settings/FlirtView.vue';
import SexualitySettingsView from '../views/settings/SexualityView.vue';
import AccountSettingsView from '../views/settings/AccountView.vue';
import InterestsView from '../views/settings/InterestsView.vue';
const settingsRoutes = [
{
path: '/settings/personal',
name: 'Personal settings',
component: PeronalSettingsView,
meta: { requiresAuth: true }
},
{
path: '/settings/view',
name: 'View settings',
component: ViewSettingsView,
meta: { requiresAuth: true }
},
{
path: '/settings/sexuality',
name: 'Sexuality settings',
component: SexualitySettingsView,
meta: { requiresAuth: true }
},
{
path: '/settings/flirt',
name: 'Flirt settings',
component: FlirtSettingsView,
meta: { requiresAuth: true }
},
{
path: '/settings/account',
name: 'Account settings',
component: AccountSettingsView,
meta: { requiresAuth: true }
},
{
path: '/settings/interests',
name: 'Interests',
component: InterestsView,
meta: { requiresAuth: true }
},
];
export default settingsRoutes;

View File

@@ -0,0 +1,54 @@
import FriendsView from '../views/social/FriendsView.vue';
import SearchView from '../views/social/SearchView.vue';
import GalleryView from '../views/social/GalleryView.vue';
import GuestbookView from '../views/social/GuestbookView.vue';
import DiaryView from '../views/social/DiaryView.vue';
import ForumView from '../views/social/ForumView.vue';
import ForumTopicView from '../views/social/ForumTopicView.vue';
const socialRoutes = [
{
path: '/friends',
name: 'Friends',
component: FriendsView,
meta: { requiresAuth: true }
},
{
path: '/socialnetwork/guestbook',
name: 'Guestbook',
component: GuestbookView,
meta: { requiresAuth: true }
},
{
path: '/socialnetwork/search',
name: 'Search users',
component: SearchView,
meta: { requiresAuth: true }
},
{
path: '/socialnetwork/gallery',
name: 'Gallery',
component: GalleryView,
meta: { requiresAuth: true }
},
{
path: '/socialnetwork/forum/:id',
name: 'Forum',
component: ForumView,
meta: { requiresAuth: true }
},
{
path: '/socialnetwork/forumtopic/:id',
name: 'ForumTopic',
component: ForumTopicView,
meta: { requiresAuth: true }
},
{
path: '/socialnetwork/diary',
name: 'Diary',
component: DiaryView,
meta: { requiresAuth: true }
},
];
export default socialRoutes;

View File

@@ -7,10 +7,10 @@ import { io } from 'socket.io-client';
const store = createStore({
state: {
isLoggedIn: false,
user: null,
isLoggedIn: localStorage.getItem('isLoggedIn') === 'true',
user: JSON.parse(localStorage.getItem('user')) || null,
language: navigator.language.startsWith('de') ? 'de' : 'en',
menu: [],
menu: JSON.parse(localStorage.getItem('menu')) || [],
socket: null,
menuNeedsUpdate: false,
},
@@ -32,19 +32,22 @@ const store = createStore({
localStorage.removeItem('user');
localStorage.removeItem('menu');
state.menuNeedsUpdate = false;
// await apiClient.get('/api/auth/logout');
},
setLanguage(state, language) {
state.language = language;
},
setMenu(state, menu) {
state.menu = menu;
localStorage.setItem('menu', JSON.stringify(menu));
state.menuNeedsUpdate = false;
},
setSocket(state, socket) {
state.socket = socket;
},
clearSocket(state) {
if (state.socket) {
state.socket.disconnect();
}
state.socket = null;
},
},
@@ -58,17 +61,20 @@ const store = createStore({
}
await dispatch('loadMenu');
},
logout({ commit, state }) {
if (state.socket) {
state.socket.disconnect();
commit('clearSocket');
}
logout({ commit }) {
commit('clearSocket');
commit('dologout');
router.push('/');
},
initializeSocket({ commit, state }) {
if (state.isLoggedIn && state.user) {
const socket = io(import.meta.env.VITE_API_BASE_URL);
socket.on('connect', () => {
socket.emit('setUserId', state.user.id);
});
socket.on('disconnect', (reason) => {
console.warn('WebSocket disconnected:', reason);
});
commit('setSocket', socket);
}
},
@@ -90,12 +96,16 @@ const store = createStore({
user: state => state.user,
language: state => state.language,
menu: state => state.menu,
socket: state => state.socket,
menuNeedsUpdate: state => state.menuNeedsUpdate
socket: state => state.socket,
menuNeedsUpdate: state => state.menuNeedsUpdate,
},
modules: {
dialogs,
},
});
if (store.state.isLoggedIn && store.state.user) {
store.dispatch('initializeSocket');
}
export default store;

View File

@@ -0,0 +1,130 @@
<template>
<div>
<StatusBar />
<h2>{{ $t('falukant.branch.title') }}</h2>
<!-- Branch Selection Section -->
<div class="branch-selection">
<h3>{{ $t('falukant.branch.selection.title') }}</h3>
<div>
<FormattedDropdown :options="branches" :columns="branchColumns" v-model="selectedBranch"
:placeholder="$t('falukant.branch.selection.placeholder')" />
</div>
<div>
<button @click="createBranch">{{ $t('falukant.branch.actions.create') }}</button>
<button @click="upgradeBranch" :disabled="!selectedBranch">
{{ $t('falukant.branch.actions.upgrade') }}
</button>
</div>
</div>
<!-- Director Info Section -->
<div class="director-info">
<h3>{{ $t('falukant.branch.director.title') }}</h3>
<p v-if="selectedBranch">
{{ $t('falukant.branch.director.info', { branchName: selectedBranch.cityName }) }}
</p>
<p v-else>{{ $t('falukant.branch.director.noSelection') }}</p>
</div>
<!-- Sale Section -->
<div class="sale-section">
<h3>{{ $t('falukant.branch.sale.title') }}</h3>
<p>{{ $t('falukant.branch.sale.info') }}</p>
</div>
<!-- Production Section -->
<div class="production-section">
<h3>{{ $t('falukant.branch.production.title') }}</h3>
<p>{{ $t('falukant.branch.production.info') }}</p>
</div>
</div>
</template>
<script>
import StatusBar from '@/components/falukant/StatusBar.vue';
import FormattedDropdown from '@/components/form/FormattedDropdown.vue';
import apiClient from '@/utils/axios.js';
export default {
name: "BranchView",
components: {
StatusBar,
FormattedDropdown,
},
data() {
return {
selectedBranch: null,
branches: [],
branchColumns: [
{ field: "cityName", label: this.$t('falukant.branch.columns.city') },
{ field: "type", label: this.$t('falukant.branch.columns.type') },
],
};
},
async mounted() {
await this.loadBranches();
const branchId = this.$route.params.branchId;
console.log('route params:', this.$route.params, branchId);
if (branchId) {
console.log('branch selected');
this.selectedBranch = this.branches.find(branch => branch.id === parseInt(branchId)) || null;
} else {
console.log('main branch selected');
this.selectMainBranch();
}
},
methods: {
async loadBranches() {
try {
const branchesResult = await apiClient.get('/api/falukant/branches');
this.branches = branchesResult.data.map((branch) => ({
id: branch.id,
cityName: branch.region.name,
type: this.$t(`falukant.branch.types.${branch.branchType.labelTr}`),
isMainBranch: branch.isMainBranch,
}));
// Wenn keine selectedBranch gesetzt ist, versuche die Main Branch zu wählen
if (!this.selectedBranch) {
this.selectMainBranch();
}
} catch (error) {
console.error('Error loading branches:', error);
}
},
selectMainBranch() {
const main = this.branches.find(b => b.isMainBranch) || null;
if (main !== this.selectedBranch) {
this.selectedBranch = main;
console.log("Main branch selected:", this.selectedBranch);
}
},
createBranch() {
alert(this.$t('falukant.branch.actions.createAlert'));
},
upgradeBranch() {
if (this.selectedBranch) {
alert(
this.$t('falukant.branch.actions.upgradeAlert', { branchId: this.selectedBranch.id })
);
}
},
},
};
</script>
<style scoped lang="scss">
.branch-selection,
.director-info,
.sale-section,
.production-section {
border: 1px solid #ccc;
margin: 10px 0;
border-radius: 4px;
padding: 10px;
}
button {
margin: 5px;
}
</style>

View File

@@ -0,0 +1,118 @@
<template>
<div>
<h2>{{ $t('falukant.create.title') }}</h2>
<form @submit.prevent="createFalukant">
<label>{{ $t('falukant.create.gender') }}</label>
<select v-model="falukant.gender" required @change="randomFirstName">
<option value="male">{{ $t('falukant.create.male') }}</option>
<option value="female">{{ $t('falukant.create.female') }}</option>
</select>
<div></div>
<label>{{ $t('falukant.create.firstname') }}</label>
<input type="text" v-model="falukant.firstname" required>
<button @click="randomFirstName" type="button">{{ $t('falukant.create.random') }}</button>
<label>{{ $t('falukant.create.lastname') }}</label>
<input type="text" v-model="falukant.lastname" required>
<button @click="randomLastName" type="button">{{ $t('falukant.create.random') }}</button>
<button type="submit">{{ $t('falukant.create.submit') }}</button>
</form>
<img :src="falukant.gender == 'male' ? '/images/mascot/mascot_male.png' : '/images/mascot/mascot_female.png'"
class="mascot-image" />
</div>
</template>
<script>
import { mapActions } from 'vuex';
import apiClient from '@/utils/axios.js';
export default {
name: 'FalukantCreateView',
data() {
return {
falukant: {
gender: 'male',
firstname: '',
lastname: '',
},
};
},
async mounted() {
try {
const falukantUser = await apiClient.get('/api/falukant/user');
if (falukantUser.data) {
this.$router.push({ name: 'FalukantOverview' });
return;
}
} catch (error) {
}
await this.randomFirstName();
await this.randomLastName();
},
methods: {
...mapActions(['createFalukant']),
async createFalukant() {
const newUser = await apiClient.post('/api/falukant/user', this.falukant);
console.log(newUser);
this.$router.push({ name: 'FalukantOverview' });
},
async randomFirstName() {
const randomNameResult = await apiClient.get('/api/falukant/name/randomfirstname/' + this.falukant.gender);
this.falukant.firstname = randomNameResult.data.name;
console.log(this.falukant, randomNameResult);
},
async randomLastName() {
const randomNameResult = await apiClient.get('/api/falukant/name/randomlastname');
this.falukant.lastname = randomNameResult.data.name;
console.log(this.falukant, randomNameResult);
},
},
};
</script>
<style scoped lang="scss">
form {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 10px;
row-gap: 15px;
width: fit-content;
margin: 0 auto;
border: 1px solid #ccc;
padding: 20px;
border-radius: 4px;
}
label {
text-align: right;
font-weight: bold;
margin-right: 10px;
}
select,
input {
width: auto;
}
button {
width: auto;
}
button[type="submit"] {
grid-column: 1 / -1;
justify-self: start;
width: auto;
}
.mascot-image {
display: block;
margin: 0 auto;
height: calc(100vh - 400px);
max-height: 100%;
min-height: 150px;
width: auto;
object-fit: contain;
}
</style>

View File

@@ -0,0 +1,187 @@
<template>
<div>
<StatusBar />
<h2>{{ $t('falukant.overview.title') }}</h2>
<div class="overviewcontainer">
<div>
<h3>{{ $t('falukant.overview.metadata.title') }}</h3>
<table>
<tr>
<td>{{ $t('falukant.overview.metadata.name') }}</td>
<td>{{ falukantUser?.character.definedFirstName.name }} {{
falukantUser?.character.definedLastName.name }}</td>
</tr>
<tr>
<td>{{ $t('falukant.overview.metadata.money') }}</td>
<td>{{ falukantUser?.money }}</td>
</tr>
<tr>
<td>{{ $t('falukant.overview.metadata.age') }}</td>
<td>{{ falukantUser?.character.age }}</td>
</tr>
<tr>
<td>{{ $t('falukant.overview.metadata.mainbranch') }}</td>
<td>{{ falukantUser?.mainBranchRegion.name }}</td>
</tr>
</table>
</div>
<div>
<h3>{{ $t('falukant.overview.productions.title') }}</h3>
</div>
<div>
<h3>{{ $t('falukant.overview.stock.title') }}</h3>
</div>
<div>
<h3>{{ $t('falukant.overview.branches.title') }}</h3>
<table>
<tr v-for="branch in falukantUser?.branches">
<td><span @click="openBranch(branch.id)" class="link">{{ branch.region.name }}</span></td>
<td>{{ $t(`falukant.overview.branches.level.${branch.branchType.labelTr}`) }}</td>
</tr>
</table>
</div>
</div>
<div class="imagecontainer">
<div :style="getAvatarStyle" class="avatar"></div>
</div>
</div>
</template>
<script>
import apiClient from '@/utils/axios.js';
import StatusBar from '@/components/falukant/StatusBar.vue';
const AVATAR_POSITIONS = {
male: {
width: 195,
height: 300,
positions: {
"0-1": { x: 161, y: 28 },
"2-3": { x: 802, y: 28 },
"4-6": { x: 1014, y: 28 },
"7-10": { x: 800, y: 368 },
"11-13": { x: 373, y: 368 },
"14-16": { x: 1441, y: 28 },
"17-20": { x: 1441, y: 368 },
"21-30": { x: 1014, y: 368 },
"31-45": { x: 1227, y: 368 },
"45-55": { x: 803, y: 687 },
"55+": { x: 1441, y: 687 },
},
},
female: {
width: 223,
height: 298,
positions: {
"0-1": { x: 302, y: 66 },
"2-3": { x: 792, y: 66 },
"4-6": { x: 62, y: 66 },
"7-10": { x: 1034, y: 66 },
"11-13": { x: 1278, y: 66 },
"14-16": { x: 303, y: 392 },
"17-20": { x: 1525, y: 392 },
"21-30": { x: 1278, y: 392 },
"31-45": { x: 547, y: 718 },
"45-55": { x: 1034, y: 718 },
"55+": { x: 1525, y: 718 },
},
},
};
export default {
name: 'FalukantOverviewView',
data() {
return {
falukantUser: null,
};
},
components: {
StatusBar,
},
async mounted() {
await this.fetchFalukantUser();
if (this.socket) {
this.socket.on("falukantUserUpdated", this.fetchFalukantUser);
}
},
beforeUnmount() {
if (this.socket) {
this.socket.off("falukantUserUpdated", this.fetchFalukantUser);
}
},
computed: {
getAvatarStyle() {
if (!this.falukantUser) return {};
const { gender, age } = this.falukantUser.character;
const imageUrl = `/images/falukant/avatar/${gender}.png`;
const ageGroup = this.getAgeGroup(age);
const genderData = AVATAR_POSITIONS[gender] || {};
const position = genderData.positions?.[ageGroup] || { x: 0, y: 0 };
const width = genderData.width || 100;
const height = genderData.height || 100;
return {
backgroundImage: `url(${imageUrl})`,
backgroundPosition: `-${position.x}px -${position.y}px`,
backgroundSize: "1792px 1024px",
width: `${width}px`,
height: `${height}px`,
};
},
},
methods: {
getAgeGroup(age) {
if (age <= 1) return "0-1";
if (age <= 3) return "2-3";
if (age <= 6) return "4-6";
if (age <= 10) return "7-10";
if (age <= 13) return "11-13";
if (age <= 16) return "14-16";
if (age <= 20) return "17-20";
if (age <= 30) return "21-30";
if (age <= 45) return "31-45";
if (age <= 55) return "45-55";
return "55+";
},
async fetchFalukantUser() {
const falukantUser = await apiClient.get('/api/falukant/user');
if (!falukantUser.data) {
this.$router.push({ name: 'FalukantCreate' });
return;
}
this.falukantUser = falukantUser.data;
},
openBranch(branchId) {
this.$router.push({ name: 'BranchView', params: { branchId: branchId } });
}
},
};
</script>
<style scoped lang="scss">
.overviewcontainer {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-gap: 5px;
}
.overviewcontainer>div {
border: 1px solid #ccc;
padding: 5px;
border-radius: 4px;
}
.imagecontainer {
margin-top: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.avatar {
border: 1px solid #ccc;
border-radius: 4px;
background-repeat: no-repeat;
background-size: cover;
image-rendering: crisp-edges;
}
</style>

14
package-lock.json generated
View File

@@ -9,7 +9,6 @@
"version": "3.0.0-pre-alpha.0.1",
"dependencies": {
"cors": "^2.8.5",
"mitt": "^3.0.1",
"sequelize-cli": "^6.6.2"
},
"devDependencies": {
@@ -483,11 +482,10 @@
}
},
"node_modules/cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz",
"integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==",
"dev": true,
"license": "MIT",
"dependencies": {
"nice-try": "^1.0.4",
"path-key": "^2.0.1",
@@ -1830,12 +1828,6 @@
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",