should update values from DataSheetView

This commit is contained in:
2025-12-29 17:07:45 +01:00
parent 3a12b613d9
commit 28b6ed204a
2 changed files with 304 additions and 29 deletions
+9 -2
View File
@@ -14,6 +14,7 @@ import (
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"gorm.io/gorm"
) )
// Character Handlers // Character Handlers
@@ -100,14 +101,20 @@ func UpdateCharacter(c *gin.Context) {
return return
} }
// Store the original ID to preserve it
originalID := character.ID
// Bind the updated data // Bind the updated data
if err := c.ShouldBindJSON(&character); err != nil { if err := c.ShouldBindJSON(&character); err != nil {
respondWithError(c, http.StatusBadRequest, err.Error()) respondWithError(c, http.StatusBadRequest, err.Error())
return return
} }
// Save the updated character // Restore the ID
if err := database.DB.Save(&character).Error; err != nil { character.ID = originalID
// Update all associations
if err := database.DB.Session(&gorm.Session{FullSaveAssociations: true}).Save(&character).Error; err != nil {
respondWithError(c, http.StatusInternalServerError, "Failed to update character") respondWithError(c, http.StatusInternalServerError, "Failed to update character")
return return
} }
+295 -27
View File
@@ -12,7 +12,23 @@
<div class="character-stats"> <div class="character-stats">
<div class="stat" v-for="(stat, index) in characterStats" :key="index"> <div class="stat" v-for="(stat, index) in characterStats" :key="index">
<span>{{ $t(stat.label) }}</span> <span>{{ $t(stat.label) }}</span>
<strong>{{ getStat(stat.path) }}</strong> <strong
v-if="editingIndex !== index"
@dblclick="startEdit(index, stat.path)"
class="editable-value"
>
{{ getStat(stat.path) }}
</strong>
<input
v-else
v-model="editValue"
@blur="saveEdit(stat.path)"
@keyup.enter="saveEdit(stat.path)"
@keyup.esc="cancelEdit"
ref="editInput"
type="number"
class="edit-input"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -21,37 +37,133 @@
<div class="character-info"> <div class="character-info">
<div class="info-section"> <div class="info-section">
<p> <p>
<strong>Aktive Figur?</strong> <span
<strong>Aktuelle Kampagne:</strong> Melzindar v-if="editingProp !== 'typ'"
</p> @dblclick="startEditProp('typ', character.typ)"
<p> class="editable-prop"
{{ character.typ || 'x' }} ({{ character.gender || 'x' }}), >{{ character.typ || 'x' }}</span>
Grad: {{ character.grad || 'x' }}, <input v-else v-model="editPropValue" @blur="saveProp('typ')" @keyup.enter="saveProp('typ')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />
Rasse: {{ character.rasse || 'x' }}, (
Heimat: {{ character.origin || '-' }}, <span
Stand: {{ character.social_class || '-' }}. v-if="editingProp !== 'gender'"
@dblclick="startEditProp('gender', character.gender)"
class="editable-prop"
>{{ character.gender || 'x' }}</span>
<input v-else v-model="editPropValue" @blur="saveProp('gender')" @keyup.enter="saveProp('gender')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />
),
Grad:
<span
v-if="editingProp !== 'grad'"
@dblclick="startEditProp('grad', character.grad, 'number')"
class="editable-prop"
>{{ character.grad || 'x' }}</span>
<input v-else v-model="editPropValue" type="number" @blur="saveProp('grad')" @keyup.enter="saveProp('grad')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />,
Rasse:
<span
v-if="editingProp !== 'rasse'"
@dblclick="startEditProp('rasse', character.rasse)"
class="editable-prop"
>{{ character.rasse || 'x' }}</span>
<input v-else v-model="editPropValue" @blur="saveProp('rasse')" @keyup.enter="saveProp('rasse')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />,
Heimat:
<span
v-if="editingProp !== 'origin'"
@dblclick="startEditProp('origin', character.origin)"
class="editable-prop"
>{{ character.origin || '-' }}</span>
<input v-else v-model="editPropValue" @blur="saveProp('origin')" @keyup.enter="saveProp('origin')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />,
Stand:
<span
v-if="editingProp !== 'social_class'"
@dblclick="startEditProp('social_class', character.social_class)"
class="editable-prop"
>{{ character.social_class || '-' }}</span>
<input v-else v-model="editPropValue" @blur="saveProp('social_class')" @keyup.enter="saveProp('social_class')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />.
</p> </p>
<p v-if="character.rasse==='Zwerg'"> <p v-if="character.rasse==='Zwerg'">
Hort für Grad {{ character.grad || 'x' }}: 125 GS, für nächsten Grad: 250 GS. Hort für Grad {{ character.grad || 'x' }}: 125 GS, für nächsten Grad: 250 GS.
</p> </p>
<p> <p>
<strong>Spezialisierung:</strong> {{ character.spezialisierung || '-'}}. <strong>Spezialisierung:</strong>
<span
v-if="editingProp !== 'spezialisierung'"
@dblclick="startEditProp('spezialisierung', character.spezialisierung)"
class="editable-prop"
>{{ character.spezialisierung || '-' }}</span>
<input v-else v-model="editPropValue" @blur="saveProp('spezialisierung')" @keyup.enter="saveProp('spezialisierung')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />.
</p> </p>
<p> <p>
Alter: {{ character.alter || 'xx' }}, Alter:
<strong v-if="character.hand=='rechts'"> Rechtshänder</strong> <span
<strong v-else-if="character.hand=='links'"> Linkshänder</strong> v-if="editingProp !== 'alter'"
<strong v-else> Beidhändig</strong>, @dblclick="startEditProp('alter', character.alter, 'number')"
Größe: {{ character.groesse }}cm, class="editable-prop"
Gewicht: {{ character.gewicht }}kg, >{{ character.alter || 'xx' }}</span>
Gestalt: {{ character.merkmale?.groesse || '-'}} <input v-else v-model="editPropValue" type="number" @blur="saveProp('alter')" @keyup.enter="saveProp('alter')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />,
und {{ character.merkmale?.breite || '-'}}, <strong v-if="editingProp !== 'hand'" @dblclick="startEditProp('hand', character.hand)" class="editable-prop">
Augen: {{ character.merkmale?.augenfarbe || '-' }}, <span v-if="character.hand=='rechts'">Rechtshänder</span>
Haare: {{ character.merkmale?.haarfarbe || '-' }}, <span v-else-if="character.hand=='links'">Linkshänder</span>
Glaube: {{ character.glaube || '-' }} <span v-else>Beidhändig</span>
</strong>
<input v-else v-model="editPropValue" @blur="saveProp('hand')" @keyup.enter="saveProp('hand')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />,
Größe:
<span
v-if="editingProp !== 'groesse'"
@dblclick="startEditProp('groesse', character.groesse, 'number')"
class="editable-prop"
>{{ character.groesse }}</span>
<input v-else v-model="editPropValue" type="number" @blur="saveProp('groesse')" @keyup.enter="saveProp('groesse')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />cm,
Gewicht:
<span
v-if="editingProp !== 'gewicht'"
@dblclick="startEditProp('gewicht', character.gewicht, 'number')"
class="editable-prop"
>{{ character.gewicht }}</span>
<input v-else v-model="editPropValue" type="number" @blur="saveProp('gewicht')" @keyup.enter="saveProp('gewicht')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />kg,
Gestalt:
<span
v-if="editingProp !== 'merkmale.groesse'"
@dblclick="startEditProp('merkmale.groesse', character.merkmale?.groesse)"
class="editable-prop"
>{{ character.merkmale?.groesse || '-' }}</span>
<input v-else v-model="editPropValue" @blur="saveProp('merkmale.groesse')" @keyup.enter="saveProp('merkmale.groesse')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />
und
<span
v-if="editingProp !== 'merkmale.breite'"
@dblclick="startEditProp('merkmale.breite', character.merkmale?.breite)"
class="editable-prop"
>{{ character.merkmale?.breite || '-' }}</span>
<input v-else v-model="editPropValue" @blur="saveProp('merkmale.breite')" @keyup.enter="saveProp('merkmale.breite')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />,
Augen:
<span
v-if="editingProp !== 'merkmale.augenfarbe'"
@dblclick="startEditProp('merkmale.augenfarbe', character.merkmale?.augenfarbe)"
class="editable-prop"
>{{ character.merkmale?.augenfarbe || '-' }}</span>
<input v-else v-model="editPropValue" @blur="saveProp('merkmale.augenfarbe')" @keyup.enter="saveProp('merkmale.augenfarbe')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />,
Haare:
<span
v-if="editingProp !== 'merkmale.haarfarbe'"
@dblclick="startEditProp('merkmale.haarfarbe', character.merkmale?.haarfarbe)"
class="editable-prop"
>{{ character.merkmale?.haarfarbe || '-' }}</span>
<input v-else v-model="editPropValue" @blur="saveProp('merkmale.haarfarbe')" @keyup.enter="saveProp('merkmale.haarfarbe')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />,
Glaube:
<span
v-if="editingProp !== 'glaube'"
@dblclick="startEditProp('glaube', character.glaube)"
class="editable-prop"
>{{ character.glaube || '-' }}</span>
<input v-else v-model="editPropValue" @blur="saveProp('glaube')" @keyup.enter="saveProp('glaube')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" />
</p> </p>
<p> <p>
<strong>Merkmale:</strong> {{ character.merkmale?.sonstige || '-' }} <strong>Merkmale:</strong>
<span
v-if="editingProp !== 'merkmale.sonstige'"
@dblclick="startEditProp('merkmale.sonstige', character.merkmale?.sonstige)"
class="editable-prop"
>{{ character.merkmale?.sonstige || '-' }}</span>
<input v-else v-model="editPropValue" @blur="saveProp('merkmale.sonstige')" @keyup.enter="saveProp('merkmale.sonstige')" @keyup.esc="cancelEditProp" ref="propInput" class="prop-input" style="width: 300px;" />
</p> </p>
<p> <p>
<em>Persönlicher Bonus für</em> Ausdauer 12, Schaden 5, Angriff 2, <em>Persönlicher Bonus für</em> Ausdauer 12, Schaden 5, Angriff 2,
@@ -66,7 +178,7 @@
<style> <style>
/* DatasheetView spezifische Styles */ /* DatasheetView spezifische Styles */
.datasheet-container { .datasheet-container {
padding-top: 10px; /* Reduziertes oberes Padding */ padding-top: 10px;
} }
.info-section { .info-section {
@@ -82,7 +194,7 @@
.character-overview { .character-overview {
margin-bottom: 30px; margin-bottom: 30px;
margin-top: 0; /* Kein zusätzlicher oberer Margin */ margin-top: 0;
} }
.character-image { .character-image {
@@ -98,11 +210,63 @@
.character-info { .character-info {
margin-top: 20px; margin-top: 20px;
} }
</style>
.editable-value {
cursor: pointer;
padding: 2px 4px;
border-radius: 3px;
transition: background-color 0.2s;
}
.editable-value:hover {
background-color: rgba(0, 123, 255, 0.1);
}
.edit-input {
width: 60px;
padding: 2px 4px;
font-size: inherit;
font-weight: bold;
border: 2px solid var(--primary-color);
border-radius: 3px;
text-align: center;
}
.edit-input:focus {
outline: none;
border-color: #0056b3;
}
.editable-prop {
cursor: pointer;
padding: 1px 3px;
border-radius: 2px;
transition: background-color 0.2s;
display: inline-block;
min-width: 20px;
}
.editable-prop:hover {
background-color: rgba(0, 123, 255, 0.1);
}
.prop-input {
padding: 1px 4px;
font-size: inherit;
border: 1px solid var(--primary-color);
border-radius: 3px;
min-width: 60px;
}
.prop-input:focus {
outline: none;
border-color: #0056b3;
}
</style>
<script> <script>
import ImageUploadCropper from './ImageUploadCropper.vue' import ImageUploadCropper from './ImageUploadCropper.vue'
import API from '../utils/api'
export default { export default {
name: "DatasheetView", name: "DatasheetView",
@@ -124,6 +288,11 @@ export default {
}, },
data() { data() {
return { return {
editingIndex: null,
editValue: '',
editingProp: null,
editPropValue: '',
editPropType: 'text',
characterStats: [ characterStats: [
{ label: 'stats.strength', path: 'eigenschaften.6.value' }, { label: 'stats.strength', path: 'eigenschaften.6.value' },
{ label: 'stats.dexterity', path: 'eigenschaften.1.value' }, { label: 'stats.dexterity', path: 'eigenschaften.1.value' },
@@ -148,10 +317,109 @@ export default {
this.$emit('character-updated') this.$emit('character-updated')
}, },
getStat(path) { getStat(path) {
if (path === 'git' ){ if (path === 'git') {
return '64!' return '64!'
} }
return path.split('.').reduce((obj, key) => obj?.[key], this.character) ?? '-' return path.split('.').reduce((obj, key) => obj?.[key], this.character) ?? '-'
},
startEdit(index, path) {
if (path === 'git') return
this.editingIndex = index
this.editValue = this.getStat(path)
this.$nextTick(() => {
if (this.$refs.editInput && this.$refs.editInput[0]) {
this.$refs.editInput[0].focus()
this.$refs.editInput[0].select()
}
})
},
async saveEdit(path) {
if (this.editingIndex === null) return
const newValue = parseInt(this.editValue)
if (isNaN(newValue)) {
this.cancelEdit()
return
}
try {
// Update the character object directly
const pathParts = path.split('.')
let obj = this.character
for (let i = 0; i < pathParts.length - 1; i++) {
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.cancelEdit()
} catch (error) {
console.error('Failed to update stat:', error)
alert('Fehler beim Speichern: ' + (error.response?.data?.error || error.message))
this.cancelEdit()
}
},
cancelEdit() {
this.editingIndex = null
this.editValue = ''
},
startEditProp(prop, value, type = 'text') {
this.editingProp = prop
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()
input.select()
}
}
})
},
async saveProp(prop) {
if (this.editingProp === null) return
// Update the character object directly
const pathParts = prop.split('.')
let obj = this.character
for (let i = 0; i < pathParts.length - 1; i++) {
if (!obj[pathParts[i]]) {
obj[pathParts[i]] = {}
}
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
}
}
try {
this.$emit('update-property', prop, newValue)
this.cancelEditProp()
} catch (error) {
console.error('Failed to update property:', error)
alert('Fehler beim Speichern: ' + (error.response?.data?.error || error.message))
this.cancelEditProp()
}
},
cancelEditProp() {
this.editingProp = null
this.editPropValue = ''
this.editPropType = 'text'
} }
} }
}; };