Small fixes aside (#29)

* version string was not set in correct place
* add new item buttons and edit masks.
* localized reset password form
* common layout
buttons:
Save left green
Cancel right red

* added help-icon
* setting version improved
make the handling to set versions more convenient. Decoupled setting versions, committing files and tagging.
This commit is contained in:
Bardioc26
2026-02-04 22:18:37 +01:00
committed by GitHub
parent 95f0fc0b7a
commit 6943e678a9
20 changed files with 1431 additions and 216 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ import (
) )
// Version is the application version // Version is the application version
const Version = "0.2.2" const Version = "0.2.3"
var ( var (
// GitCommit will be set by build flags or detected at runtime // GitCommit will be set by build flags or detected at runtime
+20
View File
@@ -4403,3 +4403,23 @@ a:focus {
max-height: 150px; max-height: 150px;
} }
} }
.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;
}
@@ -549,7 +549,7 @@ export default {
color: #666; color: #666;
margin-top: 15px; margin-top: 15px;
} }
/*
.help-icon { .help-icon {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -561,7 +561,6 @@ export default {
border-radius: 50%; border-radius: 50%;
font-size: 12px; font-size: 12px;
line-height: 1; line-height: 1;
/*cursor: help;*/
background: #f5f5f5; background: #f5f5f5;
color: #555; color: #555;
} }
@@ -570,4 +569,5 @@ export default {
background: #e0e0e0; background: #e0e0e0;
color: #222; color: #222;
} }
*/
</style> </style>
@@ -36,6 +36,14 @@
<!-- Character Information --> <!-- Character Information -->
<div class="character-info"> <div class="character-info">
<div class="info-section"> <div class="info-section">
<label for="name"><span
class="help-icon"
:title="$t('characters.datasheet.editHelp')"
role="img"
:aria-label="$t('characters.datasheet.editHelp')"
>
?
</span></label>
<p> <p>
<strong>{{ $t('char') }}:</strong> <strong>{{ $t('char') }}:</strong>
<span <span
+4 -4
View File
@@ -36,13 +36,13 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button @click="closeDialog" class="btn-cancel" :disabled="isExporting"> <button @click="performExport" class="btn-primary btn-save" :disabled="!canExport || isExporting">
{{ $t('export.cancel') }}
</button>
<button @click="performExport" class="btn-export" :disabled="!canExport || isExporting">
<span v-if="!isExporting">{{ $t('export.export') }}</span> <span v-if="!isExporting">{{ $t('export.export') }}</span>
<span v-else>{{ $t('export.exporting') }}</span> <span v-else>{{ $t('export.exporting') }}</span>
</button> </button>
<button @click="closeDialog" class="btn-cancel" :disabled="isExporting">
{{ $t('common.cancel') }}
</button>
</div> </div>
</div> </div>
</div> </div>
+79 -29
View File
@@ -1,57 +1,56 @@
<template> <template>
<div class="fullwidth-page" style="display: flex; justify-content: center; align-items: center; min-height: 100vh;"> <div class="fullwidth-page reset-wrapper">
<div class="card" style="max-width: 400px; width: 100%; margin: 20px;"> <div class="card reset-card">
<div class="page-header"> <div class="page-header">
<h2>Passwort zurücksetzen</h2> <h2>{{ $t('forgotPassword.title') }}</h2>
<p style="color: #666; font-size: 0.9em; margin-top: 10px;"> <p class="reset-description">
Geben Sie Ihre E-Mail-Adresse ein, um einen Reset-Link zu erhalten. {{ $t('forgotPassword.description') }}
</p> </p>
</div> </div>
<form @submit.prevent="requestReset" v-if="!submitted"> <form @submit.prevent="requestReset" v-if="!submitted">
<div class="form-group"> <div class="form-group">
<label for="email">E-Mail-Adresse</label> <label for="email">{{ $t('forgotPassword.emailLabel') }}</label>
<input <input
v-model="email" v-model="email"
type="email" type="email"
id="email" id="email"
name="email" name="email"
class="form-control" class="form-control"
placeholder="ihre@email.de" :placeholder="$t('forgotPassword.emailPlaceholder')"
required required
/> />
</div> </div>
<button <button
type="submit" type="submit"
class="btn btn-primary" class="btn btn-primary reset-button"
style="width: 100%; margin-top: 10px;"
:disabled="isLoading" :disabled="isLoading"
> >
<span v-if="isLoading">Wird gesendet...</span> <span v-if="isLoading">{{ $t('forgotPassword.submitting') }}</span>
<span v-else>Reset-Link senden</span> <span v-else>{{ $t('forgotPassword.submit') }}</span>
</button> </button>
</form> </form>
<div v-if="submitted" class="badge badge-success" style="width: 100%; margin-top: 15px; text-align: center; display: block;"> <div v-if="submitted" class="badge badge-success reset-badge">
<p style="margin: 10px 0;"> <p class="reset-success-title">
<strong>E-Mail gesendet!</strong> <strong>{{ $t('forgotPassword.successTitle') }}</strong>
</p> </p>
<p style="font-size: 0.9em; margin: 5px 0;"> <p class="reset-success-text">
Falls ein Account mit dieser E-Mail-Adresse existiert, wurde ein Reset-Link gesendet. {{ $t('forgotPassword.successInfo') }}
</p> </p>
<p style="font-size: 0.8em; margin: 5px 0; opacity: 0.8;"> <p class="reset-success-hint">
Prüfen Sie Ihre E-Mails und folgen Sie dem Link. {{ $t('forgotPassword.successHint') }}
</p> </p>
</div> </div>
<div v-if="error" class="badge badge-danger" style="width: 100%; margin-top: 15px; text-align: center; display: block;"> <div v-if="error" class="badge badge-danger reset-badge">
{{ error }} {{ error }}
</div> </div>
<div style="text-align: center; margin-top: 20px; padding-top: 15px; border-top: 1px solid #dee2e6;"> <div class="reset-footer">
<router-link to="/" class="btn btn-secondary"> <router-link to="/" class="btn btn-secondary">
Zurück zum Login {{ $t('forgotPassword.backToLogin') }}
</router-link> </router-link>
</div> </div>
</div> </div>
@@ -86,7 +85,7 @@ export default {
console.log('Password reset email requested for:', this.email) console.log('Password reset email requested for:', this.email)
} catch (err) { } catch (err) {
console.error('Password reset request error:', err) console.error('Password reset request error:', err)
this.error = err.response?.data?.error || 'Fehler beim Senden der E-Mail. Versuchen Sie es später erneut.' this.error = err.response?.data?.error || this.$t('forgotPassword.error')
} finally { } finally {
this.isLoading = false this.isLoading = false
} }
@@ -95,6 +94,57 @@ export default {
} }
</script> </script>
<style> <style scoped>
/* All common styles moved to main.css */ .reset-wrapper {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.reset-card {
max-width: 400px;
width: 100%;
margin: 20px;
}
.reset-description {
color: #666;
font-size: 0.9em;
margin-top: 10px;
}
.reset-button {
width: 100%;
margin-top: 10px;
}
.reset-badge {
width: 100%;
margin-top: 15px;
text-align: center;
display: block;
}
.reset-success-title {
margin: 10px 0;
}
.reset-success-text {
font-size: 0.9em;
margin: 5px 0;
}
.reset-success-hint {
font-size: 0.8em;
margin: 5px 0;
opacity: 0.8;
}
.reset-footer {
text-align: center;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #dee2e6;
}
</style> </style>
+5 -5
View File
@@ -111,12 +111,12 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button @click="closeDialog" class="btn-cancel" :disabled="isUpdating"> <button @click="updateVisibilityAndShares" class="btn-primary btn-save" :disabled="isUpdating">
{{ $t('visibility.cancel') }} <span v-if="!isUpdating">{{ $t('common.save') }}</span>
<span v-else>{{ $t('common.saving') }}</span>
</button> </button>
<button @click="updateVisibilityAndShares" class="btn-primary" :disabled="isUpdating"> <button @click="closeDialog" class="btn-cancel" :disabled="isUpdating">
<span v-if="!isUpdating">{{ $t('visibility.save') }}</span> {{ $t('common.cancel') }}
<span v-else>{{ $t('visibility.saving') }}</span>
</button> </button>
</div> </div>
</div> </div>
@@ -7,6 +7,7 @@
type="text" type="text"
:placeholder="$t('search')" :placeholder="$t('search')"
/> />
<button class="btn-primary" @click="startCreate">{{ $t('newEntry') }}</button>
</div> </div>
</div> </div>
@@ -31,6 +32,65 @@
<td colspan="7">{{ $t('common.loading') }}</td> <td colspan="7">{{ $t('common.loading') }}</td>
</tr> </tr>
<tr v-if="creatingNew">
<td>New</td>
<td colspan="6">
<div class="edit-form">
<div class="edit-row">
<label>{{ $t('believe.name') }}</label>
<input v-model="newItem.name" />
</div>
<div class="edit-row">
<label>{{ $t('believe.description') }}</label>
<input v-model="newItem.beschreibung" />
</div>
<div class="edit-row">
<label>{{ $t('believe.source') }}</label>
<select v-model="newItem.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="newItem.page_number" type="number" min="0" />
</div>
<div class="edit-row">
<label>{{ $t('believe.system') }}</label>
<select v-model.number="createSelectedSystemId">
<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 btn-save"
:disabled="isSaving"
@click="saveCreate"
>
<span v-if="!isSaving">{{ $t('common.save') }}</span>
<span v-else>{{ $t('common.saving') }}</span>
</button>
<button
class="btn-cancel"
:disabled="isSaving"
@click="cancelCreate"
>
{{ $t('common.cancel') }}
</button>
</div>
</div>
</td>
</tr>
<template v-for="believe in filteredBelieves" :key="believe.id"> <template v-for="believe in filteredBelieves" :key="believe.id">
<tr v-if="editingId !== believe.id"> <tr v-if="editingId !== believe.id">
<td>{{ believe.id }}</td> <td>{{ believe.id }}</td>
@@ -40,7 +100,7 @@
<td>{{ believe.page_number || '-' }}</td> <td>{{ believe.page_number || '-' }}</td>
<td>{{ getSystemCodeById(believe.game_system_id, believe.game_system) || '-' }}</td> <td>{{ getSystemCodeById(believe.game_system_id, believe.game_system) || '-' }}</td>
<td> <td>
<button @click="startEdit(believe)">{{ $t('believe.edit') }}</button> <button @click="startEdit(believe)">{{ $t('common.edit') }}</button>
</td> </td>
</tr> </tr>
<tr v-else> <tr v-else>
@@ -82,19 +142,19 @@
<div class="edit-actions"> <div class="edit-actions">
<button <button
class="btn-primary" class="btn-primary btn-save"
:disabled="isSaving" :disabled="isSaving"
@click="saveEdit" @click="saveEdit"
> >
<span v-if="!isSaving">{{ $t('believe.save') }}</span> <span v-if="!isSaving">{{ $t('common.save') }}</span>
<span v-else>{{ $t('believe.saving') }}</span> <span v-else>{{ $t('common.saving') }}</span>
</button> </button>
<button <button
class="btn-cancel" class="btn-cancel"
:disabled="isSaving" :disabled="isSaving"
@click="cancelEdit" @click="cancelEdit"
> >
{{ $t('believe.cancel') }} {{ $t('common.cancel') }}
</button> </button>
</div> </div>
</div> </div>
@@ -179,6 +239,9 @@ export default {
editedItem: null, editedItem: null,
gameSystems: [], gameSystems: [],
selectedSystemId: null, selectedSystemId: null,
creatingNew: false,
newItem: null,
createSelectedSystemId: null,
isLoading: false, isLoading: false,
isSaving: false, isSaving: false,
error: '', error: '',
@@ -251,6 +314,23 @@ export default {
findSystemIdByCode(code) { findSystemIdByCode(code) {
return findSystemIdByCode(this.gameSystems, code) return findSystemIdByCode(this.gameSystems, code)
}, },
startCreate() {
this.cancelEdit()
const defaultSystem = this.gameSystems.find(gs => gs.is_active) || this.gameSystems[0] || null
this.createSelectedSystemId = defaultSystem ? defaultSystem.id : null
this.newItem = {
name: '',
beschreibung: '',
sourceCode: '',
page_number: 0
}
this.creatingNew = true
},
cancelCreate() {
this.creatingNew = false
this.newItem = null
this.createSelectedSystemId = null
},
async saveEdit() { async saveEdit() {
if (!this.editedItem || !this.editingId) { if (!this.editedItem || !this.editingId) {
return return
@@ -294,6 +374,45 @@ export default {
} finally { } finally {
this.isSaving = false this.isSaving = false
} }
},
async saveCreate() {
if (!this.newItem) return
const trimmedName = (this.newItem.name || '').trim()
if (!trimmedName) {
alert(this.$t('believe.nameRequired'))
return
}
const selectedSource = this.sources.find(src => src.code === this.newItem.sourceCode)
const selectedSystem = this.gameSystems.find(gs => gs.id === this.createSelectedSystemId)
const payload = {
name: trimmedName,
beschreibung: this.newItem.beschreibung || '',
source_id: selectedSource ? selectedSource.id : null,
page_number: this.newItem.page_number || 0,
game_system_id: selectedSystem ? selectedSystem.id : null,
game_system: selectedSystem ? selectedSystem.code : '',
}
this.isSaving = true
try {
const response = await API.post('/api/maintenance/gsm-believes', payload)
const created = response.data
this.believes.push({
...created,
source_code: this.getSourceCode(created.source_id),
game_system: selectedSystem ? selectedSystem.code : created.game_system,
game_system_id: selectedSystem ? selectedSystem.id : (created.game_system_id ?? null)
})
this.cancelCreate()
} catch (err) {
console.error('Failed to create believe:', err)
this.error = err.response?.data?.error || err.message
} finally {
this.isSaving = false
}
} }
} }
} }
@@ -8,6 +8,7 @@
v-model="searchTerm" v-model="searchTerm"
:placeholder="`${$t('search')} ${$t('Equipment')}...`" :placeholder="`${$t('search')} ${$t('Equipment')}...`"
/> />
<button @click="startCreate" class="btn-primary">{{ $t('newEntry') }}</button>
</div> </div>
</div> </div>
@@ -52,6 +53,68 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-if="creatingNew">
<td>New</td>
<td colspan="8">
<div class="edit-form">
<div class="edit-row">
<div class="edit-field">
<label>{{ $t('equipment.name') }}:</label>
<input v-model="newItem.name" />
</div>
<div class="edit-field">
<label>{{ $t('equipment.gewicht') }}:</label>
<input v-model.number="newItem.gewicht" type="number" style="width:80px;" />
</div>
<div class="edit-field">
<label>{{ $t('equipment.wert') }}:</label>
<input v-model="newItem.wert" style="width:100px;" />
</div>
</div>
<div class="edit-row">
<div class="edit-field full-width">
<label>{{ $t('equipment.description') }}:</label>
<input v-model="newItem.beschreibung" />
</div>
</div>
<div class="edit-row">
<div class="edit-field">
<label>{{ $t('equipment.quelle') }}:</label>
<select v-model="newItem.sourceCode" style="width:100px;">
<option value="">-</option>
<option v-for="source in availableSources" :key="source.code" :value="source.code">
{{ source.code }}
</option>
</select>
</div>
<div class="edit-field">
<label>{{ $t('equipment.page') || 'Page' }}:</label>
<input v-model.number="newItem.page_number" type="number" style="width:60px;" />
</div>
<div class="edit-field">
<label>{{ $t('equipment.personal_item') }}:</label>
<input type="checkbox" v-model="newItem.personal_item" />
</div>
<div class="edit-field">
<label>{{ $t('equipment.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-actions">
<button @click="saveCreate" class="btn-save">{{ $t('common.save') }}</button>
<button @click="cancelCreate" class="btn-cancel">{{ $t('common.cancel') }}</button>
</div>
</div>
</td>
</tr>
<template v-for="(dtaItem, index) in filteredAndSortedEquipments" :key="dtaItem.id"> <template v-for="(dtaItem, index) in filteredAndSortedEquipments" :key="dtaItem.id">
<tr v-if="editingIndex !== index"> <tr v-if="editingIndex !== index">
<td>{{ dtaItem.id || '' }}</td> <td>{{ dtaItem.id || '' }}</td>
@@ -64,7 +127,7 @@
<td><input type="checkbox" :checked="dtaItem.personal_item" disabled /></td> <td><input type="checkbox" :checked="dtaItem.personal_item" disabled /></td>
<td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.system || 'midgard') }}</td> <td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.system || 'midgard') }}</td>
<td> <td>
<button @click="startEdit(index)">Edit</button> <button @click="startEdit(index)">{{ $t('common.edit') }}</button>
</td> </td>
</tr> </tr>
<!-- Edit Mode --> <!-- Edit Mode -->
@@ -125,8 +188,8 @@
</div> </div>
<div class="edit-actions"> <div class="edit-actions">
<button @click="saveEdit(index)" class="btn-save">Save</button> <button @click="saveEdit(index)" class="btn-save">{{ $t('common.save') }}</button>
<button @click="cancelEdit" class="btn-cancel">Cancel</button> <button @click="cancelEdit" class="btn-cancel">{{ $t('common.cancel') }}</button>
</div> </div>
</div> </div>
</td> </td>
@@ -216,7 +279,10 @@ export default {
enhancedEquipment: [], enhancedEquipment: [],
availableSources: [], availableSources: [],
gameSystems: [], gameSystems: [],
selectedSystemId: null selectedSystemId: null,
creatingNew: false,
newItem: null,
createSelectedSystemId: null
} }
}, },
async created() { async created() {
@@ -352,6 +418,57 @@ export default {
this.editedItem = null; this.editedItem = null;
this.selectedSystemId = null; this.selectedSystemId = null;
}, },
startCreate() {
this.cancelEdit()
const defaultSystem = this.gameSystems.find(gs => gs.is_active) || this.gameSystems[0] || null
this.createSelectedSystemId = defaultSystem ? defaultSystem.id : null
this.newItem = {
name: '',
gewicht: 0,
wert: '',
beschreibung: '',
sourceCode: '',
page_number: 0,
personal_item: false,
system: defaultSystem ? defaultSystem.code : ''
}
this.creatingNew = true
},
cancelCreate() {
this.creatingNew = false
this.newItem = null
this.createSelectedSystemId = null
},
async saveCreate() {
if (!this.newItem) return
try {
const source = this.availableSources.find(s => s.code === this.newItem.sourceCode)
const selectedSystem = this.gameSystems.find(gs => gs.id === this.createSelectedSystemId)
const createData = {
name: this.newItem.name,
gewicht: this.newItem.gewicht ?? 0,
wert: this.newItem.wert || '',
beschreibung: this.newItem.beschreibung || '',
source_id: source ? source.id : null,
page_number: this.newItem.page_number || 0,
personal_item: !!this.newItem.personal_item,
system: selectedSystem ? selectedSystem.code : (this.newItem.system || ''),
game_system_id: selectedSystem ? selectedSystem.id : null
}
const response = await API.post(
'/api/maintenance/equipment-enhanced',
createData
)
this.enhancedEquipment.push(response.data)
this.cancelCreate()
} catch (error) {
console.error('Failed to create equipment:', error)
alert('Failed to create equipment: ' + (error.response?.data?.error || error.message))
}
},
findSystemIdByCode(code) { findSystemIdByCode(code) {
return findSystemIdByCode(this.gameSystems, code) return findSystemIdByCode(this.gameSystems, code)
}, },
@@ -3,6 +3,7 @@
<h2>{{ $t('maintenance') }} - {{ $t('gamesystem.title') }}</h2> <h2>{{ $t('maintenance') }} - {{ $t('gamesystem.title') }}</h2>
<div class="search-box"> <div class="search-box">
<input v-model="searchTerm" type="text" :placeholder="$t('search')" /> <input v-model="searchTerm" type="text" :placeholder="$t('search')" />
<button class="btn-primary" @click="startCreate">{{ $t('newEntry') }}</button>
</div> </div>
</div> </div>
@@ -26,6 +27,39 @@
<td colspan="6">{{ $t('common.loading') }}</td> <td colspan="6">{{ $t('common.loading') }}</td>
</tr> </tr>
<tr v-if="creatingNew">
<td>New</td>
<td colspan="5">
<div class="edit-form">
<div class="edit-row">
<label>{{ $t('gamesystem.code') }}</label>
<input v-model="newItem.code" />
</div>
<div class="edit-row">
<label>{{ $t('gamesystem.name') }}</label>
<input v-model="newItem.name" />
</div>
<div class="edit-row">
<label>{{ $t('gamesystem.description') }}</label>
<input v-model="newItem.description" />
</div>
<div class="edit-row">
<label>{{ $t('gamesystem.active') }}</label>
<input type="checkbox" v-model="newItem.is_active" />
</div>
<div class="edit-actions">
<button class="btn-primary btn-save" :disabled="isSaving" @click="saveCreate">
<span v-if="!isSaving">{{ $t('common.save') }}</span>
<span v-else>{{ $t('common.saving') }}</span>
</button>
<button class="btn-cancel" :disabled="isSaving" @click="cancelCreate">
{{ $t('common.cancel') }}
</button>
</div>
</div>
</td>
</tr>
<template v-for="gs in filteredSystems" :key="gs.id"> <template v-for="gs in filteredSystems" :key="gs.id">
<tr v-if="editingId !== gs.id"> <tr v-if="editingId !== gs.id">
<td>{{ gs.id }}</td> <td>{{ gs.id }}</td>
@@ -33,7 +67,7 @@
<td>{{ gs.name }}</td> <td>{{ gs.name }}</td>
<td>{{ gs.description || '-' }}</td> <td>{{ gs.description || '-' }}</td>
<td><input type="checkbox" :checked="gs.is_active" disabled /></td> <td><input type="checkbox" :checked="gs.is_active" disabled /></td>
<td><button @click="startEdit(gs)">{{ $t('gamesystem.edit') }}</button></td> <td><button @click="startEdit(gs)">{{ $t('common.edit') }}</button></td>
</tr> </tr>
<tr v-else> <tr v-else>
<td>{{ gs.id }}</td> <td>{{ gs.id }}</td>
@@ -53,12 +87,12 @@
<input type="checkbox" v-model="editedItem.is_active" /> <input type="checkbox" v-model="editedItem.is_active" />
</div> </div>
<div class="edit-actions"> <div class="edit-actions">
<button class="btn-primary" :disabled="isSaving" @click="saveEdit"> <button class="btn-primary btn-save" :disabled="isSaving" @click="saveEdit">
<span v-if="!isSaving">{{ $t('gamesystem.save') }}</span> <span v-if="!isSaving">{{ $t('common.save') }}</span>
<span v-else>{{ $t('gamesystem.saving') }}</span> <span v-else>{{ $t('common.saving') }}</span>
</button> </button>
<button class="btn-cancel" :disabled="isSaving" @click="cancelEdit"> <button class="btn-cancel" :disabled="isSaving" @click="cancelEdit">
{{ $t('gamesystem.cancel') }} {{ $t('common.cancel') }}
</button> </button>
</div> </div>
</div> </div>
@@ -107,6 +141,8 @@ export default {
systems: [], systems: [],
editingId: null, editingId: null,
editedItem: null, editedItem: null,
creatingNew: false,
newItem: null,
isLoading: false, isLoading: false,
isSaving: false, isSaving: false,
error: '', error: '',
@@ -149,6 +185,20 @@ export default {
this.editingId = null this.editingId = null
this.editedItem = null this.editedItem = null
}, },
startCreate() {
this.cancelEdit()
this.newItem = {
code: '',
name: '',
description: '',
is_active: true,
}
this.creatingNew = true
},
cancelCreate() {
this.creatingNew = false
this.newItem = null
},
async saveEdit() { async saveEdit() {
if (!this.editedItem) return if (!this.editedItem) return
const payload = { const payload = {
@@ -169,6 +219,32 @@ export default {
this.isSaving = false this.isSaving = false
} }
}, },
async saveCreate() {
if (!this.newItem) return
const code = (this.newItem.code || '').trim()
const name = (this.newItem.name || '').trim()
if (!code || !name) {
alert(this.$t('gamesystem.code') + ' / ' + this.$t('gamesystem.name'))
return
}
const payload = {
code,
name,
description: this.newItem.description || '',
is_active: !!this.newItem.is_active,
}
this.isSaving = true
try {
const resp = await API.post('/api/maintenance/game-systems', payload)
this.systems.push(resp.data)
this.cancelCreate()
} catch (err) {
console.error('Failed to create game system:', err)
this.error = err.response?.data?.error || err.message
} finally {
this.isSaving = false
}
},
}, },
} }
</script> </script>
@@ -12,6 +12,7 @@
</option> </option>
</select> </select>
</div> </div>
<button class="btn-primary" @click="startCreate">{{ $t('newEntry') }}</button>
</div> </div>
<div v-if="error" class="error-box">{{ error }}</div> <div v-if="error" class="error-box">{{ error }}</div>
@@ -38,6 +39,60 @@
<td colspan="10">{{ $t('common.loading') }}</td> <td colspan="10">{{ $t('common.loading') }}</td>
</tr> </tr>
<tr v-if="creatingNew">
<td>New</td>
<td colspan="9">
<div class="edit-form">
<div class="edit-row">
<label>{{ $t('litsource.code') }}</label>
<input v-model="newItem.code" />
<label class="inline-label">{{ $t('litsource.name') }}</label>
<input v-model="newItem.name" />
</div>
<div class="edit-row">
<label>{{ $t('litsource.fullName') }}</label>
<input v-model="newItem.full_name" />
</div>
<div class="edit-row">
<label>{{ $t('litsource.edition') }}</label>
<input v-model="newItem.edition" />
<label class="inline-label">{{ $t('litsource.publisher') }}</label>
<input v-model="newItem.publisher" />
<label class="inline-label">{{ $t('litsource.year') }}</label>
<input v-model.number="newItem.publish_year" type="number" />
</div>
<div class="edit-row">
<label>{{ $t('litsource.description') }}</label>
<input v-model="newItem.description" />
</div>
<div class="edit-row">
<label>{{ $t('litsource.active') }}</label>
<input type="checkbox" v-model="newItem.is_active" />
<label class="inline-label">{{ $t('litsource.core') }}</label>
<input type="checkbox" v-model="newItem.is_core" />
</div>
<div class="edit-row">
<label>{{ $t('gamesystem.title') }}</label>
<select v-model.number="createSelectedSystemId">
<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 btn-save" :disabled="isSaving" @click="saveCreate">
<span v-if="!isSaving">{{ $t('common.save') }}</span>
<span v-else>{{ $t('common.saving') }}</span>
</button>
<button class="btn-cancel" :disabled="isSaving" @click="cancelCreate">
{{ $t('common.cancel') }}
</button>
</div>
</div>
</td>
</tr>
<template v-for="src in filteredSources" :key="src.id"> <template v-for="src in filteredSources" :key="src.id">
<tr v-if="editingId !== src.id"> <tr v-if="editingId !== src.id">
<td>{{ src.id }}</td> <td>{{ src.id }}</td>
@@ -49,7 +104,7 @@
<td>{{ src.publish_year }}</td> <td>{{ src.publish_year }}</td>
<td><input type="checkbox" :checked="src.is_active" disabled /></td> <td><input type="checkbox" :checked="src.is_active" disabled /></td>
<td><input type="checkbox" :checked="src.is_core" disabled /></td> <td><input type="checkbox" :checked="src.is_core" disabled /></td>
<td><button @click="startEdit(src)">{{ $t('litsource.edit') }}</button></td> <td><button @click="startEdit(src)">{{ $t('common.edit') }}</button></td>
</tr> </tr>
<tr v-else> <tr v-else>
<td>{{ src.id }}</td> <td>{{ src.id }}</td>
@@ -92,12 +147,12 @@
</select> </select>
</div> </div>
<div class="edit-actions"> <div class="edit-actions">
<button class="btn-primary" :disabled="isSaving" @click="saveEdit"> <button class="btn-primary btn-save" :disabled="isSaving" @click="saveEdit">
<span v-if="!isSaving">{{ $t('litsource.save') }}</span> <span v-if="!isSaving">{{ $t('common.save') }}</span>
<span v-else>{{ $t('litsource.saving') }}</span> <span v-else>{{ $t('common.saving') }}</span>
</button> </button>
<button class="btn-cancel" :disabled="isSaving" @click="cancelEdit"> <button class="btn-cancel" :disabled="isSaving" @click="cancelEdit">
{{ $t('litsource.cancel') }} {{ $t('common.cancel') }}
</button> </button>
</div> </div>
</div> </div>
@@ -159,6 +214,9 @@ export default {
sources: [], sources: [],
editingId: null, editingId: null,
editedItem: null, editedItem: null,
creatingNew: false,
newItem: null,
createSelectedSystemId: null,
isLoading: false, isLoading: false,
isSaving: false, isSaving: false,
error: '', error: '',
@@ -237,6 +295,28 @@ export default {
this.loadSources() this.loadSources()
} }
}, },
startCreate() {
this.cancelEdit()
const defaultSystem = this.currentGameSystem || this.gameSystems.find(s => s.is_active) || this.gameSystems[0] || null
this.createSelectedSystemId = defaultSystem ? defaultSystem.id : null
this.newItem = {
code: '',
name: '',
full_name: '',
edition: '',
publisher: '',
publish_year: 0,
description: '',
is_active: true,
is_core: false,
}
this.creatingNew = true
},
cancelCreate() {
this.creatingNew = false
this.newItem = null
this.createSelectedSystemId = this.currentGameSystem ? this.currentGameSystem.id : null
},
findSystemById(id) { findSystemById(id) {
return findSystemById(this.gameSystems, id) return findSystemById(this.gameSystems, id)
}, },
@@ -272,6 +352,35 @@ export default {
this.isSaving = false this.isSaving = false
} }
}, },
async saveCreate() {
if (!this.newItem) return
const targetSystem = this.findSystemById(this.createSelectedSystemId) || this.currentGameSystem
const payload = {
code: this.newItem.code || '',
name: this.newItem.name || '',
full_name: this.newItem.full_name || '',
edition: this.newItem.edition || '',
publisher: this.newItem.publisher || '',
publish_year: this.newItem.publish_year || 0,
description: this.newItem.description || '',
is_active: !!this.newItem.is_active,
is_core: !!this.newItem.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.post('/api/maintenance/gsm-lit-sources', payload, { params })
this.sources.push(resp.data)
this.cancelCreate()
} catch (err) {
console.error('Failed to create source:', err)
this.error = err.response?.data?.error || err.message
} finally {
this.isSaving = false
}
},
}, },
} }
</script> </script>
@@ -3,6 +3,11 @@
<h2>{{ $t('maintenance') }} - {{ $t('misc.title') }}</h2> <h2>{{ $t('maintenance') }} - {{ $t('misc.title') }}</h2>
<div class="search-box"> <div class="search-box">
<input v-model="searchTerm" type="text" :placeholder="$t('search')" /> <input v-model="searchTerm" type="text" :placeholder="$t('search')" />
<select v-model="filterKey">
<option value="">{{ $t('all') || 'All' }}</option>
<option v-for="key in keyOptions" :key="key" :value="key">{{ key }}</option>
</select>
<button class="btn-primary" @click="startCreate">{{ $t('newEntry') }}</button>
</div> </div>
</div> </div>
@@ -27,6 +32,49 @@
<td colspan="7">{{ $t('common.loading') }}</td> <td colspan="7">{{ $t('common.loading') }}</td>
</tr> </tr>
<tr v-if="creatingNew">
<td>New</td>
<td colspan="6">
<div class="edit-form">
<div class="edit-row">
<label>{{ $t('misc.key') }}</label>
<input v-model="newItem.key" list="misc-key-options" />
<datalist id="misc-key-options">
<option v-for="key in keyOptions" :key="key" :value="key" />
</datalist>
<label class="inline-label">{{ $t('misc.value') }}</label>
<input v-model="newItem.value" />
</div>
<div class="edit-row">
<label>{{ $t('misc.source') }}</label>
<select v-model.number="newItem.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="newItem.page_number" type="number" min="0" />
</div>
<div class="edit-row">
<label>{{ $t('misc.system') }}</label>
<select v-model.number="createSelectedSystemId">
<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 btn-save" :disabled="isSaving" @click="saveCreate">
<span v-if="!isSaving">{{ $t('common.save') }}</span>
<span v-else>{{ $t('common.saving') }}</span>
</button>
<button class="btn-cancel" :disabled="isSaving" @click="cancelCreate">
{{ $t('common.cancel') }}
</button>
</div>
</div>
</td>
</tr>
<template v-for="item in filteredItems" :key="item.id"> <template v-for="item in filteredItems" :key="item.id">
<tr v-if="editingId !== item.id"> <tr v-if="editingId !== item.id">
<td>{{ item.id }}</td> <td>{{ item.id }}</td>
@@ -35,7 +83,7 @@
<td>{{ sourceCodeFor(item.source_id) }}</td> <td>{{ sourceCodeFor(item.source_id) }}</td>
<td>{{ item.page_number || '-' }}</td> <td>{{ item.page_number || '-' }}</td>
<td>{{ systemCodeFor(item.game_system_id, item.game_system) || '-' }}</td> <td>{{ systemCodeFor(item.game_system_id, item.game_system) || '-' }}</td>
<td><button @click="startEdit(item)">{{ $t('misc.edit') }}</button></td> <td><button @click="startEdit(item)">{{ $t('common.edit') }}</button></td>
</tr> </tr>
<tr v-else> <tr v-else>
<td>{{ item.id }}</td> <td>{{ item.id }}</td>
@@ -68,12 +116,12 @@
</select> </select>
</div> </div>
<div class="edit-actions"> <div class="edit-actions">
<button class="btn-primary" :disabled="isSaving" @click="saveEdit"> <button class="btn-primary btn-save" :disabled="isSaving" @click="saveEdit">
<span v-if="!isSaving">{{ $t('misc.save') }}</span> <span v-if="!isSaving">{{ $t('common.save') }}</span>
<span v-else>{{ $t('misc.saving') }}</span> <span v-else>{{ $t('common.saving') }}</span>
</button> </button>
<button class="btn-cancel" :disabled="isSaving" @click="cancelEdit"> <button class="btn-cancel" :disabled="isSaving" @click="cancelEdit">
{{ $t('misc.cancel') }} {{ $t('common.cancel') }}
</button> </button>
</div> </div>
</div> </div>
@@ -136,10 +184,14 @@ export default {
editingId: null, editingId: null,
editedItem: null, editedItem: null,
selectedSystemId: null, selectedSystemId: null,
creatingNew: false,
newItem: null,
createSelectedSystemId: null,
isLoading: false, isLoading: false,
isSaving: false, isSaving: false,
error: '', error: '',
searchTerm: '', searchTerm: '',
filterKey: '',
} }
}, },
async created() { async created() {
@@ -148,12 +200,17 @@ export default {
computed: { computed: {
filteredItems() { filteredItems() {
const term = this.searchTerm.trim().toLowerCase() const term = this.searchTerm.trim().toLowerCase()
const list = term let list = term
? this.items.filter(it => ? this.items.filter(it =>
(it.key || '').toLowerCase().includes(term) || (it.key || '').toLowerCase().includes(term) ||
(it.value || '').toLowerCase().includes(term) (it.value || '').toLowerCase().includes(term)
) )
: this.items : this.items
if (this.filterKey) {
list = list.filter(it => it.key === this.filterKey)
}
return [...list].sort((a, b) => (a.key || '').localeCompare(b.key || '')) return [...list].sort((a, b) => (a.key || '').localeCompare(b.key || ''))
}, },
keyOptions() { keyOptions() {
@@ -199,6 +256,23 @@ export default {
await this.loadSources() await this.loadSources()
await this.loadItems() await this.loadItems()
}, },
startCreate() {
this.cancelEdit()
const defaultSystem = this.currentGameSystem || this.gameSystems.find(gs => gs.is_active) || this.gameSystems[0] || null
this.createSelectedSystemId = defaultSystem ? defaultSystem.id : null
this.newItem = {
key: '',
value: '',
source_id: null,
page_number: 0,
}
this.creatingNew = true
},
cancelCreate() {
this.creatingNew = false
this.newItem = null
this.createSelectedSystemId = null
},
async loadGameSystems() { async loadGameSystems() {
try { try {
const systems = await fetchGameSystems() const systems = await fetchGameSystems()
@@ -279,6 +353,27 @@ export default {
this.isSaving = false this.isSaving = false
} }
}, },
async saveCreate() {
if (!this.newItem) return
const payload = {
key: this.newItem.key || '',
value: this.newItem.value || '',
source_id: this.newItem.source_id || null,
page_number: this.newItem.page_number || 0,
}
this.isSaving = true
try {
const params = this.buildParamsForSystemId(this.createSelectedSystemId)
const resp = await API.post('/api/maintenance/gsm-misc', payload, { params })
this.items.push(resp.data)
this.cancelCreate()
} catch (err) {
console.error('Failed to create misc entry:', err)
this.error = err.response?.data?.error || err.message
} finally {
this.isSaving = false
}
},
}, },
} }
</script> </script>
@@ -1,6 +1,7 @@
<template> <template>
<div class="header-section"> <div class="header-section">
<h2>{{ $t('maintenance') }} - {{ $t('skillimprovement.title') }}</h2> <h2>{{ $t('maintenance') }} - {{ $t('skillimprovement.title') }}</h2>
<button class="btn-primary" @click="startCreate">{{ $t('newEntry') }}</button>
</div> </div>
<div v-if="error" class="error-box">{{ error }}</div> <div v-if="error" class="error-box">{{ error }}</div>
@@ -22,6 +23,42 @@
<tr v-if="isLoading"> <tr v-if="isLoading">
<td colspan="6">{{ $t('common.loading') }}</td> <td colspan="6">{{ $t('common.loading') }}</td>
</tr> </tr>
<tr v-if="creatingNew">
<td>New</td>
<td colspan="5">
<div class="edit-form">
<div class="edit-row">
<label>{{ $t('skillimprovement.level') }}</label>
<input v-model.number="newItem.current_level" type="number" />
<label class="inline-label">{{ $t('skillimprovement.te') }}</label>
<input v-model.number="newItem.te_required" type="number" />
</div>
<div class="edit-row">
<label>{{ $t('skillimprovement.category') }}</label>
<select v-model.number="newItem.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="newItem.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 btn-save" :disabled="isSaving" @click="saveCreate">
<span v-if="!isSaving">{{ $t('common.save') }}</span>
<span v-else>{{ $t('common.saving') }}</span>
</button>
<button class="btn-cancel" :disabled="isSaving" @click="cancelCreate">
{{ $t('common.cancel') }}
</button>
</div>
</div>
</td>
</tr>
<template v-for="cost in costs" :key="cost.id"> <template v-for="cost in costs" :key="cost.id">
<tr v-if="editingId !== cost.id"> <tr v-if="editingId !== cost.id">
<td>{{ cost.id }}</td> <td>{{ cost.id }}</td>
@@ -29,7 +66,7 @@
<td>{{ cost.te_required }}</td> <td>{{ cost.te_required }}</td>
<td>{{ displayCategory(cost) }}</td> <td>{{ displayCategory(cost) }}</td>
<td>{{ displayDifficulty(cost) }}</td> <td>{{ displayDifficulty(cost) }}</td>
<td><button @click="startEdit(cost)">{{ $t('skillimprovement.edit') }}</button></td> <td><button @click="startEdit(cost)">{{ $t('common.edit') }}</button></td>
</tr> </tr>
<tr v-else> <tr v-else>
<td>{{ cost.id }}</td> <td>{{ cost.id }}</td>
@@ -56,12 +93,12 @@
</select> </select>
</div> </div>
<div class="edit-actions"> <div class="edit-actions">
<button class="btn-primary" :disabled="isSaving" @click="saveEdit"> <button class="btn-primary btn-save" :disabled="isSaving" @click="saveEdit">
<span v-if="!isSaving">{{ $t('skillimprovement.save') }}</span> <span v-if="!isSaving">{{ $t('common.save') }}</span>
<span v-else>{{ $t('skillimprovement.saving') }}</span> <span v-else>{{ $t('common.saving') }}</span>
</button> </button>
<button class="btn-cancel" :disabled="isSaving" @click="cancelEdit"> <button class="btn-cancel" :disabled="isSaving" @click="cancelEdit">
{{ $t('skillimprovement.cancel') }} {{ $t('common.cancel') }}
</button> </button>
</div> </div>
</div> </div>
@@ -113,6 +150,8 @@ export default {
costs: [], costs: [],
editingId: null, editingId: null,
editedItem: null, editedItem: null,
creatingNew: false,
newItem: null,
isLoading: false, isLoading: false,
isSaving: false, isSaving: false,
error: '', error: '',
@@ -177,6 +216,22 @@ export default {
this.editingId = null this.editingId = null
this.editedItem = null this.editedItem = null
}, },
startCreate() {
this.cancelEdit()
const defaultCategory = this.categoryOptions[0]?.id ?? null
const defaultDifficulty = this.difficultyOptions[0]?.id ?? null
this.newItem = {
current_level: 0,
te_required: 0,
category_id: defaultCategory,
difficulty_id: defaultDifficulty,
}
this.creatingNew = true
},
cancelCreate() {
this.creatingNew = false
this.newItem = null
},
async saveEdit() { async saveEdit() {
if (!this.editedItem) return if (!this.editedItem) return
const payload = { const payload = {
@@ -198,6 +253,26 @@ export default {
this.isSaving = false this.isSaving = false
} }
}, },
async saveCreate() {
if (!this.newItem) return
const payload = {
current_level: this.newItem.current_level,
te_required: this.newItem.te_required,
category_id: this.newItem.category_id,
difficulty_id: this.newItem.difficulty_id,
}
this.isSaving = true
try {
const resp = await API.post('/api/maintenance/skill-improvement-cost2', payload)
this.costs.push(resp.data)
this.cancelCreate()
} catch (err) {
console.error('Failed to create cost:', err)
this.error = err.response?.data?.error || err.message
} finally {
this.isSaving = false
}
},
}, },
} }
</script> </script>
@@ -209,8 +209,8 @@
</div> </div>
<div class="edit-actions"> <div class="edit-actions">
<button @click="saveCreate" class="btn-save">{{ $t('createSkill') }}</button> <button @click="saveCreate" class="btn-save">{{ $t('common.save') }}</button>
<button @click="cancelCreate" class="btn-cancel">Cancel</button> <button @click="cancelCreate" class="btn-cancel">{{ $t('common.cancel') }}</button>
</div> </div>
</div> </div>
</td> </td>
@@ -232,7 +232,7 @@
<td>{{ formatQuelle(dtaItem) }}</td> <td>{{ formatQuelle(dtaItem) }}</td>
<td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.game_system || 'midgard') }}</td> <td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.game_system || 'midgard') }}</td>
<td> <td>
<button @click="startEdit(index)">Edit</button> <button @click="startEdit(index)">{{ $t('common.edit') }}</button>
</td> </td>
</tr> </tr>
@@ -340,8 +340,8 @@
</div> </div>
<div class="edit-actions"> <div class="edit-actions">
<button @click="saveEdit(index)" class="btn-save">Save</button> <button @click="saveEdit(index)" class="btn-save">{{ $t('common.save') }}</button>
<button @click="cancelEdit" class="btn-cancel">Cancel</button> <button @click="cancelEdit" class="btn-cancel">{{ $t('common.cancel') }}</button>
</div> </div>
</div> </div>
</td> </td>
+200 -21
View File
@@ -8,6 +8,7 @@
v-model="searchTerm" v-model="searchTerm"
:placeholder="`${$t('search')} ${$t('Spell')}...`" :placeholder="`${$t('search')} ${$t('Spell')}...`"
/> />
<button @click="startCreate" class="btn-primary">{{ $t('newEntry') }}</button>
</div> </div>
</div> </div>
@@ -39,6 +40,32 @@
{{ $t('spell.import') || 'Import Spells' }} {{ $t('spell.import') || 'Import Spells' }}
</button> </button>
<div v-if="importResult" class="import-result" :class="importResult.success ? 'success' : 'error'"> <div v-if="importResult" class="import-result" :class="importResult.success ? 'success' : 'error'">
async saveCreate() {
if (!this.newItem) return
try {
const source = this.availableSources.find(s => s.code === this.newItem.sourceCode)
const selectedSystem = this.gameSystems.find(gs => gs.id === this.createSelectedSystemId)
const createData = {
...this.newItem,
source_id: source ? source.id : null,
page_number: this.newItem.page_number || 0,
system: selectedSystem ? selectedSystem.code : (this.newItem.system || ''),
game_system_id: selectedSystem ? selectedSystem.id : null
}
const response = await API.post(
'/api/maintenance/spells-enhanced',
createData
)
this.enhancedSpells.push(response.data)
this.cancelCreate()
} catch (error) {
console.error('Failed to create spell:', error)
alert('Failed to create spell: ' + (error.response?.data?.error || error.message))
}
},
{{ importResult.message }} {{ importResult.message }}
<span v-if="importResult.total_spells"> ({{ importResult.total_spells }} spells total)</span> <span v-if="importResult.total_spells"> ({{ importResult.total_spells }} spells total)</span>
</div> </div>
@@ -121,6 +148,102 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-if="creatingNew">
<td>New</td>
<td colspan="14">
<div class="edit-form">
<div class="edit-row">
<div class="edit-field">
<label>{{ $t('spell.name') }}:</label>
<input v-model="newItem.name" />
</div>
<div class="edit-field">
<label>{{ $t('spell.category') }}:</label>
<select v-model="newItem.category" style="width:120px;">
<option v-for="category in mdata['spellcategories']" :key="category" :value="category">
{{ category }}
</option>
</select>
</div>
<div class="edit-field">
<label>{{ $t('spell.level') }}:</label>
<input v-model.number="newItem.level" type="number" style="width:60px;" />
</div>
<div class="edit-field">
<label>{{ $t('spell.apverbrauch') }}:</label>
<input v-model="newItem.ap" style="width:60px;" />
</div>
</div>
<div class="edit-row">
<div class="edit-field">
<label>{{ $t('spell.zauberdauer') }}:</label>
<input v-model="newItem.zauberdauer" style="width:120px;" />
</div>
<div class="edit-field">
<label>{{ $t('spell.reichweite') }}:</label>
<input v-model="newItem.reichweite" style="width:120px;" />
</div>
<div class="edit-field">
<label>{{ $t('spell.wirkungsdauer') }}:</label>
<input v-model="newItem.wirkungsdauer" style="width:120px;" />
</div>
</div>
<div class="edit-row">
<div class="edit-field">
<label>{{ $t('spell.wirkungsziel') }}:</label>
<input v-model="newItem.wirkungsziel" style="width:150px;" />
</div>
<div class="edit-field">
<label>{{ $t('spell.wirkungsbereich') }}:</label>
<input v-model="newItem.wirkungsbereich" style="width:150px;" />
</div>
<div class="edit-field">
<label>{{ $t('spell.ursprung') }}:</label>
<input v-model="newItem.ursprung" style="width:120px;" />
</div>
</div>
<div class="edit-row">
<div class="edit-field full-width">
<label>{{ $t('spell.description') }}:</label>
<input v-model="newItem.beschreibung" />
</div>
</div>
<div class="edit-row">
<div class="edit-field">
<label>{{ $t('spell.quelle') }}:</label>
<select v-model="newItem.sourceCode" style="width:100px;">
<option value="">-</option>
<option v-for="source in availableSources" :key="source.code" :value="source.code">
{{ source.code }}
</option>
</select>
</div>
<div class="edit-field">
<label>{{ $t('spell.page') || 'Page' }}:</label>
<input v-model.number="newItem.page_number" type="number" style="width:60px;" />
</div>
<div class="edit-field">
<label>{{ $t('spell.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-actions">
<button @click="saveCreate" class="btn-save">{{ $t('common.save') }}</button>
<button @click="cancelCreate" class="btn-cancel">{{ $t('common.cancel') }}</button>
</div>
</div>
</td>
</tr>
<template v-for="(dtaItem, index) in filteredAndSortedSpells" :key="dtaItem.id"> <template v-for="(dtaItem, index) in filteredAndSortedSpells" :key="dtaItem.id">
<tr v-if="editingIndex !== index"> <tr v-if="editingIndex !== index">
<td>{{ dtaItem.id || '' }}</td> <td>{{ dtaItem.id || '' }}</td>
@@ -138,7 +261,7 @@
<td>{{ formatQuelle(dtaItem) }}</td> <td>{{ formatQuelle(dtaItem) }}</td>
<td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.system || 'midgard') }}</td> <td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.system || 'midgard') }}</td>
<td> <td>
<button @click="startEdit(index)">Edit</button> <button @click="startEdit(index)">{{ $t('common.edit') }}</button>
</td> </td>
</tr> </tr>
<!-- Edit Mode --> <!-- Edit Mode -->
@@ -233,8 +356,8 @@
</div> </div>
<div class="edit-actions"> <div class="edit-actions">
<button @click="saveEdit(index)" class="btn-save">Save</button> <button @click="saveEdit(index)" class="btn-save">{{ $t('common.save') }}</button>
<button @click="cancelEdit" class="btn-cancel">Cancel</button> <button @click="cancelEdit" class="btn-cancel">{{ $t('common.cancel') }}</button>
</div> </div>
</div> </div>
</td> </td>
@@ -397,7 +520,10 @@ export default {
enhancedSpells: [], enhancedSpells: [],
availableSources: [], availableSources: [],
gameSystems: [], gameSystems: [],
selectedSystemId: null selectedSystemId: null,
creatingNew: false,
newItem: null,
createSelectedSystemId: null
} }
}, },
async created() { async created() {
@@ -592,6 +718,33 @@ export default {
this.editedItem = null; this.editedItem = null;
this.selectedSystemId = null; this.selectedSystemId = null;
}, },
startCreate() {
this.cancelEdit()
const defaultSystem = this.gameSystems.find(gs => gs.is_active) || this.gameSystems[0] || null
this.createSelectedSystemId = defaultSystem ? defaultSystem.id : null
this.newItem = {
name: '',
category: this.mdata.spellcategories?.[0] || '',
level: 0,
ap: '',
zauberdauer: '',
reichweite: '',
wirkungsziel: '',
wirkungsbereich: '',
wirkungsdauer: '',
ursprung: '',
beschreibung: '',
sourceCode: '',
page_number: 0,
system: defaultSystem ? defaultSystem.code : ''
}
this.creatingNew = true
},
cancelCreate() {
this.creatingNew = false
this.newItem = null
this.createSelectedSystemId = null
},
findSystemIdByCode(code) { findSystemIdByCode(code) {
return findSystemIdByCode(this.gameSystems, code) return findSystemIdByCode(this.gameSystems, code)
}, },
@@ -605,25 +758,51 @@ export default {
}, },
async handleSpellUpdate({ index, spell }) { async handleSpellUpdate({ index, spell }) {
try { try {
const response = await API.put( const response = await API.put(
`/api/maintenance/spells/${spell.id}`, spell, `/api/maintenance/spells/${spell.id}`, spell,
{ {
headers: { headers: {
Authorization: `Bearer ${localStorage.getItem('token')}` , Authorization: `Bearer ${localStorage.getItem('token')}` ,
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}
} }
) }
if (!response.statusText== "OK") throw new Error('Update failed'); )
const updatedSkill = response.data; if (!response.statusText== "OK") throw new Error('Update failed');
// Update the spell in mdata const updatedSkill = response.data;
this.mdata.spells = this.mdata.spells.map(s => // Update the spell in mdata
s.id === updatedSkill.id ? updatedSkill : s this.mdata.spells = this.mdata.spells.map(s =>
); s.id === updatedSkill.id ? updatedSkill : s
} catch (error) { );
console.error('Failed to update spell:', error); } catch (error) {
console.error('Failed to update spell:', error);
}
},
async saveCreate() {
if (!this.newItem) return
try {
const source = this.availableSources.find(s => s.code === this.newItem.sourceCode)
const selectedSystem = this.gameSystems.find(gs => gs.id === this.createSelectedSystemId)
const createData = {
...this.newItem,
source_id: source ? source.id : null,
page_number: this.newItem.page_number || 0,
system: selectedSystem ? selectedSystem.code : (this.newItem.system || ''),
game_system_id: selectedSystem ? selectedSystem.id : null
} }
},
const response = await API.post(
'/api/maintenance/spells-enhanced',
createData
)
this.enhancedSpells.push(response.data)
this.cancelCreate()
} catch (error) {
console.error('Failed to create spell:', error)
alert('Failed to create spell: ' + (error.response?.data?.error || error.message))
}
},
handleFileSelect(event) { handleFileSelect(event) {
const file = event.target.files[0]; const file = event.target.files[0];
if (file && file.type === 'text/csv') { if (file && file.type === 'text/csv') {
@@ -8,6 +8,7 @@
v-model="searchTerm" v-model="searchTerm"
:placeholder="`${$t('search')} ${$t('WaeponSkill')}...`" :placeholder="`${$t('search')} ${$t('WaeponSkill')}...`"
/> />
<button @click="startCreate" class="btn-primary">{{ $t('newEntry') }}</button>
</div> </div>
</div> </div>
@@ -47,6 +48,69 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-if="creatingNew">
<td>New</td>
<td colspan="6">
<div class="edit-form">
<div class="edit-row">
<div class="edit-field">
<label>{{ $t('weaponskill.name') }}:</label>
<input v-model="newItem.name" />
</div>
<div class="edit-field">
<label>{{ $t('weaponskill.difficulty') }}:</label>
<select v-model="newItem.difficulty" style="width:120px;">
<option value="leicht">leicht</option>
<option value="normal">normal</option>
<option value="schwer">schwer</option>
<option value="sehr schwer">sehr schwer</option>
</select>
</div>
<div class="edit-field">
<label>{{ $t('weaponskill.initialwert') }}:</label>
<input v-model.number="newItem.initialwert" type="number" style="width:60px;" />
</div>
</div>
<div class="edit-row">
<div class="edit-field full-width">
<label>{{ $t('weaponskill.description') }}:</label>
<input v-model="newItem.beschreibung" />
</div>
</div>
<div class="edit-row">
<div class="edit-field">
<label>{{ $t('weaponskill.quelle') }}:</label>
<select v-model="newItem.sourceCode" style="width:100px;">
<option value="">-</option>
<option v-for="source in availableSources" :key="source.code" :value="source.code">
{{ source.code }}
</option>
</select>
</div>
<div class="edit-field">
<label>{{ $t('weaponskill.page') || 'Page' }}:</label>
<input v-model.number="newItem.page_number" type="number" style="width:60px;" />
</div>
<div class="edit-field">
<label>{{ $t('weaponskill.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-actions">
<button @click="saveCreate" class="btn-save">{{ $t('common.save') }}</button>
<button @click="cancelCreate" class="btn-cancel">{{ $t('common.cancel') }}</button>
</div>
</div>
</td>
</tr>
<template v-for="(dtaItem, index) in filteredAndSortedWaeponSkills" :key="dtaItem.id"> <template v-for="(dtaItem, index) in filteredAndSortedWaeponSkills" :key="dtaItem.id">
<tr v-if="editingIndex !== index"> <tr v-if="editingIndex !== index">
<td>{{ dtaItem.id || '' }}</td> <td>{{ dtaItem.id || '' }}</td>
@@ -57,7 +121,7 @@
<td>{{ formatQuelle(dtaItem) }}</td> <td>{{ formatQuelle(dtaItem) }}</td>
<td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.system || 'midgard') }}</td> <td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.system || 'midgard') }}</td>
<td> <td>
<button @click="startEdit(index)">Edit</button> <button @click="startEdit(index)">{{ $t('common.edit') }}</button>
</td> </td>
</tr> </tr>
<!-- Edit Mode --> <!-- Edit Mode -->
@@ -119,8 +183,8 @@
</div> </div>
<div class="edit-actions"> <div class="edit-actions">
<button @click="saveEdit(index)" class="btn-save">Save</button> <button @click="saveEdit(index)" class="btn-save">{{ $t('common.save') }}</button>
<button @click="cancelEdit" class="btn-cancel">Cancel</button> <button @click="cancelEdit" class="btn-cancel">{{ $t('common.cancel') }}</button>
</div> </div>
</div> </div>
</td> </td>
@@ -211,7 +275,10 @@ export default {
availableSources: [], availableSources: [],
availableDifficultiesData: [], availableDifficultiesData: [],
gameSystems: [], gameSystems: [],
selectedSystemId: null selectedSystemId: null,
creatingNew: false,
newItem: null,
createSelectedSystemId: null
} }
}, },
async created() { async created() {
@@ -345,6 +412,53 @@ export default {
this.editedItem = null this.editedItem = null
this.selectedSystemId = null this.selectedSystemId = null
}, },
startCreate() {
this.cancelEdit()
const defaultSystem = this.gameSystems.find(gs => gs.is_active) || this.gameSystems[0] || null
this.createSelectedSystemId = defaultSystem ? defaultSystem.id : null
this.newItem = {
name: '',
difficulty: 'leicht',
initialwert: 0,
beschreibung: '',
sourceCode: '',
page_number: 0,
system: defaultSystem ? defaultSystem.code : ''
}
this.creatingNew = true
},
cancelCreate() {
this.creatingNew = false
this.newItem = null
this.createSelectedSystemId = null
},
async saveCreate() {
if (!this.newItem) return
try {
const source = this.availableSources.find(s => s.code === this.newItem.sourceCode)
const selectedSystem = this.gameSystems.find(gs => gs.id === this.createSelectedSystemId)
const createData = {
...this.newItem,
category: 'Waffen',
source_id: source ? source.id : null,
page_number: this.newItem.page_number || 0,
system: selectedSystem ? selectedSystem.code : (this.newItem.system || ''),
game_system_id: selectedSystem ? selectedSystem.id : null
}
const response = await API.post(
'/api/maintenance/weaponskills-enhanced',
createData
)
this.enhancedWeaponSkills.push(response.data)
this.cancelCreate()
} catch (error) {
console.error('Failed to create weapon skill:', error)
alert('Failed to create weapon skill: ' + (error.response?.data?.error || error.message))
}
},
findSystemIdByCode(code) { findSystemIdByCode(code) {
return findSystemIdByCode(this.gameSystems, code) return findSystemIdByCode(this.gameSystems, code)
}, },
@@ -8,6 +8,7 @@
v-model="searchTerm" v-model="searchTerm"
:placeholder="`${$t('search')} ${$t('Weapons')}...`" :placeholder="`${$t('search')} ${$t('Weapons')}...`"
/> />
<button @click="startCreate" class="btn-primary">{{ $t('newEntry') }}</button>
</div> </div>
</div> </div>
@@ -82,6 +83,110 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-if="creatingNew">
<td>New</td>
<td colspan="11">
<div class="edit-form">
<div class="edit-row">
<div class="edit-field">
<label>{{ $t('weapon.name') }}:</label>
<input v-model="newItem.name" />
</div>
<div class="edit-field">
<label>{{ $t('weapon.skillrequired') || 'Skill Required' }}:</label>
<input v-model="newItem.skill_required" style="width:150px;" />
</div>
<div class="edit-field">
<label>{{ $t('weapon.weight') }}:</label>
<input v-model.number="newItem.gewicht" type="number" style="width:80px;" />
</div>
<div class="edit-field">
<label>{{ $t('weapon.value') }}:</label>
<input v-model="newItem.wert" style="width:100px;" />
</div>
</div>
<div class="edit-row">
<div class="edit-field">
<label>{{ $t('weapon.damage') }}:</label>
<input v-model="newItem.damage" style="width:100px;" />
</div>
<div class="edit-field">
<label>{{ $t('weapon.rangenear') || 'Range Near' }}:</label>
<input v-model.number="newItem.range_near" type="number" style="width:80px;" />
</div>
<div class="edit-field">
<label>{{ $t('weapon.rangemiddle') || 'Range Middle' }}:</label>
<input v-model.number="newItem.range_middle" type="number" style="width:80px;" />
</div>
<div class="edit-field">
<label>{{ $t('weapon.rangefar') || 'Range Far' }}:</label>
<input v-model.number="newItem.range_far" type="number" style="width:80px;" />
</div>
</div>
<div class="edit-row">
<div class="edit-field">
<label>{{ $t('weapon.bonusskill') || 'Bonus' }}:</label>
<select v-model="newItem.bonuseigenschaft" style="width:80px;">
<option value="">-</option>
<option value="St">St</option>
<option value="Gs">Gs</option>
<option value="Gw">Gw</option>
<option value="Ko">Ko</option>
<option value="In">In</option>
<option value="Zt">Zt</option>
<option value="Au">Au</option>
<option value="pA">pA</option>
<option value="Wk">Wk</option>
<option value="B">B</option>
</select>
</div>
<div class="edit-field">
<label>{{ $t('weapon.personal_item') }}:</label>
<input type="checkbox" v-model="newItem.personal_item" />
</div>
</div>
<div class="edit-row">
<div class="edit-field full-width">
<label>{{ $t('weapon.description') }}:</label>
<input v-model="newItem.beschreibung" />
</div>
</div>
<div class="edit-row">
<div class="edit-field">
<label>{{ $t('weapon.quelle') }}:</label>
<select v-model="newItem.sourceCode" style="width:100px;">
<option value="">-</option>
<option v-for="source in availableSources" :key="source.code" :value="source.code">
{{ source.code }}
</option>
</select>
</div>
<div class="edit-field">
<label>{{ $t('weapon.page') || 'Page' }}:</label>
<input v-model.number="newItem.page_number" type="number" style="width:60px;" />
</div>
<div class="edit-field">
<label>{{ $t('weapon.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-actions">
<button @click="saveCreate" class="btn-save">{{ $t('common.save') }}</button>
<button @click="cancelCreate" class="btn-cancel">{{ $t('common.cancel') }}</button>
</div>
</div>
</td>
</tr>
<template v-for="(dtaItem, index) in filteredAndSortedWeaponss" :key="dtaItem.id"> <template v-for="(dtaItem, index) in filteredAndSortedWeaponss" :key="dtaItem.id">
<tr v-if="editingIndex !== index"> <tr v-if="editingIndex !== index">
<td>{{ dtaItem.id || '' }}</td> <td>{{ dtaItem.id || '' }}</td>
@@ -98,7 +203,7 @@
<td><input type="checkbox" :checked="dtaItem.personal_item" disabled /></td> <td><input type="checkbox" :checked="dtaItem.personal_item" disabled /></td>
<td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.system || 'midgard') }}</td> <td>{{ getSystemCodeById(dtaItem.game_system_id, dtaItem.system || 'midgard') }}</td>
<td> <td>
<button @click="startEdit(index)">Edit</button> <button @click="startEdit(index)">{{ $t('common.edit') }}</button>
</td> </td>
</tr> </tr>
<!-- Edit Mode --> <!-- Edit Mode -->
@@ -201,8 +306,8 @@
</div> </div>
<div class="edit-actions"> <div class="edit-actions">
<button @click="saveEdit(index)" class="btn-save">Save</button> <button @click="saveEdit(index)" class="btn-save">{{ $t('common.save') }}</button>
<button @click="cancelEdit" class="btn-cancel">Cancel</button> <button @click="cancelEdit" class="btn-cancel">{{ $t('common.cancel') }}</button>
</div> </div>
</div> </div>
</td> </td>
@@ -296,7 +401,10 @@ export default {
enhancedWeapons: [], enhancedWeapons: [],
availableSources: [], availableSources: [],
gameSystems: [], gameSystems: [],
selectedSystemId: null selectedSystemId: null,
creatingNew: false,
newItem: null,
createSelectedSystemId: null
} }
}, },
async created() { async created() {
@@ -478,6 +586,59 @@ export default {
this.editedItem = null this.editedItem = null
this.selectedSystemId = null this.selectedSystemId = null
}, },
startCreate() {
this.cancelEdit()
const defaultSystem = this.gameSystems.find(gs => gs.is_active) || this.gameSystems[0] || null
this.createSelectedSystemId = defaultSystem ? defaultSystem.id : null
this.newItem = {
name: '',
skill_required: '',
gewicht: 0,
wert: '',
damage: '',
range_near: 0,
range_middle: 0,
range_far: 0,
beschreibung: '',
bonuseigenschaft: '',
personal_item: false,
sourceCode: '',
page_number: 0,
system: defaultSystem ? defaultSystem.code : ''
}
this.creatingNew = true
},
cancelCreate() {
this.creatingNew = false
this.newItem = null
this.createSelectedSystemId = null
},
async saveCreate() {
if (!this.newItem) return
try {
const source = this.availableSources.find(s => s.code === this.newItem.sourceCode)
const selectedSystem = this.gameSystems.find(gs => gs.id === this.createSelectedSystemId)
const createData = {
...this.newItem,
source_id: source ? source.id : null,
page_number: this.newItem.page_number || 0,
system: selectedSystem ? selectedSystem.code : (this.newItem.system || ''),
game_system_id: selectedSystem ? selectedSystem.id : null
}
const response = await API.post(
'/api/maintenance/weapons-enhanced',
createData
)
this.enhancedWeapons.push(response.data)
this.cancelCreate()
} catch (error) {
console.error('Failed to create weapon:', error)
alert('Failed to create weapon: ' + (error.response?.data?.error || error.message))
}
},
findSystemIdByCode(code) { findSystemIdByCode(code) {
return findSystemIdByCode(this.gameSystems, code) return findSystemIdByCode(this.gameSystems, code)
}, },
+20
View File
@@ -5,6 +5,9 @@ export default {
previous: 'Zurück', previous: 'Zurück',
next: 'Weiter', next: 'Weiter',
back: 'Zurück', back: 'Zurück',
save: 'Speichern',
saving: 'Speichern...',
edit: 'Bearbeiten',
}, },
auth: { auth: {
login: 'Anmelden', login: 'Anmelden',
@@ -21,6 +24,19 @@ export default {
dontHaveAccount: 'Noch kein Konto?', dontHaveAccount: 'Noch kein Konto?',
registerHere: 'Hier registrieren' registerHere: 'Hier registrieren'
}, },
forgotPassword: {
title: 'Passwort zurücksetzen',
description: 'Geben Sie Ihre E-Mail-Adresse ein, um einen Reset-Link zu erhalten.',
emailLabel: 'E-Mail-Adresse',
emailPlaceholder: 'ihre{\'@\'}email.de',
submit: 'Reset-Link senden',
submitting: 'Wird gesendet...',
successTitle: 'E-Mail gesendet!',
successInfo: 'Falls ein Account mit dieser E-Mail-Adresse existiert, wurde ein Reset-Link gesendet.',
successHint: 'Prüfen Sie Ihre E-Mails und folgen Sie dem Link.',
error: 'Fehler beim Senden der E-Mail. Versuchen Sie es später erneut.',
backToLogin: 'Zurück zum Login'
},
import: { import: {
title: 'Daten importieren', title: 'Daten importieren',
description: 'Laden Sie Charakterdaten aus VTT- oder CSV-Dateien hoch.', description: 'Laden Sie Charakterdaten aus VTT- oder CSV-Dateien hoch.',
@@ -355,6 +371,7 @@ export default {
cancel: 'Abbrechen' cancel: 'Abbrechen'
}, },
search:'Suche', search:'Suche',
newEntry:'Neuer Eintrag',
Skill:'Fertigkeit', Skill:'Fertigkeit',
newSkill:'Neue Fertigkeit', newSkill:'Neue Fertigkeit',
createSkill:'Fertigkeit erstellen', createSkill:'Fertigkeit erstellen',
@@ -544,6 +561,9 @@ export default {
lpRollTooltip: 'LP würfeln: 1d3 + 7 + (Ko/10)', lpRollTooltip: 'LP würfeln: 1d3 + 7 + (Ko/10)',
apRollTooltip: 'AP würfeln: 3d6 + Modifikator je nach Klasse', apRollTooltip: 'AP würfeln: 3d6 + Modifikator je nach Klasse',
bRollTooltip: 'B würfeln: 1d6 + Modifikator je nach Rasse' bRollTooltip: 'B würfeln: 1d6 + Modifikator je nach Rasse'
},
datasheet: {
editHelp: 'Doppelklicken auf ein Feld, um es zu bearbeiten.'
} }
}, },
audit: { audit: {
+25 -5
View File
@@ -1,10 +1,13 @@
export default { export default {
common: { common: {
loading: 'Laden...', loading: 'Loading...',
cancel: 'Abbrechen', cancel: 'Cancel',
previous: 'Zurück', previous: 'Previous',
next: 'Weiter', next: 'Next',
back: 'Back' back: 'Back',
save: 'Save',
saving: 'Saving...',
edit: 'Edit',
}, },
auth: { auth: {
login: 'Login', login: 'Login',
@@ -21,6 +24,19 @@ export default {
dontHaveAccount: "Don't have an account?", dontHaveAccount: "Don't have an account?",
registerHere: 'Register here' registerHere: 'Register here'
}, },
forgotPassword: {
title: 'Reset password',
description: 'Enter your email address to receive a reset link.',
emailLabel: 'Email address',
emailPlaceholder: 'your{\'@\'}email.com',
submit: 'Send reset link',
submitting: 'Sending...',
successTitle: 'Email sent!',
successInfo: 'If an account exists for this email, a reset link has been sent.',
successHint: 'Check your inbox and follow the link.',
error: 'Error sending email. Please try again later.',
backToLogin: 'Back to login'
},
import: { import: {
title: 'Import Data', title: 'Import Data',
description: 'Upload character data from VTT or CSV files.', description: 'Upload character data from VTT or CSV files.',
@@ -351,6 +367,7 @@ export default {
cancel: 'Cancel' cancel: 'Cancel'
}, },
search:'Suche', search:'Suche',
newEntry:'New Entry',
Skill:'Fertigkeit', Skill:'Fertigkeit',
newSkill:'New Skill', newSkill:'New Skill',
createSkill:'Create Skill', createSkill:'Create Skill',
@@ -540,6 +557,9 @@ export default {
lpRollTooltip: 'Roll LP: 1d3 + 7 + (Ko/10)', lpRollTooltip: 'Roll LP: 1d3 + 7 + (Ko/10)',
apRollTooltip: 'Roll AP: 3d6 + modifier based on class', apRollTooltip: 'Roll AP: 3d6 + modifier based on class',
bRollTooltip: 'Roll B: 1d6 + modifier based on race' bRollTooltip: 'Roll B: 1d6 + modifier based on race'
},
datasheet: {
editHelp: 'Click twice on a field to edit it.'
} }
}, },
audit: { audit: {
+158 -106
View File
@@ -1,119 +1,171 @@
#!/bin/bash #!/bin/bash
# Script to update version number across the project # Script to update, commit, and tag versions across the project
set -e set -e
if [ -z "$1" ]; then BACKEND_VERSION_FILE="backend/appsystem/version.go"
echo "Usage: $0 <backend_version> [frontend_version] [auto]"
echo "Example: $0 0.1.31 # Sets both to 0.1.31"
echo "Example: $0 0.1.31 0.2.0 # Sets different versions"
echo "Example: $0 0.1.31 auto # Sets both to 0.1.31 and auto-commits"
echo "Example: $0 0.1.31 0.2.0 auto # Sets different versions and auto-commits"
exit 1
fi
# Parse arguments
BACKEND_VERSION="$1"
AUTO_COMMIT=false
# Check if second argument is "auto"
if [ "$2" = "auto" ]; then
FRONTEND_VERSION="$1"
AUTO_COMMIT=true
elif [ -z "$2" ]; then
FRONTEND_VERSION="$1"
elif [ "$3" = "auto" ]; then
FRONTEND_VERSION="$2"
AUTO_COMMIT=true
else
FRONTEND_VERSION="$2"
fi
if [ "$BACKEND_VERSION" = "$FRONTEND_VERSION" ]; then
echo "Updating both backend and frontend to version $BACKEND_VERSION..."
else
echo "Updating backend to $BACKEND_VERSION and frontend to $FRONTEND_VERSION..."
fi
# Update backend version
BACKEND_VERSION_FILE="backend/config/version.go"
if [ -f "$BACKEND_VERSION_FILE" ]; then
sed -i "s/const Version = \"[^\"]*\"/const Version = \"$BACKEND_VERSION\"/" "$BACKEND_VERSION_FILE"
echo "✓ Updated $BACKEND_VERSION_FILE to $BACKEND_VERSION"
else
echo "⚠ Warning: $BACKEND_VERSION_FILE not found"
fi
# Update frontend version
FRONTEND_VERSION_FILE="frontend/src/version.js" FRONTEND_VERSION_FILE="frontend/src/version.js"
if [ -f "$FRONTEND_VERSION_FILE" ]; then
sed -i "s/export const VERSION = '[^']*'/export const VERSION = '$FRONTEND_VERSION'/" "$FRONTEND_VERSION_FILE"
echo "✓ Updated $FRONTEND_VERSION_FILE to $FRONTEND_VERSION"
else
echo "⚠ Warning: $FRONTEND_VERSION_FILE not found"
fi
# Update frontend package.json version
FRONTEND_PACKAGE="frontend/package.json" FRONTEND_PACKAGE="frontend/package.json"
if [ -f "$FRONTEND_PACKAGE" ]; then
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$FRONTEND_VERSION\"/" "$FRONTEND_PACKAGE"
echo "✓ Updated $FRONTEND_PACKAGE to $FRONTEND_VERSION"
else
echo "⚠ Warning: $FRONTEND_PACKAGE not found"
fi
# Update VERSION.md files
BACKEND_VERSION_MD="backend/VERSION.md" BACKEND_VERSION_MD="backend/VERSION.md"
if [ -f "$BACKEND_VERSION_MD" ]; then
sed -i "s/## Current Version: .*/## Current Version: $BACKEND_VERSION/" "$BACKEND_VERSION_MD"
echo "✓ Updated $BACKEND_VERSION_MD to $BACKEND_VERSION"
fi
FRONTEND_VERSION_MD="frontend/VERSION.md" FRONTEND_VERSION_MD="frontend/VERSION.md"
if [ -f "$FRONTEND_VERSION_MD" ]; then
sed -i "s/## Current Version: .*/## Current Version: $FRONTEND_VERSION/" "$FRONTEND_VERSION_MD" usage() {
echo "✓ Updated $FRONTEND_VERSION_MD to $FRONTEND_VERSION" echo "Usage: $0 [-b backend_version] [-f frontend_version] [-c] [-t]"
echo " -b <version> Update backend version"
echo " -f <version> Update frontend version"
echo " -c Commit using versions from files"
echo " -t Tag using versions from files"
echo "Examples:"
echo " $0 -b 0.1.31 -f 0.2.0"
echo " $0 -b 0.1.31 -c -t"
echo " $0 -c -t"
echo "So you can set the version at any time, commit later without worrying about commit messages and tag later when merged into main."
exit 1
}
read_backend_version() {
if [ ! -f "$BACKEND_VERSION_FILE" ]; then
echo ""; return
fi
sed -n 's/.*const Version = "\(.*\)".*/\1/p' "$BACKEND_VERSION_FILE" | head -n1
}
read_frontend_version() {
if [ ! -f "$FRONTEND_VERSION_FILE" ]; then
echo ""; return
fi
sed -n "s/.*export const VERSION = '\(.*\)'.*/\1/p" "$FRONTEND_VERSION_FILE" | head -n1
}
BACKEND_VERSION_ARG=""
FRONTEND_VERSION_ARG=""
DO_COMMIT=false
DO_TAG=false
while [ $# -gt 0 ]; do
case "$1" in
-b)
[ -z "$2" ] && usage
BACKEND_VERSION_ARG="$2"
shift 2
;;
-f)
[ -z "$2" ] && usage
FRONTEND_VERSION_ARG="$2"
shift 2
;;
-c)
DO_COMMIT=true
shift
;;
-t)
DO_TAG=true
shift
;;
-h|--help)
usage
;;
*)
usage
;;
esac
done
if [ -z "$BACKEND_VERSION_ARG" ] && [ -z "$FRONTEND_VERSION_ARG" ] && [ "$DO_COMMIT" = false ] && [ "$DO_TAG" = false ]; then
usage
fi fi
echo "" if [ -n "$BACKEND_VERSION_ARG" ]; then
echo "✅ Version update complete!" if [ -f "$BACKEND_VERSION_FILE" ]; then
echo " Backend: $BACKEND_VERSION" sed -i "s/const Version = \"[^\"]*\"/const Version = \"$BACKEND_VERSION_ARG\"/" "$BACKEND_VERSION_FILE"
echo " Frontend: $FRONTEND_VERSION" echo "✓ Updated $BACKEND_VERSION_FILE to $BACKEND_VERSION_ARG"
echo "" else
echo "⚠ Warning: $BACKEND_VERSION_FILE not found"
fi
# Auto-commit if requested if [ -f "$BACKEND_VERSION_MD" ]; then
if [ "$AUTO_COMMIT" = true ]; then sed -i "s/## Current Version: .*/## Current Version: $BACKEND_VERSION_ARG/" "$BACKEND_VERSION_MD"
echo "Auto-committing changes..." echo "✓ Updated $BACKEND_VERSION_MD to $BACKEND_VERSION_ARG"
if [ "$BACKEND_VERSION" = "$FRONTEND_VERSION" ]; then fi
git add backend/config/version.go frontend/src/version.js frontend/package.json frontend/VERSION.md fi
git commit -m "Bump version to $BACKEND_VERSION"
git tag v$BACKEND_VERSION if [ -n "$FRONTEND_VERSION_ARG" ]; then
echo "✓ Committed and tagged as v$BACKEND_VERSION" if [ -f "$FRONTEND_VERSION_FILE" ]; then
else sed -i "s/export const VERSION = '[^']*'/export const VERSION = '$FRONTEND_VERSION_ARG'/" "$FRONTEND_VERSION_FILE"
git add backend/config/version.go frontend/src/version.js frontend/package.json frontend/VERSION.md echo "✓ Updated $FRONTEND_VERSION_FILE to $FRONTEND_VERSION_ARG"
git commit -m "Bump backend to $BACKEND_VERSION, frontend to $FRONTEND_VERSION" else
git tag backend-v$BACKEND_VERSION echo "⚠ Warning: $FRONTEND_VERSION_FILE not found"
git tag frontend-v$FRONTEND_VERSION fi
git tag v$BACKEND_VERSION -m "Backend version $BACKEND_VERSION, Frontend version $FRONTEND_VERSION"
echo "✓ Committed and tagged as backend-v$BACKEND_VERSION, frontend-v$FRONTEND_VERSION, v$BACKEND_VERSION" if [ -f "$FRONTEND_PACKAGE" ]; then
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$FRONTEND_VERSION_ARG\"/" "$FRONTEND_PACKAGE"
echo "✓ Updated $FRONTEND_PACKAGE to $FRONTEND_VERSION_ARG"
else
echo "⚠ Warning: $FRONTEND_PACKAGE not found"
fi
if [ -f "$FRONTEND_VERSION_MD" ]; then
sed -i "s/## Current Version: .*/## Current Version: $FRONTEND_VERSION_ARG/" "$FRONTEND_VERSION_MD"
echo "✓ Updated $FRONTEND_VERSION_MD to $FRONTEND_VERSION_ARG"
fi
fi
BACKEND_VERSION_CURRENT=$(read_backend_version)
FRONTEND_VERSION_CURRENT=$(read_frontend_version)
if [ "$DO_COMMIT" = true ]; then
if [ -z "$BACKEND_VERSION_CURRENT" ] && [ -z "$FRONTEND_VERSION_CURRENT" ]; then
echo "❌ Cannot commit: version files missing" >&2
exit 1
fi
FILES_TO_ADD=()
[ -f "$BACKEND_VERSION_FILE" ] && FILES_TO_ADD+=("$BACKEND_VERSION_FILE")
[ -f "$BACKEND_VERSION_MD" ] && FILES_TO_ADD+=("$BACKEND_VERSION_MD")
[ -f "$FRONTEND_VERSION_FILE" ] && FILES_TO_ADD+=("$FRONTEND_VERSION_FILE")
[ -f "$FRONTEND_PACKAGE" ] && FILES_TO_ADD+=("$FRONTEND_PACKAGE")
[ -f "$FRONTEND_VERSION_MD" ] && FILES_TO_ADD+=("$FRONTEND_VERSION_MD")
if [ ${#FILES_TO_ADD[@]} -eq 0 ]; then
echo "❌ Cannot commit: no files to add" >&2
exit 1
fi
git add "${FILES_TO_ADD[@]}"
if [ -n "$BACKEND_VERSION_CURRENT" ] && [ -n "$FRONTEND_VERSION_CURRENT" ]; then
if [ "$BACKEND_VERSION_CURRENT" = "$FRONTEND_VERSION_CURRENT" ]; then
COMMIT_MSG="Bump version to $BACKEND_VERSION_CURRENT"
else
COMMIT_MSG="Bump backend to $BACKEND_VERSION_CURRENT, frontend to $FRONTEND_VERSION_CURRENT"
fi
elif [ -n "$BACKEND_VERSION_CURRENT" ]; then
COMMIT_MSG="Bump backend to $BACKEND_VERSION_CURRENT"
else
COMMIT_MSG="Bump frontend to $FRONTEND_VERSION_CURRENT"
fi
git commit -m "$COMMIT_MSG"
echo "✓ Committed: $COMMIT_MSG"
fi
if [ "$DO_TAG" = true ]; then
if [ -z "$BACKEND_VERSION_CURRENT" ] && [ -z "$FRONTEND_VERSION_CURRENT" ]; then
echo "❌ Cannot tag: version files missing" >&2
exit 1
fi
if [ -n "$BACKEND_VERSION_CURRENT" ] && [ -n "$FRONTEND_VERSION_CURRENT" ] && [ "$BACKEND_VERSION_CURRENT" = "$FRONTEND_VERSION_CURRENT" ]; then
git tag "v$BACKEND_VERSION_CURRENT"
echo "✓ Tagged v$BACKEND_VERSION_CURRENT"
else
if [ -n "$BACKEND_VERSION_CURRENT" ]; then
git tag "backend-v$BACKEND_VERSION_CURRENT"
echo "✓ Tagged backend-v$BACKEND_VERSION_CURRENT"
fi
if [ -n "$FRONTEND_VERSION_CURRENT" ]; then
git tag "frontend-v$FRONTEND_VERSION_CURRENT"
echo "✓ Tagged frontend-v$FRONTEND_VERSION_CURRENT"
fi
fi fi
echo ""
echo "Next step:"
echo " Push: git push && git push --tags"
else
echo "Next steps:"
echo "1. Review changes: git diff"
if [ "$BACKEND_VERSION" = "$FRONTEND_VERSION" ]; then
echo "2. Commit changes: git commit -am 'Bump version to $BACKEND_VERSION'"
echo "3. Tag release: git tag v$BACKEND_VERSION"
git tag v$BACKEND_VERSION
else
echo "2. Commit changes: git commit -am 'Bump backend to $BACKEND_VERSION, frontend to $FRONTEND_VERSION'"
echo "3. Tag releases: git tag backend-v$BACKEND_VERSION && git tag frontend-v$FRONTEND_VERSION"
git tag backend-v$BACKEND_VERSION
git tag frontend-v$FRONTEND_VERSION
git tag v$BACKEND_VERSION -m "Backend version $BACKEND_VERSION, Frontend version $FRONTEND_VERSION"
fi
echo "4. Push: git push && git push --tags"
fi fi