Falukant production, family and administration enhancements
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<footer>
|
||||
<div class="logo"><img src="/images/icons/logo_color.png"></div>
|
||||
<div class="logo" @click="showFalukantDaemonStatus"><img src="/images/icons/logo_color.png"></div>
|
||||
<div class="window-bar">
|
||||
<button v-for="dialog in openDialogs" :key="dialog.dialog.name" class="dialog-button"
|
||||
@click="toggleDialogMinimize(dialog.dialog.name)" :title="dialog.dialog.localTitle">
|
||||
@@ -18,14 +18,21 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'AppFooter',
|
||||
components: {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('dialogs', ['openDialogs'])
|
||||
...mapGetters('dialogs', ['openDialogs']),
|
||||
...mapState(['daemonSocket']),
|
||||
},
|
||||
mounted() {
|
||||
this.daemonSocket.addEventListener('workerStatus', () => { console.log('----'); });
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.daemonSocket.removeEventListener('workerStatus', this.handleDaemonMessage);
|
||||
},
|
||||
methods: {
|
||||
openImprintDialog() {
|
||||
@@ -39,6 +46,13 @@ export default {
|
||||
},
|
||||
toggleDialogMinimize(dialogName) {
|
||||
this.$store.dispatch('dialogs/toggleDialogMinimize', dialogName);
|
||||
},
|
||||
async showFalukantDaemonStatus() {
|
||||
this.daemonSocket.send('{"event": "getWorkerStatus"}');
|
||||
},
|
||||
handleDaemonMessage(event) {
|
||||
const status = JSON.parse(event.data);
|
||||
console.log(event);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,11 +13,20 @@
|
||||
class="submenu-icon"> </span>
|
||||
<span>{{ $t(`navigation.m-${key}.${subkey}`) }}</span>
|
||||
<span v-if="subkey === 'forum'" class="subsubmenu">▶</span>
|
||||
<span v-else-if="subitem.children" class="subsubmenu">▶</span>
|
||||
<ul v-if="subkey === 'forum' && forumList.length > 0" class="submenu2">
|
||||
<li v-for="forum in forumList" :key="forum.id" @click="openForum(forum.id, $event)">
|
||||
{{ forum.name }}
|
||||
</li>
|
||||
</ul>
|
||||
<ul v-else-if="subitem.children" class="submenu2">
|
||||
<li v-for="(subsubitem, subsubkey) in subitem.children" :key="subsubitem.text"
|
||||
@click="openPage(subsubitem.path ?? null)">
|
||||
<span v-if="subsubitem.icon" :style="`background-image:url('/images/icons/${subsubitem.icon}')`"
|
||||
class="submenu-icon"> </span>
|
||||
<span>{{ $t(`navigation.m-${key}.m-${subkey}.${subsubkey}`) }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li v-if="item.showLoggedinFriends === 1 && friendsList.length > 0" v-for="friend in friendsList" :key="friend.id">
|
||||
{{ friend.username }}
|
||||
|
||||
@@ -53,7 +53,7 @@ export default {
|
||||
watch: {
|
||||
visible(newValue) {
|
||||
if (!newValue) {
|
||||
this.minimized = false; // Reset minimized state when dialog is closed
|
||||
this.minimized = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
64
frontend/src/components/falukant/BranchSelection.vue
Normal file
64
frontend/src/components/falukant/BranchSelection.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="branch-selection">
|
||||
<h3>{{ $t('falukant.branch.selection.title') }}</h3>
|
||||
<div>
|
||||
<FormattedDropdown
|
||||
:options="branches"
|
||||
:columns="branchColumns"
|
||||
v-model="localSelectedBranch"
|
||||
:placeholder="$t('falukant.branch.selection.placeholder')"
|
||||
@input="updateSelectedBranch"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button @click="$emit('createBranch')">{{ $t('falukant.branch.actions.create') }}</button>
|
||||
<button @click="$emit('upgradeBranch')" :disabled="!localSelectedBranch">
|
||||
{{ $t('falukant.branch.actions.upgrade') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormattedDropdown from '@/components/form/FormattedDropdown.vue';
|
||||
export default {
|
||||
name: "BranchSelection",
|
||||
components: { FormattedDropdown },
|
||||
props: {
|
||||
branches: { type: Array, required: true },
|
||||
selectedBranch: { type: Object, default: null },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localSelectedBranch: this.selectedBranch,
|
||||
branchColumns: [
|
||||
{ field: "cityName", label: this.$t('falukant.branch.columns.city') },
|
||||
{ field: "type", label: this.$t('falukant.branch.columns.type') },
|
||||
],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
selectedBranch(newVal) {
|
||||
this.localSelectedBranch = newVal;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateSelectedBranch(value) {
|
||||
this.$emit('branchSelected', value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.branch-selection {
|
||||
border: 1px solid #ccc;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
button {
|
||||
margin: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
120
frontend/src/components/falukant/DirectorInfo.vue
Normal file
120
frontend/src/components/falukant/DirectorInfo.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<div class="director-info">
|
||||
<h3>{{ $t('falukant.branch.director.title') }}</h3>
|
||||
<div v-if="!director || director === null">
|
||||
<button @click="openNewDirectorDialog">{{ $t('falukant.branch.director.actions.new') }}</button>
|
||||
</div>
|
||||
<div v-else class="director-info-container">
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.branch.director.name') }}</td>
|
||||
<td>
|
||||
{{ $t('falukant.titles.' + director.character.gender + '.' + director.character.title) }}
|
||||
{{ director.character.name }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.branch.director.salary') }}</td>
|
||||
<td>{{ director.income }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.branch.director.satisfaction') }}</td>
|
||||
<td>{{ director.satisfaction }} %</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<table>
|
||||
<tr>
|
||||
<td><button @click="fireDirector">{{ $t('falukant.branch.director.fire') }}</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><button @click="teachDirector">{{ $t('falukant.branch.director.teach') }}</button></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label>
|
||||
<input type="checkbox" v-model="director.mayProduce" @change="saveSetting('mayProduce', director.mayProduce)">
|
||||
{{ $t('falukant.branch.director.produce') }}
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Ähnliche Checkboxen für maySell und mayStartTransport -->
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NewDirectorDialog ref="newDirectorDialog" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import NewDirectorDialog from '@/dialogues/falukant/NewDirectorDialog.vue';
|
||||
|
||||
export default {
|
||||
name: "DirectorInfo",
|
||||
props: { branchId: { type: Number, required: true } },
|
||||
components: {
|
||||
NewDirectorDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
director: null,
|
||||
showNewDirectorDialog: false,
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadDirector();
|
||||
},
|
||||
methods: {
|
||||
async loadDirector() {
|
||||
try {
|
||||
const response = await apiClient.get(`/api/falukant/director/${this.branchId}`);
|
||||
this.director = Object.keys(response.data).length === 0 || !response.data.director ? null : response.data.director;
|
||||
} catch (error) {
|
||||
console.error('Error loading director:', error);
|
||||
}
|
||||
},
|
||||
async saveSetting(settingKey, value) {
|
||||
if (!this.director) return;
|
||||
try {
|
||||
await apiClient.post(`/api/falukant/director/settings`, {
|
||||
branchId: this.branchId,
|
||||
directorId: this.director.id,
|
||||
settingKey,
|
||||
value,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error saving setting ${settingKey}:`, error);
|
||||
}
|
||||
},
|
||||
openNewDirectorDialog() {
|
||||
console.log('openNewDirectorDialog');
|
||||
this.$refs.newDirectorDialog.open(this.branchId);
|
||||
},
|
||||
fireDirector() {
|
||||
alert(this.$t('falukant.branch.director.fireAlert'));
|
||||
},
|
||||
teachDirector() {
|
||||
alert(this.$t('falukant.branch.director.teachAlert'));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.director-info {
|
||||
border: 1px solid #ccc;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
.director-info-container {
|
||||
display: flex;
|
||||
}
|
||||
.director-info-container > div {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
182
frontend/src/components/falukant/ProductionSection.vue
Normal file
182
frontend/src/components/falukant/ProductionSection.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div class="production-section">
|
||||
<h3>{{ $t('falukant.branch.production.title') }}</h3>
|
||||
<div v-if="productions && productions.length > 0">
|
||||
<h4>{{ $t('falukant.branch.production.current') }}</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.branch.production.product') }}</th>
|
||||
<th>{{ $t('falukant.branch.production.quantity') }}</th>
|
||||
<th>{{ $t('falukant.branch.production.ending') }}</th>
|
||||
<th>{{ $t('falukant.branch.production.remainingTime') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="production in productions" :key="production.id">
|
||||
<td>{{ $t(`falukant.product.${production.productType.labelTr}`) }}</td>
|
||||
<td>{{ production.quantity }}</td>
|
||||
<td>{{ calculateEndDateTime(production.startTimestamp, production.productType.productionTime) }}</td>
|
||||
<td>{{ calculateRemainingTime(production.startTimestamp, production.productType.productionTime) }} s</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-if="(!productions || productions.length < 2)">
|
||||
<div>
|
||||
<label for="product">{{ $t('falukant.branch.production.selectProduct') }}</label>
|
||||
<select name="product" id="product" v-model="selectedProduct">
|
||||
<option v-for="product in products" :key="product.id" :value="product.id">
|
||||
{{ $t(`falukant.product.${product.labelTr}`) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="quantity">{{ $t('falukant.branch.production.quantity') }}</label>
|
||||
<input type="number" id="quantity" v-model.number="productionQuantity" min="1" max="200"/>
|
||||
</div>
|
||||
<div>
|
||||
<p>{{ $t('falukant.branch.production.cost') }}:
|
||||
<strong>{{ calculateProductionCost() }}</strong>
|
||||
</p>
|
||||
<p>{{ $t('falukant.branch.production.duration') }}:
|
||||
<strong>{{ calculateProductionDuration(selectedProduct) }}</strong>
|
||||
</p>
|
||||
<p>{{ $t('falukant.branch.production.revenue') }}:
|
||||
<strong>{{ calculateProductionRevenue() }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
<button @click="startProduction" :disabled="!selectedProduct || productionQuantity < 1">
|
||||
{{ $t('falukant.branch.production.start') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
export default {
|
||||
name: "ProductionSection",
|
||||
props: {
|
||||
branchId: { type: Number, required: true },
|
||||
products: { type: Array, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
productions: [],
|
||||
selectedProduct: null,
|
||||
productionQuantity: 1,
|
||||
currentTime: Date.now(),
|
||||
timer: null,
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadProductions();
|
||||
this.timer = setInterval(() => {
|
||||
this.currentTime = Date.now();
|
||||
}, 1000);
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadProductions() {
|
||||
try {
|
||||
const response = await apiClient.get(`/api/falukant/branches/${this.branchId}`);
|
||||
this.productions = response.data.productions.sort((a, b) => {
|
||||
const endTimeA = new Date(a.startTimestamp).getTime() + a.productType.productionTime * 60 * 1000;
|
||||
const endTimeB = new Date(b.startTimestamp).getTime() + b.productType.productionTime * 60 * 1000;
|
||||
return endTimeA - endTimeB;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error loading productions:', error);
|
||||
}
|
||||
},
|
||||
calculateProductionCost() {
|
||||
if (!this.products) return 0;
|
||||
const product = this.products.find(p => p.id === this.selectedProduct);
|
||||
return product ? 6 * product.category * this.productionQuantity : 0;
|
||||
},
|
||||
calculateProductionDuration(productId) {
|
||||
if (!this.products || !productId) return 0;
|
||||
const product = this.products.find(p => p.id === productId);
|
||||
if (!product) return 0;
|
||||
const totalMinutes = product.productionTime * 60;
|
||||
return (totalMinutes / 60).toFixed(2).replace('.', ':');
|
||||
},
|
||||
calculateProductionRevenue() {
|
||||
if (!this.selectedProduct || !this.products) return 0;
|
||||
const product = this.products.find(p => p.id === this.selectedProduct);
|
||||
if (!product) return 0;
|
||||
const revenue = this.calculateProductRevenue(product).absolute * this.productionQuantity;
|
||||
return revenue.toFixed(2).toLocaleString();
|
||||
},
|
||||
calculateEndDateTime(startTimestamp, productionTime) {
|
||||
const start = new Date(startTimestamp);
|
||||
const end = new Date(start.getTime() + productionTime * 60 * 1000);
|
||||
return end.toLocaleString();
|
||||
},
|
||||
calculateRemainingTime(startTimestamp, productionTime) {
|
||||
const start = new Date(startTimestamp).getTime();
|
||||
const end = start + productionTime * 60 * 1000;
|
||||
const secondsLeft = Math.max(Math.floor((end - this.currentTime) / 1000), 0);
|
||||
return secondsLeft;
|
||||
},
|
||||
async startProduction() {
|
||||
if (this.selectedProduct && this.productionQuantity > 0) {
|
||||
this.productionQuantity = Math.min(this.productionQuantity, 200);
|
||||
let productionQuantity = this.productionQuantity;
|
||||
while (productionQuantity > 0) {
|
||||
const sendQuantitiy = Math.min(productionQuantity, 100);
|
||||
productionQuantity -= sendQuantitiy;
|
||||
try {
|
||||
await apiClient.post(`/api/falukant/production`, {
|
||||
branchId: this.branchId,
|
||||
productId: this.selectedProduct,
|
||||
quantity: sendQuantitiy,
|
||||
});
|
||||
this.loadProductions();
|
||||
} catch (error) {
|
||||
alert(this.$t(`falukant.branch.production.error${error.response.data.error}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
calculateProductRevenue(product) {
|
||||
if (!product.knowledges || product.knowledges.length === 0) {
|
||||
return { absolute: 0, perMinute: 0 };
|
||||
}
|
||||
const knowledgeFactor = product.knowledges[0].knowledge || 0;
|
||||
const maxPrice = product.sellCost;
|
||||
const minPrice = maxPrice * 0.6;
|
||||
const revenuePerUnit = minPrice + (maxPrice - minPrice) * (knowledgeFactor / 100);
|
||||
const perMinute = product.productionTime > 0 ? revenuePerUnit / product.productionTime : 0;
|
||||
return {
|
||||
absolute: revenuePerUnit.toFixed(2),
|
||||
perMinute: perMinute.toFixed(2),
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.production-section {
|
||||
border: 1px solid #ccc;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
</style>
|
||||
|
||||
98
frontend/src/components/falukant/RevenueSection.vue
Normal file
98
frontend/src/components/falukant/RevenueSection.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="revenue-section">
|
||||
<h3>
|
||||
<button @click="toggleRevenueTable">
|
||||
{{ $t('falukant.branch.revenue.title') }}
|
||||
{{ isRevenueTableOpen ? '▲' : '▼' }}
|
||||
</button>
|
||||
</h3>
|
||||
<div v-if="isRevenueTableOpen" class="revenue-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.branch.revenue.product') }}</th>
|
||||
<th>{{ $t('falukant.branch.revenue.knowledge') }}</th>
|
||||
<th>{{ $t('falukant.branch.revenue.absolute') }}</th>
|
||||
<th>{{ $t('falukant.branch.revenue.perMinute') }}</th>
|
||||
<th>{{ $t('falukant.branch.revenue.profitAbsolute') }}</th>
|
||||
<th>{{ $t('falukant.branch.revenue.profitPerMinute') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="product in products" :key="product.id" :class="{ highlight: product.id === productWithMaxRevenuePerMinute?.id }">
|
||||
<td>{{ $t(`falukant.product.${product.labelTr}`) }}</td>
|
||||
<td>{{ product.knowledges && product.knowledges[0] ? product.knowledges[0].knowledge : 0 }}</td>
|
||||
<td>{{ calculateProductRevenue(product).absolute }}</td>
|
||||
<td>{{ calculateProductRevenue(product).perMinute }}</td>
|
||||
<td>{{ calculateProductProfit(product).absolute }}</td>
|
||||
<td>{{ calculateProductProfit(product).perMinute }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "RevenueSection",
|
||||
props: {
|
||||
products: { type: Array, required: true },
|
||||
calculateProductRevenue: { type: Function, required: true },
|
||||
calculateProductProfit: { type: Function, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isRevenueTableOpen: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
productWithMaxRevenuePerMinute() {
|
||||
if (!this.products || this.products.length === 0) return null;
|
||||
return this.products.reduce((maxProduct, currentProduct) => {
|
||||
const currentRevenue = parseFloat(this.calculateProductRevenue(currentProduct).perMinute);
|
||||
const maxRevenue = maxProduct ? parseFloat(this.calculateProductRevenue(maxProduct).perMinute) : 0;
|
||||
return currentRevenue > maxRevenue ? currentProduct : maxProduct;
|
||||
}, null);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleRevenueTable() {
|
||||
this.isRevenueTableOpen = !this.isRevenueTableOpen;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.revenue-section {
|
||||
border: 1px solid #ccc;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
.revenue-section button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #007bff;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.revenue-table {
|
||||
margin-top: 10px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.revenue-table table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
.highlight {
|
||||
background-color: #dfffd6;
|
||||
}
|
||||
</style>
|
||||
|
||||
101
frontend/src/components/falukant/SaleSection.vue
Normal file
101
frontend/src/components/falukant/SaleSection.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="sale-section">
|
||||
<h3>{{ $t('falukant.branch.sale.title') }}</h3>
|
||||
<!-- Beispielhafte Inventar-Tabelle -->
|
||||
<div v-if="inventory.length > 0" class="inventory-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.branch.sale.region') }}</th>
|
||||
<th>{{ $t('falukant.branch.sale.product') }}</th>
|
||||
<th>{{ $t('falukant.branch.sale.quality') }}</th>
|
||||
<th>{{ $t('falukant.branch.sale.quantity') }}</th>
|
||||
<th>{{ $t('falukant.branch.sale.sell') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in inventory" :key="`${item.region.id}-${item.product.id}-${item.quality}`">
|
||||
<td>{{ item.region.name }}</td>
|
||||
<td>{{ $t(`falukant.product.${item.product.labelTr}`) }}</td>
|
||||
<td>{{ item.quality }}</td>
|
||||
<td>{{ item.totalQuantity }}</td>
|
||||
<td>
|
||||
<input type="number" v-model.number="item.sellQuantity" :min="1" :max="item.totalQuantity" />
|
||||
<button @click="sellItem(index)">{{ $t('falukant.branch.sale.sellButton') }}</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button @click="sellAll">{{ $t('falukant.branch.sale.sellAllButton') }}</button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>{{ $t('falukant.branch.sale.noInventory') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
export default {
|
||||
name: "SaleSection",
|
||||
props: { branchId: { type: Number, required: true } },
|
||||
data() {
|
||||
return {
|
||||
inventory: [],
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadInventory();
|
||||
},
|
||||
methods: {
|
||||
async loadInventory() {
|
||||
try {
|
||||
const response = await apiClient.get(`/api/falukant/inventory/${this.branchId}`);
|
||||
this.inventory = response.data.map(item => ({
|
||||
...item,
|
||||
sellQuantity: item.totalQuantity,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error loading inventory:', error);
|
||||
}
|
||||
},
|
||||
sellItem(index) {
|
||||
const item = this.inventory[index];
|
||||
const quantityToSell = item.sellQuantity || item.totalQuantity;
|
||||
apiClient.post(`/api/falukant/sell`, {
|
||||
branchId: this.branchId,
|
||||
productId: item.product.id,
|
||||
quantity: quantityToSell,
|
||||
quality: item.quality,
|
||||
}).catch(() => {
|
||||
alert(this.$t('falukant.branch.sale.sellError'));
|
||||
});
|
||||
},
|
||||
sellAll() {
|
||||
apiClient.post(`/api/falukant/sell/all`, { branchId: this.branchId })
|
||||
.catch(() => {
|
||||
alert(this.$t('falukant.branch.sale.sellAllError'));
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sale-section {
|
||||
border: 1px solid #ccc;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
.inventory-table table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.inventory-table th,
|
||||
.inventory-table td {
|
||||
padding: 2px 3px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,11 +5,16 @@
|
||||
<span class="status-icon">{{ item.icon }}: {{ item.value }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<span v-if="statusItems.length > 0">
|
||||
<template v-for="(menuItem, key) in menu.falukant.children" :key="menuItem.id" >
|
||||
<img :src="'/images/icons/falukant/' + key + '.jpg'" class="menu-icon" @click="openPage(menuItem)" :title="$t(`navigation.m-falukant.${key}`)" />
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import apiClient from "@/utils/axios.js";
|
||||
|
||||
export default {
|
||||
@@ -25,18 +30,25 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState(["socket"]),
|
||||
...mapState(["socket", "daemonSocket"]),
|
||||
...mapGetters(['menu']),
|
||||
},
|
||||
async mounted() {
|
||||
await this.fetchStatus();
|
||||
if (this.socket) {
|
||||
this.socket.on("falukantUpdateStatus", this.fetchStatus);
|
||||
}
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.addEventListener("message", this.handleDaemonSocketMessage);
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.socket) {
|
||||
this.socket.off("falukantUpdateStatus", this.fetchStatus);
|
||||
}
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.removeEventListener("message", this.handleDaemonSocketMessage);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchStatus() {
|
||||
@@ -66,6 +78,24 @@ export default {
|
||||
console.error("Error fetching status:", error);
|
||||
}
|
||||
},
|
||||
async handleDaemonSocketMessage(event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.event === "falukantUpdateStatus") {
|
||||
this.fetchStatus();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error parsing daemonSocket message:", error);
|
||||
}
|
||||
},
|
||||
openPage(url, hasSubmenu = false) {
|
||||
if (hasSubmenu) {
|
||||
return;
|
||||
}
|
||||
if (url) {
|
||||
this.$router.push(url);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -92,4 +122,11 @@ export default {
|
||||
.status-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
cursor: pointer;
|
||||
padding: 4px 2px 0 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
220
frontend/src/components/falukant/StorageSection.vue
Normal file
220
frontend/src/components/falukant/StorageSection.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<div class="storage-section">
|
||||
<h3>{{ $t('falukant.branch.storage.title') }}</h3>
|
||||
<div class="storage-info">
|
||||
<p>
|
||||
{{ $t('falukant.branch.storage.currentCapacity') }}:
|
||||
<strong>{{ currentStorage }} / {{ maxStorage }}</strong>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.branch.storage.stockType') }}</th>
|
||||
<th>{{ $t('falukant.branch.storage.totalCapacity') }}</th>
|
||||
<th>{{ $t('falukant.branch.storage.used') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(usage, idx) in storageUsage" :key="idx">
|
||||
<td>{{ $t(`falukant.branch.stocktype.${usage.stockTypeLabelTr}`) }}</td>
|
||||
<td>{{ usage.totalCapacity }}</td>
|
||||
<td>{{ usage.used }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>{{ $t('falukant.branch.storage.availableToBuy') }}</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.branch.storage.stockType') }}</th>
|
||||
<th>{{ $t('falukant.branch.storage.totalCapacity') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Hier zeigen wir die gruppierten (nach Label) Einträge an -->
|
||||
<tr v-for="(group, i) in buyableUsage" :key="i">
|
||||
<td>{{ $t(`falukant.branch.stocktype.${group.stockTypeLabelTr}`) }}</td>
|
||||
<td>{{ group.totalQuantity }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="storage-market">
|
||||
<div class="buy-section">
|
||||
<label>{{ $t('falukant.branch.storage.selectStockType') }}</label>
|
||||
<!-- Auswahl basiert jetzt auf dem Label -->
|
||||
<select v-model="selectedBuyStockTypeLabelTr">
|
||||
<option v-for="group in buyableUsage" :key="group.stockTypeLabelTr" :value="group.stockTypeLabelTr">
|
||||
{{ $t(`falukant.branch.stocktype.${group.stockTypeLabelTr}`) }} - {{ getCostOfType(group.stockTypeLabelTr) }}
|
||||
</option>
|
||||
</select>
|
||||
<div>
|
||||
<label>{{ $t('falukant.branch.storage.buyAmount') }}</label>
|
||||
<input type="number" v-model.number="buyStorageAmount" :max="maxBuyableForSelectedBuy" min="1" />
|
||||
<button @click="onBuyStorage">
|
||||
{{ $t('falukant.branch.storage.buyStorageButton') }} ({{ buyCost }})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sell-section">
|
||||
<label>{{ $t('falukant.branch.storage.selectStockType') }}</label>
|
||||
<select v-model="selectedSellStockTypeId">
|
||||
<option v-for="type in storageUsage" :key="type.stockTypeId" :value="type.stockTypeId">
|
||||
{{ $t(`falukant.branch.stocktype.${type.stockTypeLabelTr}`) }} - {{ getCostOfTypeById(type.stockTypeId) }}
|
||||
</option>
|
||||
</select>
|
||||
<div>
|
||||
<label>{{ $t('falukant.branch.storage.sellAmount') }}</label>
|
||||
<input type="number" v-model.number="sellStorageAmount" :max="maxSellableForSelectedSell" min="1" />
|
||||
<button @click="onSellStorage">
|
||||
{{ $t('falukant.branch.storage.sellStorageButton') }} ({{ sellIncome }})
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
export default {
|
||||
name: "StorageSection",
|
||||
props: { branchId: { type: Number, required: true } },
|
||||
data() {
|
||||
return {
|
||||
currentStorage: 0,
|
||||
maxStorage: 0,
|
||||
storageUsage: [],
|
||||
buyableUsage: [],
|
||||
buyStorageAmount: 0,
|
||||
sellStorageAmount: 0,
|
||||
stockTypes: [],
|
||||
selectedBuyStockTypeLabelTr: null,
|
||||
selectedSellStockTypeId: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
buyCost() {
|
||||
const cost = this.getCostOfType(this.selectedBuyStockTypeLabelTr);
|
||||
return this.buyStorageAmount * cost;
|
||||
},
|
||||
sellIncome() {
|
||||
const cost = this.getCostOfTypeById(this.selectedSellStockTypeId);
|
||||
return this.sellStorageAmount * cost;
|
||||
},
|
||||
maxBuyableForSelectedBuy() {
|
||||
const group = this.buyableUsage.find(g => g.stockTypeLabelTr === this.selectedBuyStockTypeLabelTr);
|
||||
return group ? group.totalQuantity : 0;
|
||||
},
|
||||
maxSellableForSelectedSell() {
|
||||
const usage = this.storageUsage.find(u => u.stockTypeId === this.selectedSellStockTypeId);
|
||||
return usage ? usage.totalCapacity : 0;
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadStorageData();
|
||||
await this.loadStockTypes();
|
||||
},
|
||||
methods: {
|
||||
async loadStorageData() {
|
||||
try {
|
||||
const { data } = await apiClient.get(`/api/falukant/storage/${this.branchId}`);
|
||||
this.currentStorage = data.totalUsedCapacity;
|
||||
this.maxStorage = data.maxCapacity;
|
||||
this.storageUsage = data.usageByType;
|
||||
const filteredBuyable = data.buyableByType.filter(item => item.quantity > 0);
|
||||
const grouped = {};
|
||||
filteredBuyable.forEach(item => {
|
||||
const key = item.stockTypeLabelTr;
|
||||
if (!grouped[key]) {
|
||||
grouped[key] = { stockTypeLabelTr: key, totalQuantity: 0, items: [] };
|
||||
}
|
||||
grouped[key].totalQuantity += item.quantity;
|
||||
grouped[key].items.push({ stockTypeId: item.stockTypeId, quantity: item.quantity });
|
||||
});
|
||||
this.buyableUsage = Object.values(grouped);
|
||||
} catch (error) {
|
||||
console.error('Error loading storage data:', error);
|
||||
}
|
||||
},
|
||||
async loadStockTypes() {
|
||||
try {
|
||||
const response = await apiClient.get('/api/falukant/stocktypes');
|
||||
this.stockTypes = response.data;
|
||||
if (this.stockTypes.length) {
|
||||
this.selectedBuyStockTypeLabelTr = this.stockTypes[0].labelTr;
|
||||
this.selectedSellStockTypeId = this.stockTypes[0].id;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading stock types:', error);
|
||||
}
|
||||
},
|
||||
async onBuyStorage() {
|
||||
if (!this.branchId || !this.buyStorageAmount || !this.selectedBuyStockTypeLabelTr) return;
|
||||
const group = this.buyableUsage.find(g => g.stockTypeLabelTr === this.selectedBuyStockTypeLabelTr);
|
||||
if (!group) return;
|
||||
let remainingAmount = this.buyStorageAmount;
|
||||
for (const item of group.items) {
|
||||
if (remainingAmount <= 0) break;
|
||||
const toBuy = Math.min(remainingAmount, item.quantity);
|
||||
try {
|
||||
await apiClient.post(`/api/falukant/storage`, {
|
||||
branchId: this.branchId,
|
||||
amount: toBuy,
|
||||
stockTypeId: item.stockTypeId
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
alert('Error buying storage for one part of the order');
|
||||
}
|
||||
remainingAmount -= toBuy;
|
||||
}
|
||||
if (remainingAmount > 0) {
|
||||
alert(this.$t('falukant.branch.storage.notEnoughAvailable'));
|
||||
}
|
||||
this.loadStorageData();
|
||||
},
|
||||
onSellStorage() {
|
||||
if (!this.branchId || !this.sellStorageAmount || !this.selectedSellStockTypeId) return;
|
||||
apiClient.delete(`/api/falukant/storage`, {
|
||||
data: {
|
||||
branchId: this.branchId,
|
||||
amount: this.sellStorageAmount,
|
||||
stockTypeId: this.selectedSellStockTypeId
|
||||
}
|
||||
})
|
||||
.then(() => this.loadStorageData())
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
alert('Error selling storage');
|
||||
});
|
||||
},
|
||||
getCostOfType(labelTr) {
|
||||
const st = this.stockTypes.find(s => s.labelTr === labelTr);
|
||||
return st ? st.cost : 0;
|
||||
},
|
||||
getCostOfTypeById(stockTypeId) {
|
||||
const st = this.stockTypes.find(s => s.id === stockTypeId);
|
||||
return st ? st.cost : 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.storage-section {
|
||||
border: 1px solid #ccc;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
.storage-info table, .storage-market table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -42,6 +42,12 @@
|
||||
"selectPermissions": "Bitte auswählen",
|
||||
"confirmDeleteMessage": "Soll das Forum wirklich gelöscht werden?",
|
||||
"confirmDeleteTitle": "Forum löschen"
|
||||
},
|
||||
"falukant": {
|
||||
"edituser": {
|
||||
"success": "Die Änderungen wurden gespeichert.",
|
||||
"error": "Die Änderungen konnten nicht gespeichert werden."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,8 @@
|
||||
"name": "Name",
|
||||
"money": "Vermögen",
|
||||
"age": "Alter",
|
||||
"mainbranch": "Heimatstadt"
|
||||
"mainbranch": "Heimatstadt",
|
||||
"nobleTitle": "Stand"
|
||||
},
|
||||
"productions": {
|
||||
"title": "Produktionen"
|
||||
@@ -194,6 +195,55 @@
|
||||
"field": "Feldlager"
|
||||
}
|
||||
},
|
||||
"family": {
|
||||
"title": "Familie",
|
||||
"spouse": {
|
||||
"title": "Beziehung",
|
||||
"name": "Name",
|
||||
"age": "Alter",
|
||||
"status": "Status",
|
||||
"none": "Kein Ehepartner vorhanden.",
|
||||
"search": "Ehepartner suchen",
|
||||
"found": "Ehepartner gefunden",
|
||||
"select": "Verloben mit",
|
||||
"marriagecost": "Verlobungskosten",
|
||||
"notice": "Hinweis. Die beiden Ehepartner bekommen beide den Titel, der höher ist.",
|
||||
"accept": "Werbung mit diesem Partner starten",
|
||||
"wooing": {
|
||||
"gifts": "Werbegeschenke",
|
||||
"sendGift": "Werbegeschenk senden"
|
||||
}
|
||||
},
|
||||
"relationships": {
|
||||
"name": "Name"
|
||||
},
|
||||
"children": {
|
||||
"title": "Kinder",
|
||||
"name": "Name",
|
||||
"age": "Alter",
|
||||
"actions": "Aktionen",
|
||||
"none": "Keine Kinder vorhanden.",
|
||||
"detailButton": "Details anzeigen",
|
||||
"addChild": "Kind hinzufügen"
|
||||
},
|
||||
"lovers": {
|
||||
"title": "Liebhaber",
|
||||
"none": "Keine Liebhaber vorhanden.",
|
||||
"affection": "Zuneigung"
|
||||
},
|
||||
"statuses": {
|
||||
"wooing": "In Werbung",
|
||||
"engaged": "Verlobt",
|
||||
"married": "Verheiratet",
|
||||
"single": "Ledig",
|
||||
"widowed": "Verwitwet"
|
||||
},
|
||||
"actions": {
|
||||
"addSpouse": "Ehepartner hinzufügen",
|
||||
"viewDetails": "Details anzeigen",
|
||||
"remove": "Entfernen"
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"wheat": "Weizen",
|
||||
"grain": "Getreide",
|
||||
@@ -262,6 +312,21 @@
|
||||
"low": "Schlecht",
|
||||
"verylow": "Sehr schlecht",
|
||||
"none": "Kein Wissen"
|
||||
},
|
||||
"gifts": {
|
||||
"Gold Coin": "Goldmünze",
|
||||
"Silk Scarf": "Seidenschale",
|
||||
"Exotic Perfume": "Exotisches Parfum",
|
||||
"Crystal Pendant": "Kristallanhänger",
|
||||
"Leather Journal": "Lederjournal",
|
||||
"Fine Wine": "Feiner Wein",
|
||||
"Artisan Chocolate": "Kunsthandwerkliche Schokolade",
|
||||
"Pearl Necklace": "Perlenanhänger",
|
||||
"Rare Painting": "Seltenes Gemälde",
|
||||
"Silver Watch": "Silberuhr",
|
||||
"Cat": "Katze",
|
||||
"Dog": "Hund",
|
||||
"Horse": "Pferd"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,12 @@
|
||||
"forum": "Forum",
|
||||
"userrights": "Benutzerrechte",
|
||||
"interests": "Interessen",
|
||||
"falukant": "Falukant"
|
||||
"falukant": "Falukant",
|
||||
"m-falukant": {
|
||||
"logentries": "Log-Einträge",
|
||||
"edituser": "Benutzer bearbeiten",
|
||||
"database": "Datenbank"
|
||||
}
|
||||
},
|
||||
"m-friends": {
|
||||
"manageFriends": "Freunde verwalten",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import AdminInterestsView from '../views/admin/InterestsView.vue';
|
||||
import AdminContactsView from '../views/admin/ContactsView.vue';
|
||||
import ForumAdminView from '../dialogues/admin/ForumAdminView.vue';
|
||||
import AdminFalukantEditUserView from '../views/admin/falukant/EditUserView.vue'
|
||||
|
||||
const adminRoutes = [
|
||||
{
|
||||
@@ -21,6 +22,12 @@ const adminRoutes = [
|
||||
component: ForumAdminView,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/admin/falukant/edituser',
|
||||
name: 'AdminFalukantEditUserView',
|
||||
component: AdminFalukantEditUserView,
|
||||
meta: { requiresAuth: true }
|
||||
}
|
||||
];
|
||||
|
||||
export default adminRoutes;
|
||||
|
||||
@@ -2,6 +2,7 @@ import BranchView from '../views/falukant/BranchView.vue';
|
||||
import Createview from '../views/falukant/CreateView.vue';
|
||||
import FalukantOverviewView from '../views/falukant/OverviewView.vue';
|
||||
import MoneyHistoryView from '../views/falukant/MoneyHistoryView.vue';
|
||||
import FamilyView from '../views/falukant/FamilyView.vue';
|
||||
|
||||
const falukantRoutes = [
|
||||
{
|
||||
@@ -28,6 +29,12 @@ const falukantRoutes = [
|
||||
component: MoneyHistoryView,
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/falukant/family',
|
||||
name: 'FalukantFamily',
|
||||
component: FamilyView,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
];
|
||||
|
||||
export default falukantRoutes;
|
||||
|
||||
@@ -159,9 +159,9 @@ const store = createStore({
|
||||
setTimeout(() => {
|
||||
console.log('Retrying Daemon WebSocket connection...');
|
||||
reconnectFn();
|
||||
}, 1000); // Retry every second
|
||||
}, 1000);
|
||||
console.log('Retrying Daemon WebSocket connection...');
|
||||
};
|
||||
|
||||
connectDaemonSocket();
|
||||
}
|
||||
},
|
||||
|
||||
116
frontend/src/views/admin/falukant/EditUserView.vue
Normal file
116
frontend/src/views/admin/falukant/EditUserView.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Edit Falukant User</h1>
|
||||
<div>
|
||||
<label>Username: <input type="text" v-model="user.username" /></label>
|
||||
<label>Character name: <input type="text" v-model="user.characterName" /></label>
|
||||
<button @click="searchUser">Search</button>
|
||||
</div>
|
||||
<ul v-for="user in users" class="user-list">
|
||||
<li @click="selectUser(user)">{{ user.username }} ({{ user.falukantUser[0].character.definedFirstName.name }} {{
|
||||
user.falukantUser[0].character.definedLastName.name }})</li>
|
||||
</ul>
|
||||
<div v-if="editableUser" class="edit-form">
|
||||
<h2>User: {{ editableUser.username }}</h2>
|
||||
<h3>Character-Name: {{ editableUser.falukantData[0].character.definedFirstName.name }} {{
|
||||
editableUser.falukantData[0].character.definedLastName.name }}</h3>
|
||||
<label>Money: <input type="number" v-model="editableUser.falukantData[0].money" /></label>
|
||||
<label>Age: <input type="number" v-model="age" /></label>
|
||||
<label>Noble title:
|
||||
<select v-model="editableUser.falukantData[0].character.title_of_nobility">
|
||||
<option v-for="title in titles" :value="title.id">{{ $t(`falukant.titles.male.${title.labelTr}`) }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>House: <select v-model="editableUser.falukantData[0].house">
|
||||
<option v-for="house in houses" :value="house.id">{{ $t(`${house.labelTr}`) }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<button @click="saveUser">Save</button>
|
||||
<button @click="deleteUser">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { mapState } from 'vuex';
|
||||
import { mapActions } from 'vuex';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
|
||||
export default {
|
||||
name: 'AdminFalukantEditUserView',
|
||||
data() {
|
||||
return {
|
||||
user: {
|
||||
username: '',
|
||||
characterName: ''
|
||||
},
|
||||
users: [],
|
||||
editableUser: null,
|
||||
age: null,
|
||||
originalAge: null,
|
||||
originalUser: null,
|
||||
titles: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('falukant', ['user'])
|
||||
},
|
||||
async mounted() {
|
||||
const titlesResult = await apiClient.get('/api/falukant/nobility/titels');
|
||||
this.titles = titlesResult.data;
|
||||
},
|
||||
methods: {
|
||||
async searchUser() {
|
||||
const userResult = await apiClient.post('/api/admin/falukant/searchuser', {
|
||||
userName: this.user.username,
|
||||
characterName: this.user.characterName
|
||||
});
|
||||
this.users = userResult.data;
|
||||
},
|
||||
async selectUser(user) {
|
||||
const userResult = await apiClient.get(`/api/admin/falukant/getuser/${user.id}`);
|
||||
this.editableUser = userResult.data;
|
||||
this.originalUser = JSON.parse(JSON.stringify(this.editableUser));
|
||||
this.age = Math.floor((Date.now() - new Date(this.editableUser.falukantData[0].character.birthdate)) / (24 * 60 * 60 * 1000));
|
||||
this.originalAge = this.age;
|
||||
this.users = [];
|
||||
},
|
||||
async saveUser() {
|
||||
const dataToChange = {
|
||||
id: this.editableUser.falukantData[0].id,
|
||||
};
|
||||
if (this.editableUser.falukantData[0].money != this.originalUser.falukantData[0].money) {
|
||||
dataToChange.money = this.editableUser.falukantData[0].money;
|
||||
}
|
||||
if (this.editableUser.falukantData[0].character.title_of_nobility != this.originalUser.falukantData[0].character.title_of_nobility) {
|
||||
dataToChange.title_of_nobility = this.editableUser.falukantData[0].character.title_of_nobility;
|
||||
}
|
||||
if (this.originalAge != this.age) {
|
||||
dataToChange.age = this.age;
|
||||
}
|
||||
try {
|
||||
await apiClient.post(`/api/admin/falukant/edituser`, dataToChange);
|
||||
this.$root.$refs.messageDialog.open('tr:admin.falukant.edituser.success');
|
||||
} catch (error) {
|
||||
this.$root.$refs.errorDialog.open('tr:admin.falukant.edituser.error');
|
||||
}
|
||||
},
|
||||
async deleteUser() {
|
||||
const dataToChange = {
|
||||
id: this.editableUser.falukantData[0].id,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-list>li {
|
||||
cursor: pointer;
|
||||
color: #0066ff;
|
||||
}
|
||||
|
||||
.edit-form label {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
287
frontend/src/views/falukant/FamilyView.vue
Normal file
287
frontend/src/views/falukant/FamilyView.vue
Normal file
@@ -0,0 +1,287 @@
|
||||
<template>
|
||||
<div class="contenthidden">
|
||||
<StatusBar />
|
||||
<div class="contentscroll">
|
||||
|
||||
<!-- Titel -->
|
||||
<h2>{{ $t('falukant.family.title') }}</h2>
|
||||
|
||||
<!-- Ehepartner -->
|
||||
<div class="spouse-section">
|
||||
<h3>{{ $t('falukant.family.spouse.title') }}</h3>
|
||||
<div v-if="relationships.length > 0">
|
||||
<table>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.family.relationships.name') }}</td>
|
||||
<td>
|
||||
{{ $t('falukant.titles.' + relationships[0].character2.gender + '.' +
|
||||
relationships[0].character2.nobleTitle) }}
|
||||
{{ relationships[0].character2.firstName }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.family.spouse.age') }}</td>
|
||||
<td>{{ relationships[0].character2.age }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.family.spouse.status') }}</td>
|
||||
<td>{{ $t('falukant.family.statuses.' + relationships[0].relationshipType) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div v-if="relationships[0].relationshipType === 'wooing'">
|
||||
<h3>{{ $t('falukant.family.spouse.wooing.gifts') }}</h3>
|
||||
<table class="spouse-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>{{ $t('falukant.family.spouse.wooing.gift') }}</th>
|
||||
<th>{{ $t('falukant.family.spouse.wooing.value') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="gift in gifts" :key="gift.id">
|
||||
<td><input type="radio" name="gift" :value="gift.id" v-model="selectedGiftId"></td>
|
||||
<td>{{ $t(`falukant.gifts.${gift.name}`) }}</td>
|
||||
<td>{{ formatCost(gift.cost) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div>
|
||||
<button @click="sendGift" class="button">{{ $t('falukant.family.spouse.wooing.sendGift') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="proposals && proposals.length > 0">
|
||||
<table class="spouse-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.family.spouse.select') }}</th>
|
||||
<th>{{ $t('falukant.family.spouse.name') }}</th>
|
||||
<th>{{ $t('falukant.family.spouse.age') }}</th>
|
||||
<th>{{ $t('falukant.family.spouse.marriagecost') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="proposal in proposals" :key="proposal.id">
|
||||
<td><input type="radio" name="spouse" :value="proposal.proposedCharacterId"
|
||||
v-model="selectedProposalId"></td>
|
||||
<td>{{
|
||||
$t(`falukant.titles.${proposal.proposedCharacterGender}.${proposal.proposedCharacterNobleTitle}`)
|
||||
}} {{ proposal.proposedCharacterName }}</td>
|
||||
<td>{{ proposal.proposedCharacterAge }}</td>
|
||||
<td>{{ formatCost(proposal.cost) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div>{{ $t('falukant.family.spouse.notice') }}</div>
|
||||
<div v-if="selectedProposalId">
|
||||
<button @click="acceptProposal">{{ $t('falukant.family.spouse.accept') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kinder -->
|
||||
<div class="children-section">
|
||||
<h3>{{ $t('falukant.family.children.title') }}</h3>
|
||||
<div v-if="children && children.length > 0">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.family.children.name') }}</th>
|
||||
<th>{{ $t('falukant.family.children.age') }}</th>
|
||||
<th>{{ $t('falukant.family.children.actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(child, index) in children" :key="index">
|
||||
<td>
|
||||
{{ $t('falukant.titles.' + child.gender + '.' + child.title) }}
|
||||
{{ child.name }}
|
||||
</td>
|
||||
<td>{{ child.age }}</td>
|
||||
<td>
|
||||
<button @click="showChildDetails(child)">
|
||||
{{ $t('falukant.family.children.detailButton') }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>{{ $t('falukant.family.children.none') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Liebhaber / Geliebte -->
|
||||
<div class="lovers-section">
|
||||
<h3>{{ $t('falukant.family.lovers.title') }}</h3>
|
||||
<div v-if="lovers && lovers.length > 0">
|
||||
<ul>
|
||||
<li v-for="(lover, idx) in lovers" :key="idx">
|
||||
{{ $t('falukant.titles.' + lover.gender + '.' + lover.title) }} {{ lover.name }}
|
||||
({{ $t('falukant.family.lovers.affection') }}: {{ lover.affection }})
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p>{{ $t('falukant.family.lovers.none') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- Dialog-Beispiele oder ähnliche Komponenten -->
|
||||
<MessageDialog ref="messageDialog" />
|
||||
<ErrorDialog ref="errorDialog" />
|
||||
</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 { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'FamilyView',
|
||||
components: {
|
||||
StatusBar,
|
||||
MessageDialog,
|
||||
ErrorDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
relationships: [],
|
||||
children: [],
|
||||
lovers: [],
|
||||
deathPartners: [],
|
||||
proposals: [],
|
||||
selectedProposalId: null,
|
||||
gifts: [],
|
||||
selectedGiftId: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['socket', 'daemonSocket'])
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadFamilyData();
|
||||
await this.loadGifts();
|
||||
},
|
||||
methods: {
|
||||
async loadFamilyData() {
|
||||
try {
|
||||
const response = await apiClient.get('/api/falukant/family');
|
||||
console.log(response.data);
|
||||
this.relationships = response.data.relationships;
|
||||
this.children = response.data.children;
|
||||
this.lovers = response.data.lovers;
|
||||
this.proposals = response.data.possiblePartners;
|
||||
this.deathPartners = response.data.deathPartners;
|
||||
} catch (error) {
|
||||
console.error('Error loading family data:', error);
|
||||
}
|
||||
},
|
||||
|
||||
showChildDetails(child) {
|
||||
console.log('Show details for child:', child);
|
||||
},
|
||||
|
||||
formatCost(value) {
|
||||
return new Intl.NumberFormat(navigator.language, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value);
|
||||
},
|
||||
|
||||
async acceptProposal() {
|
||||
const response = await apiClient.post('/api/falukant/family/acceptmarriageproposal'
|
||||
, { proposalId: this.selectedProposalId });
|
||||
this.loadFamilyData();
|
||||
},
|
||||
|
||||
async loadGifts() {
|
||||
const response = await apiClient.get('/api/falukant/family/gifts');
|
||||
this.gifts = response.data;
|
||||
},
|
||||
|
||||
async sendGift() {
|
||||
if (!this.selectedGiftId) {
|
||||
alert('Please select a gift');
|
||||
return;
|
||||
}
|
||||
const response = await apiClient.post('/api/falukant/family/gift'
|
||||
, { giftId: this.selectedGiftId });
|
||||
this.loadFamilyData();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.spouse-section,
|
||||
.children-section,
|
||||
.lovers-section {
|
||||
border: 1px solid #ccc;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.spouse-section table,
|
||||
.children-section table {
|
||||
margin-top: 10px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.spouse-section th,
|
||||
.spouse-section td,
|
||||
.children-section th,
|
||||
.children-section td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.spouse-section th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
.children-section th {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.lovers-section {
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 4px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.spouse-table th,
|
||||
.spouse-table td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.spouse-table th:first-child,
|
||||
.spouse-table td:first-child {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.spouse-table th:nth-child(3),
|
||||
.spouse-table td:nth-child(3) {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.spouse-table th:nth-child(4),
|
||||
.spouse-table td:nth-child(4) {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-top: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,106 +1,118 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>{{ $t('falukant.moneyHistory.title') }}</h2>
|
||||
|
||||
<div class="filter-section">
|
||||
<label>{{ $t('falukant.moneyHistory.filter') }}</label>
|
||||
<input v-model="filter" type="text" />
|
||||
<button @click="fetchMoneyHistory(1)">{{ $t('falukant.moneyHistory.search') }}</button>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.moneyHistory.activity') }}</th>
|
||||
<th>{{ $t('falukant.moneyHistory.moneyBefore') }}</th>
|
||||
<th>{{ $t('falukant.moneyHistory.moneyAfter') }}</th>
|
||||
<th>{{ $t('falukant.moneyHistory.changeValue') }}</th>
|
||||
<th>{{ $t('falukant.moneyHistory.time') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(entry, index) in moneyHistory.data" :key="index">
|
||||
<td>{{ $t(`falukant.moneyHistory.activities.${entry.activity}`) }}</td>
|
||||
<td>{{ entry.moneyBefore }}</td>
|
||||
<td>{{ entry.moneyAfter }}</td>
|
||||
<td>{{ entry.changeValue }}</td>
|
||||
<td>{{ new Date(entry.time).toLocaleString() }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pagination">
|
||||
<button
|
||||
v-if="moneyHistory.currentPage > 1"
|
||||
@click="fetchMoneyHistory(moneyHistory.currentPage - 1)"
|
||||
>
|
||||
{{ $t('falukant.moneyHistory.prev') }}
|
||||
</button>
|
||||
<span>
|
||||
{{ moneyHistory.currentPage }} / {{ moneyHistory.totalPages }}
|
||||
</span>
|
||||
<button
|
||||
v-if="moneyHistory.currentPage < moneyHistory.totalPages"
|
||||
@click="fetchMoneyHistory(moneyHistory.currentPage + 1)"
|
||||
>
|
||||
{{ $t('falukant.moneyHistory.next') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="moneyflow">
|
||||
<StatusBar ref="statusBar" />
|
||||
<h2>{{ $t('falukant.moneyHistory.title') }}</h2>
|
||||
|
||||
<div class="filter-section">
|
||||
<label>{{ $t('falukant.moneyHistory.filter') }}</label>
|
||||
<input v-model="filter" type="text" />
|
||||
<button @click="fetchMoneyHistory(1)">{{ $t('falukant.moneyHistory.search') }}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
|
||||
export default {
|
||||
name: 'MoneyHistoryView',
|
||||
data() {
|
||||
return {
|
||||
filter: '',
|
||||
moneyHistory: {
|
||||
data: [],
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
},
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
await this.fetchMoneyHistory(1);
|
||||
},
|
||||
methods: {
|
||||
async fetchMoneyHistory(page) {
|
||||
try {
|
||||
const response = await apiClient.post('/api/falukant/moneyhistory', {
|
||||
page,
|
||||
filter: this.filter,
|
||||
});
|
||||
this.moneyHistory = response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching money history:', error);
|
||||
}
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ $t('falukant.moneyHistory.activity') }}</th>
|
||||
<th>{{ $t('falukant.moneyHistory.moneyBefore') }}</th>
|
||||
<th>{{ $t('falukant.moneyHistory.moneyAfter') }}</th>
|
||||
<th>{{ $t('falukant.moneyHistory.changeValue') }}</th>
|
||||
<th>{{ $t('falukant.moneyHistory.time') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(entry, index) in moneyHistory.data" :key="index">
|
||||
<td>{{ $t(`falukant.moneyHistory.activities.${entry.activity}`) }}</td>
|
||||
<td>{{ entry.moneyBefore }}</td>
|
||||
<td>{{ entry.moneyAfter }}</td>
|
||||
<td>{{ entry.changeValue }}</td>
|
||||
<td>{{ new Date(entry.time).toLocaleString() }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pagination">
|
||||
<button v-if="moneyHistory.currentPage > 1" @click="fetchMoneyHistory(moneyHistory.currentPage - 1)">
|
||||
{{ $t('falukant.moneyHistory.prev') }}
|
||||
</button>
|
||||
<span>
|
||||
{{ moneyHistory.currentPage }} / {{ moneyHistory.totalPages }}
|
||||
</span>
|
||||
<button v-if="moneyHistory.currentPage < moneyHistory.totalPages"
|
||||
@click="fetchMoneyHistory(moneyHistory.currentPage + 1)">
|
||||
{{ $t('falukant.moneyHistory.next') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StatusBar from '@/components/falukant/StatusBar.vue'
|
||||
import apiClient from '@/utils/axios.js';
|
||||
|
||||
export default {
|
||||
name: 'MoneyHistoryView',
|
||||
components: {
|
||||
StatusBar,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
filter: '',
|
||||
moneyHistory: {
|
||||
data: [],
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
},
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
await this.fetchMoneyHistory(1);
|
||||
},
|
||||
methods: {
|
||||
async fetchMoneyHistory(page) {
|
||||
try {
|
||||
const response = await apiClient.post('/api/falukant/moneyhistory', {
|
||||
page,
|
||||
filter: this.filter,
|
||||
});
|
||||
this.moneyHistory = response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching money history:', error);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filter-section {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filter-section {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.moneyflow {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -11,9 +11,17 @@
|
||||
<td>{{ falukantUser?.character.definedFirstName.name }} {{
|
||||
falukantUser?.character.definedLastName.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.overview.metadata.nobleTitle') }}</td>
|
||||
<td>{{ $t('falukant.titles.' + falukantUser?.character.gender + '.' + falukantUser?.character.nobleTitle.labelTr) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.overview.metadata.money') }}</td>
|
||||
<td>{{ falukantUser?.money }}</td>
|
||||
<td>
|
||||
{{ moneyValue != null
|
||||
? moneyValue.toLocaleString(locale, { style: 'currency', currency: 'EUR' })
|
||||
: '---' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('falukant.overview.metadata.age') }}</td>
|
||||
@@ -150,7 +158,7 @@ export default {
|
||||
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 width = genderData.width || 100;
|
||||
const height = genderData.height || 100;
|
||||
return {
|
||||
backgroundImage: `url(${imageUrl})`,
|
||||
@@ -160,6 +168,13 @@ export default {
|
||||
height: `${height}px`,
|
||||
};
|
||||
},
|
||||
moneyValue() {
|
||||
const m = this.falukantUser?.money;
|
||||
return typeof m === 'string' ? parseFloat(m) : m;
|
||||
},
|
||||
locale() {
|
||||
return window.navigator.language || 'en-US';
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
await this.fetchFalukantUser();
|
||||
@@ -171,7 +186,6 @@ export default {
|
||||
}
|
||||
if (this.daemonSocket) {
|
||||
this.daemonSocket.addEventListener('message', (event) => {
|
||||
console.log('incoming event', event);
|
||||
try {
|
||||
if (event.data === "ping") return;
|
||||
const message = JSON.parse(event.data);
|
||||
@@ -182,6 +196,8 @@ export default {
|
||||
console.error('Error processing WebSocket message in FalukantOverviewView:', error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('no daemon socket');
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
@@ -281,4 +297,8 @@ export default {
|
||||
background-size: cover;
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-top: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user