editing and maintenance and user suggestions
* every user has a right of a username and a display name and has the right to change it * System information page now shows information about database user count, char count, and database schema version * more maintenance lists * show the right values in columns and fields * move similar from inside of frontend component functions to utility js when used multiple times * display help on mouse over * add more than one believe to character * make char name editable with better char info in headline * GiT Gifttoleranz value not calculated correctly * Bump backend to 0.2.3, frontend to 0.2.2
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Frontend Version Management
|
||||
|
||||
## Current Version: 0.2.1
|
||||
## Current Version: 0.2.2
|
||||
|
||||
The frontend version is managed independently from the backend.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bamort-frontend",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"private": true,
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"type": "module",
|
||||
|
||||
@@ -6,7 +6,16 @@
|
||||
<div class="form-row">
|
||||
<!-- 1. Name -->
|
||||
<div class="form-group">
|
||||
<label for="name">{{ $t('characters.basicInfo.characterName') }} {{ $t('characters.basicInfo.required') }}</label>
|
||||
<label for="name">{{ $t('characters.basicInfo.characterName') }} {{ $t('characters.basicInfo.required') }}
|
||||
<span
|
||||
class="help-icon"
|
||||
:title="$t('characters.basicInfo.characterNameHelp')"
|
||||
role="img"
|
||||
:aria-label="$t('characters.basicInfo.characterNameHelp')"
|
||||
>
|
||||
?
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
id="name"
|
||||
v-model="formData.name"
|
||||
@@ -30,7 +39,17 @@
|
||||
|
||||
<!-- 3. Glaube -->
|
||||
<div class="form-group">
|
||||
<label for="glaube">{{ $t('characters.basicInfo.religion') }}</label>
|
||||
<label for="glaube">
|
||||
{{ $t('characters.basicInfo.religion') }}
|
||||
<span
|
||||
class="help-icon"
|
||||
:title="$t('characters.basicInfo.religionHelp')"
|
||||
role="img"
|
||||
:aria-label="$t('characters.basicInfo.religionHelp')"
|
||||
>
|
||||
?
|
||||
</span>
|
||||
</label>
|
||||
<div class="belief-search">
|
||||
<input
|
||||
id="glaube"
|
||||
@@ -530,4 +549,25 @@ export default {
|
||||
color: #666;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.help-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-left: 6px;
|
||||
border: 1px solid #999;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
/*cursor: help;*/
|
||||
background: #f5f5f5;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.help-icon:hover {
|
||||
background: #e0e0e0;
|
||||
color: #222;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<button v-if="isOwner" @click="showVisibilityDialog = true" class="export-button-small" :title="$t('visibility.title')">
|
||||
{{ character.public ? '🌐' : '🔒' }}
|
||||
</button>
|
||||
<h2>{{ $t('char') }}: {{ character.name }} ({{ $t(currentView) }})</h2>
|
||||
<h2>{{ character.name }} {{$t('characters.list.class')}}: {{ character.typ }} {{$t('characters.list.grade') }}: {{ character.grad }} <!--({{ $t(currentView) }})--></h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -36,6 +36,23 @@
|
||||
<!-- Character Information -->
|
||||
<div class="character-info">
|
||||
<div class="info-section">
|
||||
<p>
|
||||
<strong>{{ $t('char') }}:</strong>
|
||||
<span
|
||||
v-if="editingProp !== 'name'"
|
||||
@dblclick="startEditProp('name', character.name)"
|
||||
class="editable-prop"
|
||||
>{{ character.name || '-' }}</span>
|
||||
<input
|
||||
v-else
|
||||
v-model="editPropValue"
|
||||
@blur="saveProp('name')"
|
||||
@keyup.enter="saveProp('name')"
|
||||
@keyup.esc="cancelEditProp"
|
||||
ref="propInput"
|
||||
class="prop-input"
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<span
|
||||
v-if="editingProp !== 'typ'"
|
||||
@@ -167,7 +184,17 @@
|
||||
@dblclick="startEditProp('glaube', character.glaube)"
|
||||
class="editable-prop"
|
||||
>{{ character.glaube || '-' }}</span>
|
||||
<select v-else v-model="editPropValue" @blur="saveProp('glaube')" @keyup.enter="saveProp('glaube')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input">
|
||||
<select
|
||||
v-else
|
||||
v-model="editPropValue"
|
||||
multiple
|
||||
@blur="saveProp('glaube')"
|
||||
@keyup.enter="saveProp('glaube')"
|
||||
@keyup.esc="cancelEditProp"
|
||||
ref="propInput"
|
||||
class="prop-input multi-select"
|
||||
size="8"
|
||||
>
|
||||
<option v-for="option in getSelectOptions('glaube')" :key="option" :value="option">{{ option }}</option>
|
||||
</select>
|
||||
</p>
|
||||
@@ -190,8 +217,12 @@
|
||||
<div v-else>Loading character data...</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* All styles moved to main.css */
|
||||
<style scoped>
|
||||
.multi-select {
|
||||
min-width: 200px;
|
||||
max-height: 180px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -259,14 +290,16 @@ export default {
|
||||
this.$emit('character-updated')
|
||||
},
|
||||
getStat(path) {
|
||||
/*
|
||||
if (path === 'git') {
|
||||
// Todo: calculate poison tolerance based on character data
|
||||
return '64!'
|
||||
}
|
||||
*/
|
||||
return path.split('.').reduce((obj, key) => obj?.[key], this.character) ?? '-'
|
||||
},
|
||||
startEdit(index, path) {
|
||||
if (path === 'git') return
|
||||
//if (path === 'git') return
|
||||
|
||||
this.editingIndex = index
|
||||
this.editValue = this.getStat(path)
|
||||
@@ -315,18 +348,22 @@ export default {
|
||||
const selectFields = ['gender', 'rasse', 'origin', 'social_class', 'glaube', 'hand', 'spezialisierung']
|
||||
if (selectFields.includes(prop)) {
|
||||
this.loadDatasheetOptions()
|
||||
type = 'select'
|
||||
type = prop === 'glaube' ? 'multi-select' : 'select'
|
||||
}
|
||||
|
||||
this.editingProp = prop
|
||||
this.editPropValue = value || ''
|
||||
if (prop === 'glaube') {
|
||||
this.editPropValue = value ? value.split(',').map(v => v.trim()).filter(Boolean) : []
|
||||
} else {
|
||||
this.editPropValue = value || ''
|
||||
}
|
||||
this.editPropType = type
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.propInput) {
|
||||
const input = Array.isArray(this.$refs.propInput) ? this.$refs.propInput[0] : this.$refs.propInput
|
||||
if (input) {
|
||||
input.focus()
|
||||
if (type !== 'select') {
|
||||
if (type !== 'select' && type !== 'multi-select') {
|
||||
input.select()
|
||||
}
|
||||
}
|
||||
@@ -335,6 +372,17 @@ export default {
|
||||
},
|
||||
async saveProp(prop) {
|
||||
if (this.editingProp === null) return
|
||||
let newValue = this.editPropValue
|
||||
if (this.editPropType === 'number') {
|
||||
newValue = parseInt(this.editPropValue)
|
||||
if (isNaN(newValue)) {
|
||||
this.cancelEditProp()
|
||||
return
|
||||
}
|
||||
} else if (this.editPropType === 'multi-select') {
|
||||
newValue = Array.isArray(this.editPropValue) ? this.editPropValue.join(', ') : ''
|
||||
}
|
||||
|
||||
// Update the character object directly
|
||||
const pathParts = prop.split('.')
|
||||
let obj = this.character
|
||||
@@ -345,19 +393,11 @@ export default {
|
||||
obj = obj[pathParts[i]]
|
||||
}
|
||||
obj[pathParts[pathParts.length - 1]] = newValue
|
||||
|
||||
|
||||
// Save to backend
|
||||
await API.put(`/api/characters/${this.character.id}`, this.character)
|
||||
|
||||
this.$emit('character-updated',this.character)
|
||||
let newValue = this.editPropValue
|
||||
if (this.editPropType === 'number') {
|
||||
newValue = parseInt(this.editPropValue)
|
||||
if (isNaN(newValue)) {
|
||||
this.cancelEditProp()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.$emit('character-updated', this.character)
|
||||
|
||||
try {
|
||||
this.$emit('update-property', prop, newValue)
|
||||
|
||||
@@ -34,6 +34,11 @@ import SpellView from "./maintenance/SpellView.vue"; // Component for character
|
||||
import EquipmentView from "./maintenance/EquipmentView.vue"; // Component for character equipment
|
||||
import WeaponView from "./maintenance/WeaponView.vue"; // Component for character history
|
||||
import WeaponSkillView from "./maintenance/WeaponSkillView.vue"; // Component for character equipment
|
||||
import BelieveView from "./maintenance/BelieveView.vue"; // Component for believes maintenance
|
||||
import GameSystemView from "./maintenance/GameSystemView.vue";
|
||||
import LitSourceView from "./maintenance/LitSourceView.vue";
|
||||
import MiscLookupView from "./maintenance/MiscLookupView.vue";
|
||||
import SkillImprovementCostView from "./maintenance/SkillImprovementCostView.vue";
|
||||
|
||||
|
||||
export default {
|
||||
@@ -45,6 +50,11 @@ export default {
|
||||
EquipmentView,
|
||||
WeaponView,
|
||||
WeaponSkillView,
|
||||
BelieveView,
|
||||
GameSystemView,
|
||||
LitSourceView,
|
||||
MiscLookupView,
|
||||
SkillImprovementCostView,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -63,10 +73,15 @@ export default {
|
||||
lastView: "SkillView",
|
||||
menus: [
|
||||
{ id: 0, name: "skill", component: "SkillView" },
|
||||
{ id: 2, name: "spell", component: "SpellView" },
|
||||
{ id: 3, name: "equipment", component: "EquipmentView" },
|
||||
{ id: 1, name: "weapon", component: "WeaponView" },
|
||||
{ id: 1, name: "weaponskill", component: "WeaponSkillView" },
|
||||
{ id: 1, name: "spell", component: "SpellView" },
|
||||
{ id: 2, name: "equipment", component: "EquipmentView" },
|
||||
{ id: 3, name: "weapon", component: "WeaponView" },
|
||||
{ id: 4, name: "weaponskill", component: "WeaponSkillView" },
|
||||
{ id: 5, name: "believe", component: "BelieveView" },
|
||||
{ id: 6, name: "gamesystem", component: "GameSystemView" },
|
||||
{ id: 7, name: "litsource", component: "LitSourceView" },
|
||||
{ id: 8, name: "misc", component: "MiscLookupView" },
|
||||
{ id: 9, name: "skillimprovement", component: "SkillImprovementCostView" },
|
||||
|
||||
],
|
||||
};
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
<div v-else>
|
||||
<div class="page-header">
|
||||
<h2>Neues Passwort setzen</h2>
|
||||
<p style="color: #666; font-size: 0.9em; margin-top: 10px;" v-if="userInfo.username">
|
||||
Für Benutzer: <strong>{{ userInfo.username }}</strong>
|
||||
<p style="color: #666; font-size: 0.9em; margin-top: 10px;" v-if="userInfo.display_name || userInfo.username">
|
||||
Für Benutzer: <strong>{{ userInfo.display_name || userInfo.username }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -155,6 +155,7 @@ export default {
|
||||
this.isValidToken = response.data.valid
|
||||
this.userInfo = {
|
||||
username: response.data.username,
|
||||
display_name: response.data.display_name,
|
||||
expires: response.data.expires
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
@click="toggleUser(user.user_id)"
|
||||
>
|
||||
<div class="user-info">
|
||||
<span class="user-name">{{ user.username }}</span>
|
||||
<span class="user-name">{{ user.display_name || user.username }}</span>
|
||||
<span class="user-email">{{ user.email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,6 +161,7 @@ export default {
|
||||
}
|
||||
const query = this.searchQuery.toLowerCase()
|
||||
return users.filter(user =>
|
||||
(user.display_name && user.display_name.toLowerCase().includes(query)) ||
|
||||
user.username.toLowerCase().includes(query) ||
|
||||
user.email.toLowerCase().includes(query)
|
||||
)
|
||||
@@ -228,7 +229,7 @@ export default {
|
||||
|
||||
getUserName(userId) {
|
||||
const user = this.availableUsers.find(u => u.user_id === userId)
|
||||
return user ? user.username : 'Unknown'
|
||||
return user ? (user.display_name || user.username) : 'Unknown'
|
||||
},
|
||||
|
||||
getUserEmail(userId) {
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<div class="header-section">
|
||||
<h2>{{ $t('maintenance') }} - {{ $t('believe.title') }}</h2>
|
||||
<div class="search-box">
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
type="text"
|
||||
:placeholder="$t('search')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error-box">{{ error }}</div>
|
||||
|
||||
<div class="cd-view">
|
||||
<div class="cd-list">
|
||||
<table class="cd-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cd-table-header">{{ $t('believe.id') }}</th>
|
||||
<th class="cd-table-header">{{ $t('believe.name') }}</th>
|
||||
<th class="cd-table-header">{{ $t('believe.description') }}</th>
|
||||
<th class="cd-table-header">{{ $t('believe.source') }}</th>
|
||||
<th class="cd-table-header">{{ $t('believe.page') }}</th>
|
||||
<th class="cd-table-header">{{ $t('believe.system') }}</th>
|
||||
<th class="cd-table-header"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="isLoading">
|
||||
<td colspan="7">{{ $t('common.loading') }}</td>
|
||||
</tr>
|
||||
|
||||
<template v-for="believe in filteredBelieves" :key="believe.id">
|
||||
<tr v-if="editingId !== believe.id">
|
||||
<td>{{ believe.id }}</td>
|
||||
<td>{{ believe.name }}</td>
|
||||
<td>{{ believe.beschreibung || '-' }}</td>
|
||||
<td>{{ getSourceCode(believe.source_id) || '-' }}</td>
|
||||
<td>{{ believe.page_number || '-' }}</td>
|
||||
<td>{{ getSystemCodeById(believe.game_system_id, believe.game_system) || '-' }}</td>
|
||||
<td>
|
||||
<button @click="startEdit(believe)">{{ $t('believe.edit') }}</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>{{ believe.id }}</td>
|
||||
<td colspan="6">
|
||||
<div class="edit-form">
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('believe.name') }}</label>
|
||||
<input v-model="editedItem.name" />
|
||||
</div>
|
||||
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('believe.description') }}</label>
|
||||
<input v-model="editedItem.beschreibung" />
|
||||
</div>
|
||||
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('believe.source') }}</label>
|
||||
<select v-model="editedItem.sourceCode">
|
||||
<option value="">-</option>
|
||||
<option v-for="source in sources" :key="source.id" :value="source.code">
|
||||
{{ source.code }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<label class="inline-label">{{ $t('believe.page') }}</label>
|
||||
<input v-model.number="editedItem.page_number" type="number" min="0" />
|
||||
</div>
|
||||
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('believe.system') }}</label>
|
||||
<select v-model.number="selectedSystemId">
|
||||
<option value="">-</option>
|
||||
<option v-for="system in systemOptions" :key="system.id" :value="system.id">
|
||||
{{ system.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="edit-actions">
|
||||
<button
|
||||
class="btn-primary"
|
||||
:disabled="isSaving"
|
||||
@click="saveEdit"
|
||||
>
|
||||
<span v-if="!isSaving">{{ $t('believe.save') }}</span>
|
||||
<span v-else>{{ $t('believe.saving') }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="btn-cancel"
|
||||
:disabled="isSaving"
|
||||
@click="cancelEdit"
|
||||
>
|
||||
{{ $t('believe.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Uses shared maintenance styles */
|
||||
.error-box {
|
||||
margin: 10px 0;
|
||||
padding: 10px 12px;
|
||||
background: #ffe3e3;
|
||||
color: #8a1c1c;
|
||||
border: 1px solid #f5c2c2;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.edit-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.edit-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.edit-row label {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.edit-row input,
|
||||
.edit-row select {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.edit-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.inline-label {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import API from '../../utils/api'
|
||||
import {
|
||||
findSystemIdByCode,
|
||||
getSourceCode,
|
||||
getSystemCodeById,
|
||||
loadGameSystems as fetchGameSystems,
|
||||
buildSystemOptions,
|
||||
} from '../../utils/maintenanceGameSystems'
|
||||
|
||||
export default {
|
||||
name: "BelieveView",
|
||||
props: {
|
||||
mdata: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
believes: [],
|
||||
sources: [],
|
||||
editingId: null,
|
||||
editedItem: null,
|
||||
gameSystems: [],
|
||||
selectedSystemId: null,
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
error: '',
|
||||
searchTerm: ''
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadGameSystems()
|
||||
await this.loadBelieves()
|
||||
},
|
||||
computed: {
|
||||
filteredBelieves() {
|
||||
const term = this.searchTerm.trim().toLowerCase()
|
||||
const filtered = term
|
||||
? this.believes.filter(believe => {
|
||||
const name = (believe.name || '').toLowerCase()
|
||||
const desc = (believe.beschreibung || '').toLowerCase()
|
||||
return name.includes(term) || desc.includes(term)
|
||||
})
|
||||
: this.believes
|
||||
|
||||
return [...filtered].sort((a, b) => (a.name || '').localeCompare(b.name || ''))
|
||||
},
|
||||
systemOptions() {
|
||||
return buildSystemOptions(this.gameSystems)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadGameSystems() {
|
||||
try {
|
||||
this.gameSystems = await fetchGameSystems()
|
||||
} catch (err) {
|
||||
console.error('Failed to load game systems:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
}
|
||||
},
|
||||
async loadBelieves() {
|
||||
this.isLoading = true
|
||||
this.error = ''
|
||||
try {
|
||||
const response = await API.get('/api/maintenance/gsm-believes')
|
||||
this.believes = response.data?.believes || []
|
||||
this.sources = response.data?.sources || []
|
||||
} catch (err) {
|
||||
console.error('Failed to load believes:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
getSourceCode(sourceId) {
|
||||
return getSourceCode(this.sources, sourceId)
|
||||
},
|
||||
getSystemCodeById(systemId, fallback = '') {
|
||||
return getSystemCodeById(this.gameSystems, systemId, fallback)
|
||||
},
|
||||
startEdit(believe) {
|
||||
this.editingId = believe.id
|
||||
this.editedItem = {
|
||||
...believe,
|
||||
sourceCode: this.getSourceCode(believe.source_id)
|
||||
}
|
||||
this.selectedSystemId = believe.game_system_id ?? this.findSystemIdByCode(believe.game_system)
|
||||
},
|
||||
cancelEdit() {
|
||||
this.editingId = null
|
||||
this.editedItem = null
|
||||
this.selectedSystemId = null
|
||||
},
|
||||
findSystemIdByCode(code) {
|
||||
return findSystemIdByCode(this.gameSystems, code)
|
||||
},
|
||||
async saveEdit() {
|
||||
if (!this.editedItem || !this.editingId) {
|
||||
return
|
||||
}
|
||||
|
||||
const trimmedName = (this.editedItem.name || '').trim()
|
||||
if (!trimmedName) {
|
||||
alert(this.$t('believe.nameRequired'))
|
||||
return
|
||||
}
|
||||
|
||||
const selectedSource = this.sources.find(src => src.code === this.editedItem.sourceCode)
|
||||
const selectedSystem = this.gameSystems.find(gs => gs.id === this.selectedSystemId)
|
||||
|
||||
const payload = {
|
||||
name: trimmedName,
|
||||
beschreibung: this.editedItem.beschreibung || '',
|
||||
source_id: selectedSource ? selectedSource.id : null,
|
||||
page_number: this.editedItem.page_number || 0,
|
||||
game_system_id: selectedSystem ? selectedSystem.id : null,
|
||||
game_system: selectedSystem ? selectedSystem.code : '',
|
||||
}
|
||||
|
||||
this.isSaving = true
|
||||
try {
|
||||
const response = await API.put(`/api/maintenance/gsm-believes/${this.editingId}`, payload)
|
||||
const updated = response.data
|
||||
const sourceCode = this.getSourceCode(updated.source_id)
|
||||
const gameSystemCode = selectedSystem ? selectedSystem.code : updated.game_system
|
||||
const gameSystemId = selectedSystem ? selectedSystem.id : (updated.game_system_id ?? null)
|
||||
|
||||
const idx = this.believes.findIndex(b => b.id === this.editingId)
|
||||
if (idx !== -1) {
|
||||
this.believes.splice(idx, 1, { ...updated, source_code: sourceCode, game_system: gameSystemCode, game_system_id: gameSystemId })
|
||||
}
|
||||
|
||||
this.cancelEdit()
|
||||
} catch (err) {
|
||||
console.error('Failed to save believe:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isSaving = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -62,7 +62,7 @@
|
||||
<td>{{ dtaItem.beschreibung || '-' }}</td>
|
||||
<td>{{ formatQuelle(dtaItem) }}</td>
|
||||
<td><input type="checkbox" :checked="dtaItem.personal_item" disabled /></td>
|
||||
<td>{{ dtaItem.system || 'midgard' }}</td>
|
||||
<td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.system || 'midgard') }}</td>
|
||||
<td>
|
||||
<button @click="startEdit(index)">Edit</button>
|
||||
</td>
|
||||
@@ -115,7 +115,12 @@
|
||||
</div>
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('equipment.system') }}:</label>
|
||||
<input v-model="editedItem.system" style="width:100px;" />
|
||||
<select v-model.number="selectedSystemId" style="width:140px;">
|
||||
<option value="">-</option>
|
||||
<option v-for="system in systemOptions" :key="system.id" :value="system.id">
|
||||
{{ system.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -180,6 +185,13 @@
|
||||
|
||||
<script>
|
||||
import API from '../../utils/api'
|
||||
import {
|
||||
findSystemIdByCode,
|
||||
getSourceCode,
|
||||
getSystemCodeById,
|
||||
loadGameSystems as fetchGameSystems,
|
||||
buildSystemOptions,
|
||||
} from '../../utils/maintenanceGameSystems'
|
||||
export default {
|
||||
name: "EquipmentView",
|
||||
props: {
|
||||
@@ -202,11 +214,16 @@ export default {
|
||||
filterPersonalItem: '',
|
||||
filterQuelle: '',
|
||||
enhancedEquipment: [],
|
||||
availableSources: []
|
||||
availableSources: [],
|
||||
gameSystems: [],
|
||||
selectedSystemId: null
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadEnhancedEquipment()
|
||||
await Promise.all([
|
||||
this.loadGameSystems(),
|
||||
this.loadEnhancedEquipment()
|
||||
])
|
||||
},
|
||||
computed: {
|
||||
availableQuellen() {
|
||||
@@ -266,9 +283,19 @@ export default {
|
||||
? aValue.localeCompare(bValue)
|
||||
: bValue.localeCompare(aValue);
|
||||
});
|
||||
},
|
||||
systemOptions() {
|
||||
return buildSystemOptions(this.gameSystems)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadGameSystems() {
|
||||
try {
|
||||
this.gameSystems = await fetchGameSystems()
|
||||
} catch (error) {
|
||||
console.error('Failed to load game systems:', error)
|
||||
}
|
||||
},
|
||||
async loadEnhancedEquipment() {
|
||||
try {
|
||||
const response = await API.get('/api/maintenance/equipment-enhanced')
|
||||
@@ -284,17 +311,21 @@ export default {
|
||||
...equipment,
|
||||
sourceCode: this.getSourceCode(equipment.source_id)
|
||||
}
|
||||
this.selectedSystemId = equipment.game_system_id ?? this.findSystemIdByCode(equipment.system)
|
||||
this.editingIndex = index
|
||||
},
|
||||
async saveEdit(index) {
|
||||
try {
|
||||
// Find source ID from code
|
||||
const source = this.availableSources.find(s => s.code === this.editedItem.sourceCode)
|
||||
const selectedSystem = this.gameSystems.find(gs => gs.id === this.selectedSystemId)
|
||||
|
||||
const updateData = {
|
||||
...this.editedItem,
|
||||
source_id: source ? source.id : null,
|
||||
page_number: this.editedItem.page_number || 0
|
||||
page_number: this.editedItem.page_number || 0,
|
||||
system: selectedSystem ? selectedSystem.code : (this.editedItem.system || ''),
|
||||
game_system_id: selectedSystem ? selectedSystem.id : (this.editedItem.game_system_id ?? null)
|
||||
}
|
||||
|
||||
const response = await API.put(
|
||||
@@ -310,6 +341,7 @@ export default {
|
||||
|
||||
this.editingIndex = -1
|
||||
this.editedItem = null
|
||||
this.selectedSystemId = null
|
||||
} catch (error) {
|
||||
console.error('Failed to save equipment:', error)
|
||||
alert('Failed to save equipment: ' + (error.response?.data?.error || error.message))
|
||||
@@ -318,6 +350,10 @@ export default {
|
||||
cancelEdit() {
|
||||
this.editingIndex = -1;
|
||||
this.editedItem = null;
|
||||
this.selectedSystemId = null;
|
||||
},
|
||||
findSystemIdByCode(code) {
|
||||
return findSystemIdByCode(this.gameSystems, code)
|
||||
},
|
||||
sortBy(field) {
|
||||
if (this.sortField === field) {
|
||||
@@ -343,15 +379,16 @@ export default {
|
||||
return equipment.quelle || '-'
|
||||
},
|
||||
getSourceCode(sourceId) {
|
||||
if (!sourceId || !this.availableSources.length) return ''
|
||||
const source = this.availableSources.find(s => s.id === sourceId)
|
||||
return source ? source.code : ''
|
||||
return getSourceCode(this.availableSources, sourceId)
|
||||
},
|
||||
clearFilters() {
|
||||
this.searchTerm = ''
|
||||
this.filterPersonalItem = ''
|
||||
this.filterQuelle = ''
|
||||
},
|
||||
getSystemCodeById(systemId, fallback = '') {
|
||||
return getSystemCodeById(this.gameSystems, systemId, fallback)
|
||||
},
|
||||
async handleEquipmentUpdate({ index, equipment }) {
|
||||
try {
|
||||
const response = await API.put(
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<div class="header-section">
|
||||
<h2>{{ $t('maintenance') }} - {{ $t('gamesystem.title') }}</h2>
|
||||
<div class="search-box">
|
||||
<input v-model="searchTerm" type="text" :placeholder="$t('search')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error-box">{{ error }}</div>
|
||||
|
||||
<div class="cd-view">
|
||||
<div class="cd-list">
|
||||
<table class="cd-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cd-table-header">{{ $t('gamesystem.id') }}</th>
|
||||
<th class="cd-table-header">{{ $t('gamesystem.code') }}</th>
|
||||
<th class="cd-table-header">{{ $t('gamesystem.name') }}</th>
|
||||
<th class="cd-table-header">{{ $t('gamesystem.description') }}</th>
|
||||
<th class="cd-table-header">{{ $t('gamesystem.active') }}</th>
|
||||
<th class="cd-table-header"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="isLoading">
|
||||
<td colspan="6">{{ $t('common.loading') }}</td>
|
||||
</tr>
|
||||
|
||||
<template v-for="gs in filteredSystems" :key="gs.id">
|
||||
<tr v-if="editingId !== gs.id">
|
||||
<td>{{ gs.id }}</td>
|
||||
<td>{{ gs.code }}</td>
|
||||
<td>{{ gs.name }}</td>
|
||||
<td>{{ gs.description || '-' }}</td>
|
||||
<td><input type="checkbox" :checked="gs.is_active" disabled /></td>
|
||||
<td><button @click="startEdit(gs)">{{ $t('gamesystem.edit') }}</button></td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>{{ gs.id }}</td>
|
||||
<td>{{ gs.code }}</td>
|
||||
<td colspan="4">
|
||||
<div class="edit-form">
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('gamesystem.name') }}</label>
|
||||
<input v-model="editedItem.name" />
|
||||
</div>
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('gamesystem.description') }}</label>
|
||||
<input v-model="editedItem.description" />
|
||||
</div>
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('gamesystem.active') }}</label>
|
||||
<input type="checkbox" v-model="editedItem.is_active" />
|
||||
</div>
|
||||
<div class="edit-actions">
|
||||
<button class="btn-primary" :disabled="isSaving" @click="saveEdit">
|
||||
<span v-if="!isSaving">{{ $t('gamesystem.save') }}</span>
|
||||
<span v-else>{{ $t('gamesystem.saving') }}</span>
|
||||
</button>
|
||||
<button class="btn-cancel" :disabled="isSaving" @click="cancelEdit">
|
||||
{{ $t('gamesystem.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.error-box {
|
||||
margin: 10px 0;
|
||||
padding: 10px 12px;
|
||||
background: #ffe3e3;
|
||||
color: #8a1c1c;
|
||||
border: 1px solid #f5c2c2;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.edit-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.edit-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
.edit-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import API from '../../utils/api'
|
||||
import { loadGameSystems as fetchGameSystems } from '../../utils/maintenanceGameSystems'
|
||||
|
||||
export default {
|
||||
name: 'GameSystemView',
|
||||
data() {
|
||||
return {
|
||||
systems: [],
|
||||
editingId: null,
|
||||
editedItem: null,
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
error: '',
|
||||
searchTerm: '',
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadSystems()
|
||||
},
|
||||
computed: {
|
||||
filteredSystems() {
|
||||
const term = this.searchTerm.trim().toLowerCase()
|
||||
const list = term
|
||||
? this.systems.filter(gs =>
|
||||
(gs.name || '').toLowerCase().includes(term) ||
|
||||
(gs.code || '').toLowerCase().includes(term)
|
||||
)
|
||||
: this.systems
|
||||
return [...list].sort((a, b) => (a.code || '').localeCompare(b.code || ''))
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async loadSystems() {
|
||||
this.isLoading = true
|
||||
this.error = ''
|
||||
try {
|
||||
this.systems = await fetchGameSystems()
|
||||
} catch (err) {
|
||||
console.error('Failed to load game systems:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
startEdit(gs) {
|
||||
this.editingId = gs.id
|
||||
this.editedItem = { ...gs }
|
||||
},
|
||||
cancelEdit() {
|
||||
this.editingId = null
|
||||
this.editedItem = null
|
||||
},
|
||||
async saveEdit() {
|
||||
if (!this.editedItem) return
|
||||
const payload = {
|
||||
name: this.editedItem.name || '',
|
||||
description: this.editedItem.description || '',
|
||||
is_active: !!this.editedItem.is_active,
|
||||
}
|
||||
this.isSaving = true
|
||||
try {
|
||||
const resp = await API.put(`/api/maintenance/game-systems/${this.editingId}`, payload)
|
||||
const idx = this.systems.findIndex(s => s.id === this.editingId)
|
||||
if (idx !== -1) this.systems.splice(idx, 1, resp.data)
|
||||
this.cancelEdit()
|
||||
} catch (err) {
|
||||
console.error('Failed to save game system:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isSaving = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<div class="header-section">
|
||||
<h2>{{ $t('maintenance') }} - {{ $t('litsource.title') }}</h2>
|
||||
<div class="search-box">
|
||||
<input v-model="searchTerm" type="text" :placeholder="$t('search')" />
|
||||
</div>
|
||||
<div class="search-box">
|
||||
<select v-model.number="selectedSystemId" @change="handleGameSystemChange">
|
||||
<option value="">{{ $t('gamesystem.title') }}</option>
|
||||
<option v-for="system in systemOptions" :key="system.id" :value="system.id">
|
||||
{{ system.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error-box">{{ error }}</div>
|
||||
|
||||
<div class="cd-view">
|
||||
<div class="cd-list">
|
||||
<table class="cd-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cd-table-header">{{ $t('litsource.id') }}</th>
|
||||
<th class="cd-table-header">{{ $t('litsource.code') }}</th>
|
||||
<th class="cd-table-header">{{ $t('litsource.name') }}</th>
|
||||
<th class="cd-table-header">{{ $t('litsource.fullName') }}</th>
|
||||
<th class="cd-table-header">{{ $t('litsource.edition') }}</th>
|
||||
<th class="cd-table-header">{{ $t('litsource.publisher') }}</th>
|
||||
<th class="cd-table-header">{{ $t('litsource.year') }}</th>
|
||||
<th class="cd-table-header">{{ $t('litsource.active') }}</th>
|
||||
<th class="cd-table-header">{{ $t('litsource.core') }}</th>
|
||||
<th class="cd-table-header"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="isLoading">
|
||||
<td colspan="10">{{ $t('common.loading') }}</td>
|
||||
</tr>
|
||||
|
||||
<template v-for="src in filteredSources" :key="src.id">
|
||||
<tr v-if="editingId !== src.id">
|
||||
<td>{{ src.id }}</td>
|
||||
<td>{{ src.code }}</td>
|
||||
<td>{{ src.name }}</td>
|
||||
<td>{{ src.full_name }}</td>
|
||||
<td>{{ src.edition }}</td>
|
||||
<td>{{ src.publisher }}</td>
|
||||
<td>{{ src.publish_year }}</td>
|
||||
<td><input type="checkbox" :checked="src.is_active" disabled /></td>
|
||||
<td><input type="checkbox" :checked="src.is_core" disabled /></td>
|
||||
<td><button @click="startEdit(src)">{{ $t('litsource.edit') }}</button></td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>{{ src.id }}</td>
|
||||
<td>{{ src.code }}</td>
|
||||
<td colspan="8">
|
||||
<div class="edit-form">
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('litsource.name') }}</label>
|
||||
<input v-model="editedItem.name" />
|
||||
</div>
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('litsource.fullName') }}</label>
|
||||
<input v-model="editedItem.full_name" />
|
||||
</div>
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('litsource.edition') }}</label>
|
||||
<input v-model="editedItem.edition" />
|
||||
<label class="inline-label">{{ $t('litsource.publisher') }}</label>
|
||||
<input v-model="editedItem.publisher" />
|
||||
<label class="inline-label">{{ $t('litsource.year') }}</label>
|
||||
<input v-model.number="editedItem.publish_year" type="number" />
|
||||
</div>
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('litsource.description') }}</label>
|
||||
<input v-model="editedItem.description" />
|
||||
</div>
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('litsource.active') }}</label>
|
||||
<input type="checkbox" v-model="editedItem.is_active" />
|
||||
<label class="inline-label">{{ $t('litsource.core') }}</label>
|
||||
<input type="checkbox" v-model="editedItem.is_core" />
|
||||
</div>
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('gamesystem.title') }}</label>
|
||||
<select v-model.number="selectedSystemId">
|
||||
<option value="">-</option>
|
||||
<option v-for="system in systemOptions" :key="system.id" :value="system.id">
|
||||
{{ system.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="edit-actions">
|
||||
<button class="btn-primary" :disabled="isSaving" @click="saveEdit">
|
||||
<span v-if="!isSaving">{{ $t('litsource.save') }}</span>
|
||||
<span v-else>{{ $t('litsource.saving') }}</span>
|
||||
</button>
|
||||
<button class="btn-cancel" :disabled="isSaving" @click="cancelEdit">
|
||||
{{ $t('litsource.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.error-box {
|
||||
margin: 10px 0;
|
||||
padding: 10px 12px;
|
||||
background: #ffe3e3;
|
||||
color: #8a1c1c;
|
||||
border: 1px solid #f5c2c2;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.edit-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.edit-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
.edit-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.inline-label {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import API from '../../utils/api'
|
||||
import {
|
||||
buildGameSystemParams,
|
||||
findSystemById,
|
||||
findSystemIdByCode,
|
||||
loadGameSystems as fetchGameSystems,
|
||||
buildSystemOptions,
|
||||
} from '../../utils/maintenanceGameSystems'
|
||||
|
||||
export default {
|
||||
name: 'LitSourceView',
|
||||
data() {
|
||||
return {
|
||||
gameSystems: [],
|
||||
currentGameSystem: null,
|
||||
selectedSystemId: null,
|
||||
sources: [],
|
||||
editingId: null,
|
||||
editedItem: null,
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
error: '',
|
||||
searchTerm: '',
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.initialize()
|
||||
},
|
||||
computed: {
|
||||
filteredSources() {
|
||||
const term = this.searchTerm.trim().toLowerCase()
|
||||
const list = term
|
||||
? this.sources.filter(src =>
|
||||
(src.name || '').toLowerCase().includes(term) ||
|
||||
(src.code || '').toLowerCase().includes(term)
|
||||
)
|
||||
: this.sources
|
||||
return [...list].sort((a, b) => (a.code || '').localeCompare(b.code || ''))
|
||||
},
|
||||
systemOptions() {
|
||||
return buildSystemOptions(this.gameSystems)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async initialize() {
|
||||
this.error = ''
|
||||
await this.loadGameSystems()
|
||||
if (this.currentGameSystem) {
|
||||
await this.loadSources()
|
||||
}
|
||||
},
|
||||
async loadGameSystems() {
|
||||
try {
|
||||
const systems = await fetchGameSystems()
|
||||
this.gameSystems = systems
|
||||
const active = systems.find(s => s.is_active)
|
||||
this.currentGameSystem = active || systems[0] || null
|
||||
this.selectedSystemId = this.currentGameSystem ? this.currentGameSystem.id : null
|
||||
} catch (err) {
|
||||
console.error('Failed to load game systems:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
}
|
||||
},
|
||||
async loadSources() {
|
||||
this.isLoading = true
|
||||
this.error = ''
|
||||
try {
|
||||
const params = buildGameSystemParams(this.currentGameSystem)
|
||||
const resp = await API.get('/api/maintenance/gsm-lit-sources', { params })
|
||||
this.sources = resp.data?.sources || []
|
||||
} catch (err) {
|
||||
console.error('Failed to load sources:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
startEdit(src) {
|
||||
this.editingId = src.id
|
||||
this.editedItem = { ...src }
|
||||
this.selectedSystemId = src.game_system_id
|
||||
|| this.findSystemIdByCode(src.game_system)
|
||||
|| this.currentGameSystem?.id
|
||||
|| null
|
||||
},
|
||||
cancelEdit() {
|
||||
this.editingId = null
|
||||
this.editedItem = null
|
||||
this.selectedSystemId = this.currentGameSystem ? this.currentGameSystem.id : null
|
||||
},
|
||||
handleGameSystemChange() {
|
||||
const target = this.findSystemById(this.selectedSystemId)
|
||||
this.currentGameSystem = target || this.currentGameSystem
|
||||
if (this.currentGameSystem) {
|
||||
this.loadSources()
|
||||
}
|
||||
},
|
||||
findSystemById(id) {
|
||||
return findSystemById(this.gameSystems, id)
|
||||
},
|
||||
findSystemIdByCode(code) {
|
||||
return findSystemIdByCode(this.gameSystems, code)
|
||||
},
|
||||
async saveEdit() {
|
||||
if (!this.editedItem) return
|
||||
const targetSystem = this.findSystemById(this.selectedSystemId) || this.currentGameSystem
|
||||
const payload = {
|
||||
name: this.editedItem.name || '',
|
||||
full_name: this.editedItem.full_name || '',
|
||||
edition: this.editedItem.edition || '',
|
||||
publisher: this.editedItem.publisher || '',
|
||||
publish_year: this.editedItem.publish_year || 0,
|
||||
description: this.editedItem.description || '',
|
||||
is_active: !!this.editedItem.is_active,
|
||||
is_core: !!this.editedItem.is_core,
|
||||
game_system_id: targetSystem ? targetSystem.id : null,
|
||||
game_system: targetSystem ? targetSystem.code : '',
|
||||
}
|
||||
this.isSaving = true
|
||||
try {
|
||||
const params = buildGameSystemParams(targetSystem)
|
||||
const resp = await API.put(`/api/maintenance/gsm-lit-sources/${this.editingId}`, payload, { params })
|
||||
const idx = this.sources.findIndex(s => s.id === this.editingId)
|
||||
if (idx !== -1) this.sources.splice(idx, 1, resp.data)
|
||||
this.cancelEdit()
|
||||
} catch (err) {
|
||||
console.error('Failed to save source:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isSaving = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<div class="header-section">
|
||||
<h2>{{ $t('maintenance') }} - {{ $t('misc.title') }}</h2>
|
||||
<div class="search-box">
|
||||
<input v-model="searchTerm" type="text" :placeholder="$t('search')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error-box">{{ error }}</div>
|
||||
|
||||
<div class="cd-view">
|
||||
<div class="cd-list">
|
||||
<table class="cd-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cd-table-header">{{ $t('misc.id') }}</th>
|
||||
<th class="cd-table-header">{{ $t('misc.key') }}</th>
|
||||
<th class="cd-table-header">{{ $t('misc.value') }}</th>
|
||||
<th class="cd-table-header">{{ $t('misc.source') }}</th>
|
||||
<th class="cd-table-header">{{ $t('misc.page') }}</th>
|
||||
<th class="cd-table-header">{{ $t('misc.system') }}</th>
|
||||
<th class="cd-table-header"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="isLoading">
|
||||
<td colspan="7">{{ $t('common.loading') }}</td>
|
||||
</tr>
|
||||
|
||||
<template v-for="item in filteredItems" :key="item.id">
|
||||
<tr v-if="editingId !== item.id">
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item.key }}</td>
|
||||
<td>{{ item.value }}</td>
|
||||
<td>{{ sourceCodeFor(item.source_id) }}</td>
|
||||
<td>{{ item.page_number || '-' }}</td>
|
||||
<td>{{ systemCodeFor(item.game_system_id, item.game_system) || '-' }}</td>
|
||||
<td><button @click="startEdit(item)">{{ $t('misc.edit') }}</button></td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>{{ item.id }}</td>
|
||||
<td colspan="6">
|
||||
<div class="edit-form">
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('misc.key') }}</label>
|
||||
<select v-model="editedItem.key">
|
||||
<option :value="''">-</option>
|
||||
<option v-for="key in keyOptionsWithCurrent" :key="key" :value="key">{{ key }}</option>
|
||||
</select>
|
||||
<label class="inline-label">{{ $t('misc.value') }}</label>
|
||||
<input v-model="editedItem.value" />
|
||||
</div>
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('misc.source') }}</label>
|
||||
<select v-model.number="editedItem.source_id">
|
||||
<option :value="null">-</option>
|
||||
<option v-for="src in sourceOptions" :key="src.id" :value="src.id">
|
||||
{{ src.label }}
|
||||
</option>
|
||||
</select>
|
||||
<label class="inline-label">{{ $t('misc.page') }}</label>
|
||||
<input v-model.number="editedItem.page_number" type="number" min="0" />
|
||||
</div>
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('misc.system') }}</label>
|
||||
<select v-model.number="selectedSystemId">
|
||||
<option v-for="sys in systemOptions" :key="sys.id" :value="sys.id">{{ sys.label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="edit-actions">
|
||||
<button class="btn-primary" :disabled="isSaving" @click="saveEdit">
|
||||
<span v-if="!isSaving">{{ $t('misc.save') }}</span>
|
||||
<span v-else>{{ $t('misc.saving') }}</span>
|
||||
</button>
|
||||
<button class="btn-cancel" :disabled="isSaving" @click="cancelEdit">
|
||||
{{ $t('misc.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.error-box {
|
||||
margin: 10px 0;
|
||||
padding: 10px 12px;
|
||||
background: #ffe3e3;
|
||||
color: #8a1c1c;
|
||||
border: 1px solid #f5c2c2;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.edit-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.edit-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
.edit-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.inline-label {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import API from '../../utils/api'
|
||||
import {
|
||||
buildGameSystemParams,
|
||||
findSystemById,
|
||||
loadGameSystems as fetchGameSystems,
|
||||
systemCodeFor as resolveSystemCode,
|
||||
buildSystemOptions,
|
||||
} from '../../utils/maintenanceGameSystems'
|
||||
|
||||
export default {
|
||||
name: 'MiscLookupView',
|
||||
data() {
|
||||
return {
|
||||
items: [],
|
||||
gameSystems: [],
|
||||
currentGameSystem: null,
|
||||
sources: [],
|
||||
editingId: null,
|
||||
editedItem: null,
|
||||
selectedSystemId: null,
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
error: '',
|
||||
searchTerm: '',
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.initialize()
|
||||
},
|
||||
computed: {
|
||||
filteredItems() {
|
||||
const term = this.searchTerm.trim().toLowerCase()
|
||||
const list = term
|
||||
? this.items.filter(it =>
|
||||
(it.key || '').toLowerCase().includes(term) ||
|
||||
(it.value || '').toLowerCase().includes(term)
|
||||
)
|
||||
: this.items
|
||||
return [...list].sort((a, b) => (a.key || '').localeCompare(b.key || ''))
|
||||
},
|
||||
keyOptions() {
|
||||
const set = new Set()
|
||||
this.items.forEach(it => {
|
||||
if (it.key) set.add(it.key)
|
||||
})
|
||||
return Array.from(set.values()).sort()
|
||||
},
|
||||
keyOptionsWithCurrent() {
|
||||
const set = new Set(this.keyOptions)
|
||||
if (this.editedItem?.key) set.add(String(this.editedItem.key))
|
||||
return Array.from(set.values()).sort()
|
||||
},
|
||||
systemOptions() {
|
||||
const labelBuilder = system => {
|
||||
const code = system.code || ''
|
||||
const name = system.name || ''
|
||||
if (code && name) return `${code} - ${name}`.trim()
|
||||
return code || name || String(system.id ?? '')
|
||||
}
|
||||
return buildSystemOptions(this.gameSystems, labelBuilder)
|
||||
},
|
||||
sourceMap() {
|
||||
const map = new Map()
|
||||
this.sources.forEach(src => {
|
||||
map.set(src.id, src.code || src.name || src.id)
|
||||
})
|
||||
return map
|
||||
},
|
||||
sourceOptions() {
|
||||
return this.sources.map(src => ({
|
||||
id: src.id,
|
||||
label: src.code ? `${src.code} - ${src.name || ''}`.trim() : src.name || src.id,
|
||||
}))
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async initialize() {
|
||||
this.error = ''
|
||||
await this.loadGameSystems()
|
||||
if (!this.currentGameSystem) return
|
||||
await this.loadSources()
|
||||
await this.loadItems()
|
||||
},
|
||||
async loadGameSystems() {
|
||||
try {
|
||||
const systems = await fetchGameSystems()
|
||||
this.gameSystems = systems
|
||||
const active = systems.find(s => s.is_active)
|
||||
this.currentGameSystem = active || systems[0] || null
|
||||
} catch (err) {
|
||||
console.error('Failed to load game systems:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
}
|
||||
},
|
||||
async loadSources() {
|
||||
try {
|
||||
const params = buildGameSystemParams(this.currentGameSystem)
|
||||
const resp = await API.get('/api/maintenance/gsm-lit-sources', { params })
|
||||
this.sources = resp.data?.sources || []
|
||||
} catch (err) {
|
||||
console.error('Failed to load sources:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
}
|
||||
},
|
||||
async loadItems() {
|
||||
this.isLoading = true
|
||||
this.error = ''
|
||||
try {
|
||||
const params = buildGameSystemParams(this.currentGameSystem)
|
||||
const resp = await API.get('/api/maintenance/gsm-misc', { params })
|
||||
this.items = resp.data?.misc || []
|
||||
} catch (err) {
|
||||
console.error('Failed to load misc:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
buildParamsForSystemId(systemId) {
|
||||
const sys = findSystemById(this.gameSystems, systemId) || this.currentGameSystem
|
||||
if (!sys) return {}
|
||||
return buildGameSystemParams(sys)
|
||||
},
|
||||
sourceCodeFor(id) {
|
||||
if (!id) return '-'
|
||||
const code = this.sourceMap.get(id)
|
||||
return code || id
|
||||
},
|
||||
systemCodeFor(systemId, fallback = '') {
|
||||
return resolveSystemCode(this.gameSystems, systemId, fallback)
|
||||
},
|
||||
startEdit(item) {
|
||||
this.editingId = item.id
|
||||
this.editedItem = { ...item }
|
||||
this.selectedSystemId = item.game_system_id ?? this.currentGameSystem?.id ?? null
|
||||
},
|
||||
cancelEdit() {
|
||||
this.editingId = null
|
||||
this.editedItem = null
|
||||
this.selectedSystemId = null
|
||||
},
|
||||
async saveEdit() {
|
||||
if (!this.editedItem) return
|
||||
const payload = {
|
||||
key: this.editedItem.key || '',
|
||||
value: this.editedItem.value || '',
|
||||
source_id: this.editedItem.source_id || null,
|
||||
page_number: this.editedItem.page_number || 0,
|
||||
}
|
||||
this.isSaving = true
|
||||
try {
|
||||
const params = this.buildParamsForSystemId(this.selectedSystemId)
|
||||
const resp = await API.put(`/api/maintenance/gsm-misc/${this.editingId}`, payload, { params })
|
||||
const idx = this.items.findIndex(i => i.id === this.editingId)
|
||||
if (idx !== -1) this.items.splice(idx, 1, resp.data)
|
||||
this.cancelEdit()
|
||||
} catch (err) {
|
||||
console.error('Failed to save misc:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isSaving = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<div class="header-section">
|
||||
<h2>{{ $t('maintenance') }} - {{ $t('skillimprovement.title') }}</h2>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error-box">{{ error }}</div>
|
||||
|
||||
<div class="cd-view">
|
||||
<div class="cd-list">
|
||||
<table class="cd-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="cd-table-header">{{ $t('skillimprovement.id') }}</th>
|
||||
<th class="cd-table-header">{{ $t('skillimprovement.level') }}</th>
|
||||
<th class="cd-table-header">{{ $t('skillimprovement.te') }}</th>
|
||||
<th class="cd-table-header">{{ $t('skillimprovement.category') }}</th>
|
||||
<th class="cd-table-header">{{ $t('skillimprovement.difficulty') }}</th>
|
||||
<th class="cd-table-header"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="isLoading">
|
||||
<td colspan="6">{{ $t('common.loading') }}</td>
|
||||
</tr>
|
||||
<template v-for="cost in costs" :key="cost.id">
|
||||
<tr v-if="editingId !== cost.id">
|
||||
<td>{{ cost.id }}</td>
|
||||
<td>{{ cost.current_level }}</td>
|
||||
<td>{{ cost.te_required }}</td>
|
||||
<td>{{ displayCategory(cost) }}</td>
|
||||
<td>{{ displayDifficulty(cost) }}</td>
|
||||
<td><button @click="startEdit(cost)">{{ $t('skillimprovement.edit') }}</button></td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>{{ cost.id }}</td>
|
||||
<td colspan="5">
|
||||
<div class="edit-form">
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('skillimprovement.level') }}</label>
|
||||
<input v-model.number="editedItem.current_level" type="number" />
|
||||
<label class="inline-label">{{ $t('skillimprovement.te') }}</label>
|
||||
<input v-model.number="editedItem.te_required" type="number" />
|
||||
</div>
|
||||
<div class="edit-row">
|
||||
<label>{{ $t('skillimprovement.category') }}</label>
|
||||
<select v-model.number="editedItem.category_id">
|
||||
<option v-for="cat in categoryOptions" :key="cat.id" :value="cat.id">
|
||||
{{ cat.label }}
|
||||
</option>
|
||||
</select>
|
||||
<label class="inline-label">{{ $t('skillimprovement.difficulty') }}</label>
|
||||
<select v-model.number="editedItem.difficulty_id">
|
||||
<option v-for="diff in difficultyOptions" :key="diff.id" :value="diff.id">
|
||||
{{ diff.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="edit-actions">
|
||||
<button class="btn-primary" :disabled="isSaving" @click="saveEdit">
|
||||
<span v-if="!isSaving">{{ $t('skillimprovement.save') }}</span>
|
||||
<span v-else>{{ $t('skillimprovement.saving') }}</span>
|
||||
</button>
|
||||
<button class="btn-cancel" :disabled="isSaving" @click="cancelEdit">
|
||||
{{ $t('skillimprovement.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.error-box {
|
||||
margin: 10px 0;
|
||||
padding: 10px 12px;
|
||||
background: #ffe3e3;
|
||||
color: #8a1c1c;
|
||||
border: 1px solid #f5c2c2;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.edit-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.edit-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
.edit-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.inline-label {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import API from '../../utils/api'
|
||||
|
||||
export default {
|
||||
name: 'SkillImprovementCostView',
|
||||
data() {
|
||||
return {
|
||||
costs: [],
|
||||
editingId: null,
|
||||
editedItem: null,
|
||||
isLoading: false,
|
||||
isSaving: false,
|
||||
error: '',
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadCosts()
|
||||
},
|
||||
computed: {
|
||||
categoryOptions() {
|
||||
const seen = new Map()
|
||||
this.costs.forEach(c => {
|
||||
const id = c.category_id ?? c.skillCategoryId
|
||||
const name = c.category_name || c.skillCategoryName
|
||||
if (id != null && !seen.has(id)) {
|
||||
seen.set(id, name ? `${name} (${id})` : `${id}`)
|
||||
}
|
||||
})
|
||||
return Array.from(seen.entries()).map(([id, label]) => ({ id, label }))
|
||||
},
|
||||
difficultyOptions() {
|
||||
const seen = new Map()
|
||||
this.costs.forEach(c => {
|
||||
const id = c.difficulty_id ?? c.skillDifficultyId
|
||||
const name = c.difficulty_name || c.skillDifficultyName
|
||||
if (id != null && !seen.has(id)) {
|
||||
seen.set(id, name ? `${name} (${id})` : `${id}`)
|
||||
}
|
||||
})
|
||||
return Array.from(seen.entries()).map(([id, label]) => ({ id, label }))
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
displayCategory(cost) {
|
||||
return cost.category_name || cost.skillCategoryName || cost.skillCategoryId || cost.category_id
|
||||
},
|
||||
displayDifficulty(cost) {
|
||||
return cost.difficulty_name || cost.skillDifficultyName || cost.skillDifficultyId || cost.difficulty_id
|
||||
},
|
||||
async loadCosts() {
|
||||
this.isLoading = true
|
||||
this.error = ''
|
||||
try {
|
||||
const resp = await API.get('/api/maintenance/skill-improvement-cost2')
|
||||
this.costs = resp.data?.costs || []
|
||||
} catch (err) {
|
||||
console.error('Failed to load costs:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
startEdit(cost) {
|
||||
this.editingId = cost.id
|
||||
this.editedItem = {
|
||||
...cost,
|
||||
category_id: cost.category_id ?? cost.skillCategoryId,
|
||||
difficulty_id: cost.difficulty_id ?? cost.skillDifficultyId,
|
||||
}
|
||||
},
|
||||
cancelEdit() {
|
||||
this.editingId = null
|
||||
this.editedItem = null
|
||||
},
|
||||
async saveEdit() {
|
||||
if (!this.editedItem) return
|
||||
const payload = {
|
||||
current_level: this.editedItem.current_level,
|
||||
te_required: this.editedItem.te_required,
|
||||
category_id: this.editedItem.category_id,
|
||||
difficulty_id: this.editedItem.difficulty_id,
|
||||
}
|
||||
this.isSaving = true
|
||||
try {
|
||||
const resp = await API.put(`/api/maintenance/skill-improvement-cost2/${this.editingId}`, payload)
|
||||
const idx = this.costs.findIndex(c => c.id === this.editingId)
|
||||
if (idx !== -1) this.costs.splice(idx, 1, resp.data)
|
||||
this.cancelEdit()
|
||||
} catch (err) {
|
||||
console.error('Failed to save cost:', err)
|
||||
this.error = err.response?.data?.error || err.message
|
||||
} finally {
|
||||
this.isSaving = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -151,6 +151,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="edit-row">
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('skill.system') }}</label>
|
||||
<select v-model.number="selectedSystemId" style="width:140px;">
|
||||
<option value="">-</option>
|
||||
<option v-for="system in systemOptions" :key="system.id" :value="system.id">
|
||||
{{ system.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="edit-row">
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('skill.system') }}</label>
|
||||
<select v-model.number="createSelectedSystemId" style="width:140px;">
|
||||
<option value="">-</option>
|
||||
<option v-for="system in systemOptions" :key="system.id" :value="system.id">
|
||||
{{ system.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="edit-row">
|
||||
<div class="edit-field full-width">
|
||||
<label>{{ $t('skill.categories') || 'Categories' }}:</label>
|
||||
@@ -206,7 +230,7 @@
|
||||
<td>{{ dtaItem.beschreibung || '-' }}</td>
|
||||
<td>{{ dtaItem.bonuseigenschaft || '-' }}</td>
|
||||
<td>{{ formatQuelle(dtaItem) }}</td>
|
||||
<td>{{ dtaItem.game_system || 'midgard' }}</td>
|
||||
<td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.game_system || 'midgard') }}</td>
|
||||
<td>
|
||||
<button @click="startEdit(index)">Edit</button>
|
||||
</td>
|
||||
@@ -336,6 +360,13 @@
|
||||
|
||||
<script>
|
||||
import API from '../../utils/api'
|
||||
import {
|
||||
findSystemIdByCode,
|
||||
getSourceCode,
|
||||
getSystemCodeById,
|
||||
loadGameSystems as fetchGameSystems,
|
||||
buildSystemOptions,
|
||||
} from '../../utils/maintenanceGameSystems'
|
||||
|
||||
export default {
|
||||
name: "SkillView",
|
||||
@@ -365,11 +396,17 @@ export default {
|
||||
filterInnateskill: '',
|
||||
filterBonuseigenschaft: '',
|
||||
enhancedSkills: [],
|
||||
availableSources: []
|
||||
availableSources: [],
|
||||
gameSystems: [],
|
||||
selectedSystemId: null,
|
||||
createSelectedSystemId: null
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadEnhancedSkills()
|
||||
await Promise.all([
|
||||
this.loadGameSystems(),
|
||||
this.loadEnhancedSkills()
|
||||
])
|
||||
},
|
||||
computed: {
|
||||
availableCategories() {
|
||||
@@ -458,9 +495,19 @@ export default {
|
||||
})
|
||||
|
||||
return filtered
|
||||
},
|
||||
systemOptions() {
|
||||
return buildSystemOptions(this.gameSystems)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadGameSystems() {
|
||||
try {
|
||||
this.gameSystems = await fetchGameSystems()
|
||||
} catch (error) {
|
||||
console.error('Failed to load game systems:', error)
|
||||
}
|
||||
},
|
||||
async loadEnhancedSkills() {
|
||||
try {
|
||||
const response = await API.get('/api/maintenance/skills-enhanced')
|
||||
@@ -522,12 +569,18 @@ export default {
|
||||
})
|
||||
}
|
||||
|
||||
this.selectedSystemId = skill.game_system_id ?? this.findSystemIdByCode(skill.game_system)
|
||||
|
||||
this.editingIndex = index
|
||||
},
|
||||
getSourceCode(sourceId) {
|
||||
if (!sourceId || !this.availableSources.length) return ''
|
||||
const source = this.availableSources.find(s => s.id === sourceId)
|
||||
return source ? source.code : ''
|
||||
return getSourceCode(this.availableSources, sourceId)
|
||||
},
|
||||
findSystemIdByCode(code) {
|
||||
return findSystemIdByCode(this.gameSystems, code)
|
||||
},
|
||||
getSystemCodeById(systemId, fallback = '') {
|
||||
return getSystemCodeById(this.gameSystems, systemId, fallback)
|
||||
},
|
||||
onCategoryToggle(categoryId) {
|
||||
// If category was removed, also remove its difficulty setting
|
||||
@@ -546,6 +599,7 @@ export default {
|
||||
try {
|
||||
// Find source ID from code
|
||||
const source = this.availableSources.find(s => s.code === this.editedItem.sourceCode)
|
||||
const selectedSystem = this.gameSystems.find(gs => gs.id === this.selectedSystemId)
|
||||
|
||||
// Build category_difficulties array
|
||||
const categoryDifficulties = this.editedItem.selectedCategories.map(catId => ({
|
||||
@@ -557,7 +611,8 @@ export default {
|
||||
id: this.editedItem.id,
|
||||
name: this.editedItem.name,
|
||||
beschreibung: this.editedItem.beschreibung,
|
||||
game_system: this.editedItem.game_system || 'midgard',
|
||||
game_system: selectedSystem ? selectedSystem.code : (this.editedItem.game_system || 'midgard'),
|
||||
game_system_id: selectedSystem ? selectedSystem.id : (this.editedItem.game_system_id ?? null),
|
||||
initialwert: this.editedItem.initialwert,
|
||||
basiswert: this.editedItem.basiswert || 0,
|
||||
bonuseigenschaft: this.editedItem.bonuseigenschaft,
|
||||
@@ -581,6 +636,7 @@ export default {
|
||||
|
||||
this.editingIndex = -1
|
||||
this.editedItem = null
|
||||
this.selectedSystemId = null
|
||||
} catch (error) {
|
||||
console.error('Failed to update skill:', error)
|
||||
alert('Failed to update skill: ' + (error.response?.data?.error || error.message))
|
||||
@@ -608,10 +664,13 @@ export default {
|
||||
},
|
||||
startCreate() {
|
||||
// Initialize new skill object with defaults
|
||||
const defaultSystem = this.gameSystems.find(gs => gs.is_active) || this.gameSystems[0] || null
|
||||
this.createSelectedSystemId = defaultSystem ? defaultSystem.id : null
|
||||
this.editedItem = {
|
||||
name: '',
|
||||
beschreibung: '',
|
||||
game_system: 'midgard',
|
||||
game_system: defaultSystem ? defaultSystem.code : 'midgard',
|
||||
game_system_id: defaultSystem ? defaultSystem.id : null,
|
||||
initialwert: 5,
|
||||
basiswert: 0,
|
||||
bonuseigenschaft: '',
|
||||
@@ -634,6 +693,7 @@ export default {
|
||||
|
||||
// Find source ID from code
|
||||
const source = this.availableSources.find(s => s.code === this.editedItem.sourceCode)
|
||||
const selectedSystem = this.gameSystems.find(gs => gs.id === this.createSelectedSystemId)
|
||||
|
||||
// Build category_difficulties array
|
||||
const categoryDifficulties = this.editedItem.selectedCategories.map(catId => ({
|
||||
@@ -644,7 +704,8 @@ export default {
|
||||
const createData = {
|
||||
name: this.editedItem.name,
|
||||
beschreibung: this.editedItem.beschreibung,
|
||||
game_system: this.editedItem.game_system || 'midgard',
|
||||
game_system: selectedSystem ? selectedSystem.code : (this.editedItem.game_system || 'midgard'),
|
||||
game_system_id: selectedSystem ? selectedSystem.id : (this.editedItem.game_system_id ?? null),
|
||||
initialwert: this.editedItem.initialwert,
|
||||
basiswert: this.editedItem.basiswert || 0,
|
||||
bonuseigenschaft: this.editedItem.bonuseigenschaft,
|
||||
@@ -666,6 +727,7 @@ export default {
|
||||
// Hide the create dialog
|
||||
this.creatingNew = false
|
||||
this.editedItem = null
|
||||
this.createSelectedSystemId = null
|
||||
} catch (error) {
|
||||
console.error('Failed to create skill:', error)
|
||||
alert('Failed to create skill: ' + (error.response?.data?.error || error.message))
|
||||
@@ -675,6 +737,7 @@ export default {
|
||||
cancelCreate() {
|
||||
this.creatingNew = false
|
||||
this.editedItem = null
|
||||
this.createSelectedSystemId = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
<td>{{ dtaItem.ursprung || '-' }}</td>
|
||||
<td>{{ dtaItem.beschreibung || '-' }}</td>
|
||||
<td>{{ formatQuelle(dtaItem) }}</td>
|
||||
<td>{{ dtaItem.system || 'midgard' }}</td>
|
||||
<td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.system || 'midgard') }}</td>
|
||||
<td>
|
||||
<button @click="startEdit(index)">Edit</button>
|
||||
</td>
|
||||
@@ -223,7 +223,12 @@
|
||||
</div>
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('spell.system') }}:</label>
|
||||
<input v-model="editedItem.system" style="width:100px;" />
|
||||
<select v-model.number="selectedSystemId" style="width:140px;">
|
||||
<option value="">-</option>
|
||||
<option v-for="system in systemOptions" :key="system.id" :value="system.id">
|
||||
{{ system.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -354,6 +359,13 @@
|
||||
|
||||
<script>
|
||||
import API from '../../utils/api'
|
||||
import {
|
||||
findSystemIdByCode,
|
||||
getSourceCode,
|
||||
getSystemCodeById,
|
||||
loadGameSystems as fetchGameSystems,
|
||||
buildSystemOptions,
|
||||
} from '../../utils/maintenanceGameSystems'
|
||||
export default {
|
||||
name: "SpellView",
|
||||
props: {
|
||||
@@ -383,11 +395,16 @@ export default {
|
||||
filterWirkungsziel: '',
|
||||
filterQuelle: '',
|
||||
enhancedSpells: [],
|
||||
availableSources: []
|
||||
availableSources: [],
|
||||
gameSystems: [],
|
||||
selectedSystemId: null
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadEnhancedSpells()
|
||||
await Promise.all([
|
||||
this.loadGameSystems(),
|
||||
this.loadEnhancedSpells()
|
||||
])
|
||||
},
|
||||
computed: {
|
||||
availableCategories() {
|
||||
@@ -502,9 +519,19 @@ export default {
|
||||
? aValue.localeCompare(bValue)
|
||||
: bValue.localeCompare(aValue);
|
||||
});
|
||||
},
|
||||
systemOptions() {
|
||||
return buildSystemOptions(this.gameSystems)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadGameSystems() {
|
||||
try {
|
||||
this.gameSystems = await fetchGameSystems()
|
||||
} catch (error) {
|
||||
console.error('Failed to load game systems:', error)
|
||||
}
|
||||
},
|
||||
async loadEnhancedSpells() {
|
||||
try {
|
||||
const response = await API.get('/api/maintenance/spells-enhanced')
|
||||
@@ -524,17 +551,21 @@ export default {
|
||||
...spell,
|
||||
sourceCode: this.getSourceCode(spell.source_id)
|
||||
}
|
||||
this.selectedSystemId = spell.game_system_id ?? this.findSystemIdByCode(spell.system)
|
||||
this.editingIndex = index
|
||||
},
|
||||
async saveEdit(index) {
|
||||
try {
|
||||
// Find source ID from code
|
||||
const source = this.availableSources.find(s => s.code === this.editedItem.sourceCode)
|
||||
const selectedSystem = this.gameSystems.find(gs => gs.id === this.selectedSystemId)
|
||||
|
||||
const updateData = {
|
||||
...this.editedItem,
|
||||
source_id: source ? source.id : null,
|
||||
page_number: this.editedItem.page_number || 0
|
||||
page_number: this.editedItem.page_number || 0,
|
||||
system: selectedSystem ? selectedSystem.code : (this.editedItem.system || ''),
|
||||
game_system_id: selectedSystem ? selectedSystem.id : (this.editedItem.game_system_id ?? null)
|
||||
}
|
||||
|
||||
const response = await API.put(
|
||||
@@ -550,6 +581,7 @@ export default {
|
||||
|
||||
this.editingIndex = -1
|
||||
this.editedItem = null
|
||||
this.selectedSystemId = null
|
||||
} catch (error) {
|
||||
console.error('Failed to save spell:', error)
|
||||
alert('Failed to save spell: ' + (error.response?.data?.error || error.message))
|
||||
@@ -558,6 +590,10 @@ export default {
|
||||
cancelEdit() {
|
||||
this.editingIndex = -1;
|
||||
this.editedItem = null;
|
||||
this.selectedSystemId = null;
|
||||
},
|
||||
findSystemIdByCode(code) {
|
||||
return findSystemIdByCode(this.gameSystems, code)
|
||||
},
|
||||
sortBy(field) {
|
||||
if (this.sortField === field) {
|
||||
@@ -677,9 +713,10 @@ export default {
|
||||
return spell.quelle || '-'
|
||||
},
|
||||
getSourceCode(sourceId) {
|
||||
if (!sourceId || !this.availableSources.length) return ''
|
||||
const source = this.availableSources.find(s => s.id === sourceId)
|
||||
return source ? source.code : ''
|
||||
return getSourceCode(this.availableSources, sourceId)
|
||||
},
|
||||
getSystemCodeById(systemId, fallback = '') {
|
||||
return getSystemCodeById(this.gameSystems, systemId, fallback)
|
||||
},
|
||||
clearFilters() {
|
||||
this.searchTerm = ''
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
<td>{{ dtaItem.initialwert || '0' }}</td>
|
||||
<td>{{ dtaItem.beschreibung || '-' }}</td>
|
||||
<td>{{ formatQuelle(dtaItem) }}</td>
|
||||
<td>{{ dtaItem.system || 'midgard' }}</td>
|
||||
<td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.system || 'midgard') }}</td>
|
||||
<td>
|
||||
<button @click="startEdit(index)">Edit</button>
|
||||
</td>
|
||||
@@ -109,7 +109,12 @@
|
||||
</div>
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('weaponskill.system') }}:</label>
|
||||
<input v-model="editedItem.system" style="width:100px;" />
|
||||
<select v-model.number="selectedSystemId" style="width:140px;">
|
||||
<option value="">-</option>
|
||||
<option v-for="system in systemOptions" :key="system.id" :value="system.id">
|
||||
{{ system.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -174,6 +179,13 @@
|
||||
|
||||
<script>
|
||||
import API from '../../utils/api'
|
||||
import {
|
||||
findSystemIdByCode,
|
||||
getSourceCode,
|
||||
getSystemCodeById,
|
||||
loadGameSystems as fetchGameSystems,
|
||||
buildSystemOptions,
|
||||
} from '../../utils/maintenanceGameSystems'
|
||||
export default {
|
||||
name: "WaeponSkillView",
|
||||
props: {
|
||||
@@ -197,11 +209,16 @@ export default {
|
||||
filterQuelle: '',
|
||||
enhancedWeaponSkills: [],
|
||||
availableSources: [],
|
||||
availableDifficultiesData: []
|
||||
availableDifficultiesData: [],
|
||||
gameSystems: [],
|
||||
selectedSystemId: null
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadEnhancedWeaponSkills()
|
||||
await Promise.all([
|
||||
this.loadGameSystems(),
|
||||
this.loadEnhancedWeaponSkills()
|
||||
])
|
||||
},
|
||||
computed: {
|
||||
availableDifficulties() {
|
||||
@@ -259,9 +276,19 @@ export default {
|
||||
})
|
||||
|
||||
return filtered
|
||||
},
|
||||
systemOptions() {
|
||||
return buildSystemOptions(this.gameSystems)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadGameSystems() {
|
||||
try {
|
||||
this.gameSystems = await fetchGameSystems()
|
||||
} catch (error) {
|
||||
console.error('Failed to load game systems:', error)
|
||||
}
|
||||
},
|
||||
async loadEnhancedWeaponSkills() {
|
||||
try {
|
||||
const response = await API.get('/api/maintenance/weaponskills-enhanced')
|
||||
@@ -278,19 +305,23 @@ export default {
|
||||
...weaponSkill,
|
||||
sourceCode: this.getSourceCode(weaponSkill.source_id)
|
||||
}
|
||||
this.selectedSystemId = weaponSkill.game_system_id ?? this.findSystemIdByCode(weaponSkill.system)
|
||||
this.editingIndex = index
|
||||
},
|
||||
async saveEdit(index) {
|
||||
try {
|
||||
// Find source ID from code
|
||||
const source = this.availableSources.find(s => s.code === this.editedItem.sourceCode)
|
||||
const selectedSystem = this.gameSystems.find(gs => gs.id === this.selectedSystemId)
|
||||
|
||||
const updateData = {
|
||||
...this.editedItem,
|
||||
source_id: source ? source.id : null,
|
||||
page_number: this.editedItem.page_number || 0,
|
||||
difficulty: this.editedItem.difficulty,
|
||||
category: 'Waffen' // Weapon skills always use 'Waffen' category
|
||||
category: 'Waffen', // Weapon skills always use 'Waffen' category
|
||||
system: selectedSystem ? selectedSystem.code : (this.editedItem.system || ''),
|
||||
game_system_id: selectedSystem ? selectedSystem.id : (this.editedItem.game_system_id ?? null)
|
||||
}
|
||||
|
||||
const response = await API.put(
|
||||
@@ -303,6 +334,7 @@ export default {
|
||||
|
||||
this.editingIndex = -1
|
||||
this.editedItem = null
|
||||
this.selectedSystemId = null
|
||||
} catch (error) {
|
||||
console.error('Failed to save weapon skill:', error)
|
||||
alert('Failed to save weapon skill: ' + (error.response?.data?.error || error.message))
|
||||
@@ -311,6 +343,10 @@ export default {
|
||||
cancelEdit() {
|
||||
this.editingIndex = -1
|
||||
this.editedItem = null
|
||||
this.selectedSystemId = null
|
||||
},
|
||||
findSystemIdByCode(code) {
|
||||
return findSystemIdByCode(this.gameSystems, code)
|
||||
},
|
||||
sortBy(field) {
|
||||
if (this.sortField === field) {
|
||||
@@ -336,9 +372,10 @@ export default {
|
||||
return weaponSkill.quelle || '-'
|
||||
},
|
||||
getSourceCode(sourceId) {
|
||||
if (!sourceId || !this.availableSources.length) return ''
|
||||
const source = this.availableSources.find(s => s.id === sourceId)
|
||||
return source ? source.code : ''
|
||||
return getSourceCode(this.availableSources, sourceId)
|
||||
},
|
||||
getSystemCodeById(systemId, fallback = '') {
|
||||
return getSystemCodeById(this.gameSystems, systemId, fallback)
|
||||
},
|
||||
clearFilters() {
|
||||
this.searchTerm = ''
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
<td>{{ dtaItem.beschreibung || '-' }}</td>
|
||||
<td>{{ formatQuelle(dtaItem) }}</td>
|
||||
<td><input type="checkbox" :checked="dtaItem.personal_item" disabled /></td>
|
||||
<td>{{ dtaItem.system || 'midgard' }}</td>
|
||||
<td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.system || 'midgard') }}</td>
|
||||
<td>
|
||||
<button @click="startEdit(index)">Edit</button>
|
||||
</td>
|
||||
@@ -191,7 +191,12 @@
|
||||
</div>
|
||||
<div class="edit-field">
|
||||
<label>{{ $t('weapon.system') }}:</label>
|
||||
<input v-model="editedItem.system" style="width:100px;" />
|
||||
<select v-model.number="selectedSystemId" style="width:140px;">
|
||||
<option value="">-</option>
|
||||
<option v-for="system in systemOptions" :key="system.id" :value="system.id">
|
||||
{{ system.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -256,6 +261,13 @@
|
||||
|
||||
<script>
|
||||
import API from '../../utils/api'
|
||||
import {
|
||||
findSystemIdByCode,
|
||||
getSourceCode,
|
||||
getSystemCodeById,
|
||||
loadGameSystems as fetchGameSystems,
|
||||
buildSystemOptions,
|
||||
} from '../../utils/maintenanceGameSystems'
|
||||
export default {
|
||||
name: "WeaponView",
|
||||
props: {
|
||||
@@ -282,11 +294,16 @@ export default {
|
||||
filterRangeFar: '',
|
||||
filterQuelle: '',
|
||||
enhancedWeapons: [],
|
||||
availableSources: []
|
||||
availableSources: [],
|
||||
gameSystems: [],
|
||||
selectedSystemId: null
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadEnhancedWeapons()
|
||||
await Promise.all([
|
||||
this.loadGameSystems(),
|
||||
this.loadEnhancedWeapons()
|
||||
])
|
||||
},
|
||||
computed: {
|
||||
availableSkillsRequired() {
|
||||
@@ -392,9 +409,19 @@ export default {
|
||||
})
|
||||
|
||||
return filtered
|
||||
},
|
||||
systemOptions() {
|
||||
return buildSystemOptions(this.gameSystems)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadGameSystems() {
|
||||
try {
|
||||
this.gameSystems = await fetchGameSystems()
|
||||
} catch (error) {
|
||||
console.error('Failed to load game systems:', error)
|
||||
}
|
||||
},
|
||||
async loadEnhancedWeapons() {
|
||||
try {
|
||||
const response = await API.get('/api/maintenance/weapons-enhanced')
|
||||
@@ -410,17 +437,21 @@ export default {
|
||||
...weapon,
|
||||
sourceCode: this.getSourceCode(weapon.source_id)
|
||||
}
|
||||
this.selectedSystemId = weapon.game_system_id ?? this.findSystemIdByCode(weapon.system)
|
||||
this.editingIndex = index
|
||||
},
|
||||
async saveEdit(index) {
|
||||
try {
|
||||
// Find source ID from code
|
||||
const source = this.availableSources.find(s => s.code === this.editedItem.sourceCode)
|
||||
const selectedSystem = this.gameSystems.find(gs => gs.id === this.selectedSystemId)
|
||||
|
||||
const updateData = {
|
||||
...this.editedItem,
|
||||
source_id: source ? source.id : null,
|
||||
page_number: this.editedItem.page_number || 0
|
||||
page_number: this.editedItem.page_number || 0,
|
||||
system: selectedSystem ? selectedSystem.code : (this.editedItem.system || ''),
|
||||
game_system_id: selectedSystem ? selectedSystem.id : (this.editedItem.game_system_id ?? null)
|
||||
}
|
||||
|
||||
const response = await API.put(
|
||||
@@ -436,6 +467,7 @@ export default {
|
||||
|
||||
this.editingIndex = -1
|
||||
this.editedItem = null
|
||||
this.selectedSystemId = null
|
||||
} catch (error) {
|
||||
console.error('Failed to save weapon:', error)
|
||||
alert('Failed to save weapon: ' + (error.response?.data?.error || error.message))
|
||||
@@ -444,6 +476,10 @@ export default {
|
||||
cancelEdit() {
|
||||
this.editingIndex = -1
|
||||
this.editedItem = null
|
||||
this.selectedSystemId = null
|
||||
},
|
||||
findSystemIdByCode(code) {
|
||||
return findSystemIdByCode(this.gameSystems, code)
|
||||
},
|
||||
sortBy(field) {
|
||||
if (this.sortField === field) {
|
||||
@@ -469,9 +505,10 @@ export default {
|
||||
return weapon.quelle || '-'
|
||||
},
|
||||
getSourceCode(sourceId) {
|
||||
if (!sourceId || !this.availableSources.length) return ''
|
||||
const source = this.availableSources.find(s => s.id === sourceId)
|
||||
return source ? source.code : ''
|
||||
return getSourceCode(this.availableSources, sourceId)
|
||||
},
|
||||
getSystemCodeById(systemId, fallback = '') {
|
||||
return getSystemCodeById(this.gameSystems, systemId, fallback)
|
||||
},
|
||||
clearFilters() {
|
||||
this.searchTerm = ''
|
||||
|
||||
+90
-1
@@ -279,6 +279,80 @@ export default {
|
||||
spell:'Zauber',
|
||||
equipment:'Ausrüstung',
|
||||
weapon:'Waffen',
|
||||
believe:'Glaubensrichtungen',
|
||||
gamesystem:'Spielsysteme',
|
||||
litsource:'Literaturquellen',
|
||||
misc:'Sonstige',
|
||||
skillimprovement:'Steigerungskosten',
|
||||
},
|
||||
believe: {
|
||||
title: 'Glaubensrichtungen',
|
||||
id: 'ID',
|
||||
name: 'Name',
|
||||
nameRequired: 'Name darf nicht leer sein.',
|
||||
description: 'Beschreibung',
|
||||
source: 'Quelle',
|
||||
page: 'Seite',
|
||||
system: 'System',
|
||||
edit: 'Bearbeiten',
|
||||
save: 'Speichern',
|
||||
saving: 'Speichern...',
|
||||
cancel: 'Abbrechen',
|
||||
sourceNone: 'Keine Quelle'
|
||||
},
|
||||
gamesystem: {
|
||||
title: 'Spielsysteme',
|
||||
id: 'ID',
|
||||
code: 'Code',
|
||||
name: 'Name',
|
||||
description: 'Beschreibung',
|
||||
active: 'Aktiv',
|
||||
edit: 'Bearbeiten',
|
||||
save: 'Speichern',
|
||||
saving: 'Speichern...',
|
||||
cancel: 'Abbrechen'
|
||||
},
|
||||
litsource: {
|
||||
title: 'Literaturquellen',
|
||||
id: 'ID',
|
||||
code: 'Code',
|
||||
name: 'Name',
|
||||
fullName: 'Vollständiger Name',
|
||||
edition: 'Edition',
|
||||
publisher: 'Verlag',
|
||||
year: 'Jahr',
|
||||
description: 'Beschreibung',
|
||||
active: 'Aktiv',
|
||||
core: 'Kernquelle',
|
||||
edit: 'Bearbeiten',
|
||||
save: 'Speichern',
|
||||
saving: 'Speichern...',
|
||||
cancel: 'Abbrechen'
|
||||
},
|
||||
misc: {
|
||||
title: 'Sonstige Einträge',
|
||||
id: 'ID',
|
||||
key: 'Schlüssel',
|
||||
value: 'Wert',
|
||||
source: 'Quelle',
|
||||
page: 'Seite',
|
||||
system: 'System',
|
||||
edit: 'Bearbeiten',
|
||||
save: 'Speichern',
|
||||
saving: 'Speichern...',
|
||||
cancel: 'Abbrechen'
|
||||
},
|
||||
skillimprovement: {
|
||||
title: 'Fertigkeitssteigerungskosten',
|
||||
id: 'ID',
|
||||
level: 'Aktueller Wert',
|
||||
te: 'TE erforderlich',
|
||||
category: 'Kategorie-ID',
|
||||
difficulty: 'Schwierigkeits-ID',
|
||||
edit: 'Bearbeiten',
|
||||
save: 'Speichern',
|
||||
saving: 'Speichern...',
|
||||
cancel: 'Abbrechen'
|
||||
},
|
||||
search:'Suche',
|
||||
Skill:'Fertigkeit',
|
||||
@@ -360,6 +434,8 @@ export default {
|
||||
unfree: 'Unfrei',
|
||||
religion: 'Religion/Glaube',
|
||||
religionPlaceholder: 'Mindestens 2 Zeichen für die Suche eingeben...',
|
||||
religionHelp: 'Gib mindestens zwei Buchstaben ein, um passende Glaubensrichtungen zu finden. Klicken zum Auswählen. Kann später geändert und erweitert werden',
|
||||
characterNameHelp: 'Geben Sie den Namen Ihres Charakters ein. Dieser Name wird in der Charakterübersicht und im Datenblatt angezeigt. Kann zur Zeit nich geändert werden',
|
||||
selected: 'Ausgewählt',
|
||||
nextAttributes: 'Weiter: Attribute →',
|
||||
required: '*',
|
||||
@@ -543,6 +619,7 @@ export default {
|
||||
loading: 'Lade Benutzer...',
|
||||
loadError: 'Fehler beim Laden der Benutzer',
|
||||
id: 'ID',
|
||||
displayName: 'Anzeigename',
|
||||
username: 'Benutzername',
|
||||
email: 'E-Mail',
|
||||
role: 'Rolle',
|
||||
@@ -581,10 +658,15 @@ export default {
|
||||
title: 'Benutzerprofil',
|
||||
loading: 'Lade Profil...',
|
||||
userInfo: 'Benutzerinformationen',
|
||||
displayName: 'Anzeigename',
|
||||
username: 'Benutzername',
|
||||
currentEmail: 'Aktuelle E-Mail',
|
||||
role: 'Rolle',
|
||||
language: 'Sprache',
|
||||
changeDisplayName: 'Anzeigenamen ändern',
|
||||
displayNamePlaceholder: 'Maximal 30 Zeichen',
|
||||
displayNameHelper: 'Alle Zeichen sind erlaubt. Maximale Länge: 30.',
|
||||
updateDisplayName: 'Anzeigenamen aktualisieren',
|
||||
changeLanguage: 'Sprache ändern',
|
||||
selectLanguage: 'Sprache auswählen',
|
||||
updateLanguage: 'Sprache aktualisieren',
|
||||
@@ -614,7 +696,10 @@ export default {
|
||||
passwordMismatch: 'Die Passwörter stimmen nicht überein',
|
||||
passwordUpdateSuccess: 'Passwort erfolgreich aktualisiert',
|
||||
passwordUpdateError: 'Fehler beim Aktualisieren des Passworts',
|
||||
currentPasswordIncorrect: 'Das aktuelle Passwort ist falsch'
|
||||
currentPasswordIncorrect: 'Das aktuelle Passwort ist falsch',
|
||||
displayNameUpdateSuccess: 'Anzeigename erfolgreich aktualisiert',
|
||||
displayNameUpdateError: 'Fehler beim Aktualisieren des Anzeigenamens',
|
||||
displayNameTooLong: 'Anzeigename darf maximal 30 Zeichen lang sein'
|
||||
},
|
||||
character: {
|
||||
uploadImage: 'Bild hochladen',
|
||||
@@ -704,6 +789,10 @@ export default {
|
||||
frontend: 'Frontend',
|
||||
backend: 'Backend',
|
||||
version: 'Version',
|
||||
system: 'System',
|
||||
userCount: 'Benutzeranzahl',
|
||||
charCount: 'Charakteranzahl',
|
||||
dbVersion: 'Datenbankversion',
|
||||
commit: 'Commit',
|
||||
status: 'Status',
|
||||
statusAvailable: 'Verfügbar',
|
||||
|
||||
+90
-1
@@ -275,6 +275,80 @@ export default {
|
||||
spell:'Zauber',
|
||||
equipment:'Ausrüstung',
|
||||
weapon:'Waffen',
|
||||
believe:'Beliefs',
|
||||
gamesystem:'Game Systems',
|
||||
litsource:'Sources',
|
||||
misc:'Misc',
|
||||
skillimprovement:'Improvement Costs',
|
||||
},
|
||||
believe: {
|
||||
title: 'Beliefs',
|
||||
id: 'ID',
|
||||
name: 'Name',
|
||||
nameRequired: 'Name is required.',
|
||||
description: 'Description',
|
||||
source: 'Source',
|
||||
page: 'Page',
|
||||
system: 'System',
|
||||
edit: 'Edit',
|
||||
save: 'Save',
|
||||
saving: 'Saving...',
|
||||
cancel: 'Cancel',
|
||||
sourceNone: 'No source'
|
||||
},
|
||||
gamesystem: {
|
||||
title: 'Game Systems',
|
||||
id: 'ID',
|
||||
code: 'Code',
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
active: 'Active',
|
||||
edit: 'Edit',
|
||||
save: 'Save',
|
||||
saving: 'Saving...',
|
||||
cancel: 'Cancel'
|
||||
},
|
||||
litsource: {
|
||||
title: 'Literature Sources',
|
||||
id: 'ID',
|
||||
code: 'Code',
|
||||
name: 'Name',
|
||||
fullName: 'Full name',
|
||||
edition: 'Edition',
|
||||
publisher: 'Publisher',
|
||||
year: 'Year',
|
||||
description: 'Description',
|
||||
active: 'Active',
|
||||
core: 'Core source',
|
||||
edit: 'Edit',
|
||||
save: 'Save',
|
||||
saving: 'Saving...',
|
||||
cancel: 'Cancel'
|
||||
},
|
||||
misc: {
|
||||
title: 'Misc Entries',
|
||||
id: 'ID',
|
||||
key: 'Key',
|
||||
value: 'Value',
|
||||
source: 'Source',
|
||||
page: 'Page',
|
||||
system: 'System',
|
||||
edit: 'Edit',
|
||||
save: 'Save',
|
||||
saving: 'Saving...',
|
||||
cancel: 'Cancel'
|
||||
},
|
||||
skillimprovement: {
|
||||
title: 'Skill Improvement Costs',
|
||||
id: 'ID',
|
||||
level: 'Current level',
|
||||
te: 'TE required',
|
||||
category: 'Category ID',
|
||||
difficulty: 'Difficulty ID',
|
||||
edit: 'Edit',
|
||||
save: 'Save',
|
||||
saving: 'Saving...',
|
||||
cancel: 'Cancel'
|
||||
},
|
||||
search:'Suche',
|
||||
Skill:'Fertigkeit',
|
||||
@@ -356,6 +430,8 @@ export default {
|
||||
unfree: 'Unfree',
|
||||
religion: 'Religion/Belief',
|
||||
religionPlaceholder: 'Type at least 2 characters to search beliefs...',
|
||||
religionHelp: 'Type two or more letters to search available beliefs, then click a result to select it. Later you can change and extend it',
|
||||
characterNameHelp: 'Enter the name of your character. This name will be displayed in the character overview and datasheet. Cannot be changed at the moment',
|
||||
selected: 'Selected',
|
||||
nextAttributes: 'Next: Attributes →',
|
||||
required: '*',
|
||||
@@ -539,6 +615,7 @@ export default {
|
||||
loading: 'Loading users...',
|
||||
loadError: 'Failed to load users',
|
||||
id: 'ID',
|
||||
displayName: 'Display Name',
|
||||
username: 'Username',
|
||||
email: 'Email',
|
||||
role: 'Role',
|
||||
@@ -577,10 +654,15 @@ export default {
|
||||
title: 'User Profile',
|
||||
loading: 'Loading profile...',
|
||||
userInfo: 'User Information',
|
||||
displayName: 'Display Name',
|
||||
username: 'Username',
|
||||
currentEmail: 'Current Email',
|
||||
role: 'Role',
|
||||
language: 'Language',
|
||||
changeDisplayName: 'Change Display Name',
|
||||
displayNamePlaceholder: 'Up to 30 characters',
|
||||
displayNameHelper: 'Any characters are allowed. Maximum length: 30.',
|
||||
updateDisplayName: 'Update Display Name',
|
||||
changeLanguage: 'Change Language',
|
||||
selectLanguage: 'Select Language',
|
||||
updateLanguage: 'Update Language',
|
||||
@@ -610,7 +692,10 @@ export default {
|
||||
passwordMismatch: 'Passwords do not match',
|
||||
passwordUpdateSuccess: 'Password updated successfully',
|
||||
passwordUpdateError: 'Failed to update password',
|
||||
currentPasswordIncorrect: 'Current password is incorrect'
|
||||
currentPasswordIncorrect: 'Current password is incorrect',
|
||||
displayNameUpdateSuccess: 'Display name updated successfully',
|
||||
displayNameUpdateError: 'Failed to update display name',
|
||||
displayNameTooLong: 'Display name must be at most 30 characters'
|
||||
},
|
||||
character: {
|
||||
uploadImage: 'Upload Image',
|
||||
@@ -699,6 +784,10 @@ export default {
|
||||
frontend: 'Frontend',
|
||||
backend: 'Backend',
|
||||
version: 'Version',
|
||||
system: 'System',
|
||||
userCount: 'User Count',
|
||||
charCount: 'Character Count',
|
||||
dbVersion: 'Database Version',
|
||||
commit: 'Commit',
|
||||
status: 'Status',
|
||||
statusAvailable: 'Available',
|
||||
|
||||
@@ -21,12 +21,14 @@ export const useUserStore = defineStore('user', {
|
||||
this.isLoading = true
|
||||
try {
|
||||
const response = await API.get('/api/user/profile')
|
||||
this.currentUser = response.data
|
||||
const profile = { ...response.data }
|
||||
profile.display_name = profile.display_name || profile.username
|
||||
this.currentUser = profile
|
||||
|
||||
// Set user's preferred language
|
||||
if (response.data.preferred_language) {
|
||||
i18n.global.locale.value = response.data.preferred_language
|
||||
localStorage.setItem('language', response.data.preferred_language)
|
||||
if (profile.preferred_language) {
|
||||
i18n.global.locale.value = profile.preferred_language
|
||||
localStorage.setItem('language', profile.preferred_language)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch user profile:', error)
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import API from './api'
|
||||
|
||||
const defaultSystemLabel = (system = {}) => {
|
||||
const code = system.code || ''
|
||||
const name = system.name || ''
|
||||
if (code && name) return `${code} (${name})`
|
||||
return code || name || String(system.id ?? '')
|
||||
}
|
||||
|
||||
export const normalizeSystem = (gs = {}) => ({
|
||||
...gs,
|
||||
id: gs.id ?? gs.ID ?? gs.Id ?? null,
|
||||
code: gs.code ?? gs.Code ?? '',
|
||||
name: gs.name ?? gs.Name ?? '',
|
||||
description: gs.description ?? gs.Description ?? '',
|
||||
is_active: gs.is_active ?? gs.IsActive ?? gs.isActive ?? false,
|
||||
})
|
||||
|
||||
export const buildSystemOptions = (gameSystems = [], labelBuilder = defaultSystemLabel) =>
|
||||
gameSystems.map(system => ({
|
||||
id: system.id,
|
||||
label: labelBuilder(system),
|
||||
}))
|
||||
|
||||
// Alias retained for existing component imports
|
||||
export const systemOptionsFor = buildSystemOptions
|
||||
|
||||
export const findSystemById = (gameSystems = [], id) => {
|
||||
if (id === null || id === undefined) return null
|
||||
return gameSystems.find(gs => gs.id === id) || null
|
||||
}
|
||||
|
||||
export const findSystemIdByCode = (gameSystems = [], code) => {
|
||||
if (!code) return null
|
||||
const match = gameSystems.find(gs => gs.code === code)
|
||||
return match ? match.id : null
|
||||
}
|
||||
|
||||
export const buildGameSystemParams = system => {
|
||||
if (!system) return {}
|
||||
return {
|
||||
game_system_id: system.id,
|
||||
game_system: system.name,
|
||||
}
|
||||
}
|
||||
|
||||
export const getSystemCodeById = (gameSystems = [], systemId, fallback = '') => {
|
||||
if (!systemId) return fallback
|
||||
const sys = gameSystems.find(gs => gs.id === systemId)
|
||||
return sys ? sys.code || fallback : fallback
|
||||
}
|
||||
|
||||
// Alias retained for existing component imports
|
||||
export const systemCodeFor = getSystemCodeById
|
||||
|
||||
export const getSourceCode = (sources = [], sourceId) => {
|
||||
if (!sourceId) return ''
|
||||
const source = sources.find(src => src.id === sourceId)
|
||||
return source ? source.code || '' : ''
|
||||
}
|
||||
|
||||
export const loadGameSystems = async () => {
|
||||
const resp = await API.get('/api/maintenance/game-systems')
|
||||
const systems = resp.data?.game_systems || []
|
||||
return systems.map(normalizeSystem)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Frontend version information
|
||||
export const VERSION = '0.2.1'
|
||||
export const VERSION = '0.2.2'
|
||||
|
||||
// Git commit will be injected at build time or detected from env
|
||||
export const GIT_COMMIT = import.meta.env.VITE_GIT_COMMIT || 'unknown'
|
||||
|
||||
@@ -19,17 +19,24 @@
|
||||
<div class="card">
|
||||
<h4>{{ $t('systemInfo.frontend') }}</h4>
|
||||
<p><strong>{{ $t('systemInfo.version') }}:</strong> {{ frontendVersion }}</p>
|
||||
<p><strong>{{ $t('systemInfo.commit') }}:</strong> <code>{{ frontendCommit }}</code></p>
|
||||
<!--<p><strong>{{ $t('systemInfo.commit') }}:</strong> <code>{{ frontendCommit }}</code></p>-->
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h4>{{ $t('systemInfo.backend') }}</h4>
|
||||
<p><strong>{{ $t('systemInfo.version') }}:</strong> {{ backendVersion }}</p>
|
||||
<p><strong>{{ $t('systemInfo.commit') }}:</strong> <code>{{ backendCommit }}</code></p>
|
||||
<!--<p><strong>{{ $t('systemInfo.commit') }}:</strong> <code>{{ backendCommit }}</code></p>-->
|
||||
<p><strong>{{ $t('systemInfo.status') }}:</strong>
|
||||
<span :class="statusClass">{{ statusText }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h4>{{ $t('systemInfo.system') }}</h4>
|
||||
<p><strong>{{ $t('systemInfo.userCount') }}:</strong> {{ userCount }}</p>
|
||||
<p><strong>{{ $t('systemInfo.charCount') }}:</strong> {{ charCount }}</p>
|
||||
<p><strong>{{ $t('systemInfo.dbVersion') }}:</strong> {{ dbVersion ||"N/A" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-header">
|
||||
@@ -119,6 +126,9 @@ export default {
|
||||
frontendCommit: getGitCommit(),
|
||||
backendVersion: "Loading...",
|
||||
backendCommit: "Loading...",
|
||||
userCount: "Loading...",
|
||||
charCount: "Loading...",
|
||||
dbVersion: "Loading...",
|
||||
githubUrl: "https://github.com/Bardioc26/bamort",
|
||||
koFiUrl: "https://ko-fi.com/bardioc26",
|
||||
}
|
||||
@@ -148,16 +158,22 @@ export default {
|
||||
async fetchBackendVersion() {
|
||||
try {
|
||||
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8180'
|
||||
const response = await axios.get(`${apiUrl}/api/public/version`)
|
||||
const response = await axios.get(`${apiUrl}/api/public/systeminfo`)
|
||||
|
||||
if (response.data) {
|
||||
this.backendVersion = response.data.version || "Unknown"
|
||||
this.backendCommit = response.data.gitCommit || "Unknown"
|
||||
this.userCount = response.data.userCount || "Unknown"
|
||||
this.charCount = response.data.charCount || "Unknown"
|
||||
this.dbVersion = response.data.dbVersion || "Unknown"
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Could not fetch backend version:", error)
|
||||
this.backendVersion = "Unavailable"
|
||||
this.backendCommit = "N/A"
|
||||
this.userCount = "N/A"
|
||||
this.charCount = "N/A"
|
||||
this.dbVersion = "N/A"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<tr>
|
||||
<th>{{ $t('userManagement.id') }}</th>
|
||||
<th>{{ $t('userManagement.username') }}</th>
|
||||
<th>{{ $t('userManagement.displayName') }}</th>
|
||||
<th>{{ $t('userManagement.email') }}</th>
|
||||
<th>{{ $t('userManagement.role') }}</th>
|
||||
<th>{{ $t('userManagement.createdAt') }}</th>
|
||||
@@ -24,6 +25,7 @@
|
||||
<tr v-for="user in users" :key="user.id">
|
||||
<td>{{ user.id }}</td>
|
||||
<td>{{ user.username }}</td>
|
||||
<td>{{ user.display_name }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>
|
||||
<span :class="getRoleBadgeClass(user.role)">
|
||||
@@ -65,7 +67,7 @@
|
||||
<h3>{{ $t('userManagement.changeRoleTitle') }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ $t('userManagement.changeRoleFor') }}: <strong>{{ selectedUser.username }}</strong></p>
|
||||
<p>{{ $t('userManagement.changeRoleFor') }}: <strong>{{ selectedUser.display_name || selectedUser.username }}</strong></p>
|
||||
<div class="form-group">
|
||||
<label>{{ $t('userManagement.selectRole') }}</label>
|
||||
<select v-model="newRole" class="form-control">
|
||||
@@ -93,7 +95,7 @@
|
||||
<h3>{{ $t('userManagement.deleteUserTitle') }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ $t('userManagement.deleteConfirm') }}: <strong>{{ selectedUser.username }}</strong>?</p>
|
||||
<p>{{ $t('userManagement.deleteConfirm') }}: <strong>{{ selectedUser.display_name || selectedUser.username }}</strong>?</p>
|
||||
<p class="badge badge-warning">{{ $t('userManagement.deleteWarning') }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -114,7 +116,7 @@
|
||||
<h3>{{ $t('userManagement.changePasswordTitle') }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ $t('userManagement.changePasswordFor') }}: <strong>{{ selectedUser.username }}</strong></p>
|
||||
<p>{{ $t('userManagement.changePasswordFor') }}: <strong>{{ selectedUser.display_name || selectedUser.username }}</strong></p>
|
||||
<div class="form-group">
|
||||
<label>{{ $t('userManagement.newPassword') }}</label>
|
||||
<input
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
<!-- User Information Section -->
|
||||
<div class="profile-section">
|
||||
<h2>{{ $t('profile.userInfo') }}</h2>
|
||||
<div class="info-row">
|
||||
<label>{{ $t('profile.displayName') }}:</label>
|
||||
<span>{{ userProfile.display_name || userProfile.username }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<label>{{ $t('profile.username') }}:</label>
|
||||
<span>{{ userProfile.username }}</span>
|
||||
@@ -31,6 +35,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Change Display Name Section -->
|
||||
<div class="profile-section">
|
||||
<h2>{{ $t('profile.changeDisplayName') }}</h2>
|
||||
<form @submit.prevent="updateDisplayName" class="profile-form">
|
||||
<div class="form-group">
|
||||
<label for="displayName">{{ $t('profile.displayName') }}:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="displayName"
|
||||
v-model="displayNameForm.newDisplayName"
|
||||
:placeholder="$t('profile.displayNamePlaceholder')"
|
||||
maxlength="30"
|
||||
/>
|
||||
<small>{{ $t('profile.displayNameHelper') }}</small>
|
||||
</div>
|
||||
<button type="submit" :disabled="isUpdating" class="btn-primary">
|
||||
<span v-if="!isUpdating">{{ $t('profile.updateDisplayName') }}</span>
|
||||
<span v-else>{{ $t('profile.updating') }}</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Change Language Section -->
|
||||
<div class="profile-section">
|
||||
<h2>{{ $t('profile.changeLanguage') }}</h2>
|
||||
@@ -222,6 +248,7 @@ h1 {
|
||||
|
||||
<script>
|
||||
import API from '../utils/api'
|
||||
import { useUserStore } from '../stores/userStore'
|
||||
|
||||
export default {
|
||||
name: 'UserProfileView',
|
||||
@@ -231,10 +258,14 @@ export default {
|
||||
isUpdating: false,
|
||||
userProfile: {
|
||||
username: '',
|
||||
display_name: '',
|
||||
email: '',
|
||||
role: 'standard',
|
||||
preferred_language: 'de'
|
||||
},
|
||||
displayNameForm: {
|
||||
newDisplayName: ''
|
||||
},
|
||||
languageForm: {
|
||||
selectedLanguage: 'de'
|
||||
},
|
||||
@@ -259,6 +290,7 @@ export default {
|
||||
this.userProfile = response.data
|
||||
this.emailForm.newEmail = this.userProfile.email
|
||||
this.languageForm.selectedLanguage = this.userProfile.preferred_language || 'de'
|
||||
this.displayNameForm.newDisplayName = this.userProfile.display_name || ''
|
||||
} catch (error) {
|
||||
console.error('Failed to load profile:', error)
|
||||
alert(this.$t('profile.loadError') + ': ' + (error.response?.data?.error || error.message))
|
||||
@@ -269,6 +301,38 @@ export default {
|
||||
getRoleBadgeClass(role) {
|
||||
return `badge-role-${role}`
|
||||
},
|
||||
async updateDisplayName() {
|
||||
if (this.displayNameForm.newDisplayName.length > 30) {
|
||||
alert(this.$t('profile.displayNameTooLong'))
|
||||
return
|
||||
}
|
||||
|
||||
this.isUpdating = true
|
||||
try {
|
||||
const response = await API.put('/api/user/display-name', {
|
||||
display_name: this.displayNameForm.newDisplayName
|
||||
})
|
||||
|
||||
this.userProfile.display_name = response.data.display_name || this.userProfile.username
|
||||
this.displayNameForm.newDisplayName = this.userProfile.display_name
|
||||
|
||||
const userStore = useUserStore()
|
||||
if (userStore.currentUser) {
|
||||
userStore.currentUser.display_name = this.userProfile.display_name
|
||||
}
|
||||
|
||||
//alert(this.$t('profile.displayNameUpdateSuccess'))
|
||||
} catch (error) {
|
||||
console.error('Failed to update display name:', error)
|
||||
let errorMsg = this.$t('profile.displayNameUpdateError')
|
||||
if (error.response?.data?.error) {
|
||||
errorMsg = error.response.data.error
|
||||
}
|
||||
alert(errorMsg)
|
||||
} finally {
|
||||
this.isUpdating = false
|
||||
}
|
||||
},
|
||||
async updateEmail() {
|
||||
if (!this.emailForm.newEmail) {
|
||||
alert(this.$t('profile.emailRequired'))
|
||||
@@ -287,7 +351,7 @@ export default {
|
||||
})
|
||||
|
||||
this.userProfile.email = response.data.email
|
||||
alert(this.$t('profile.emailUpdateSuccess'))
|
||||
//alert(this.$t('profile.emailUpdateSuccess'))
|
||||
} catch (error) {
|
||||
console.error('Failed to update email:', error)
|
||||
let errorMsg = this.$t('profile.emailUpdateError')
|
||||
|
||||
Reference in New Issue
Block a user