CreateSessions aus characterlist ausgegliedert

common functions un utils.js ausgelagert
This commit is contained in:
2025-08-08 12:20:28 +02:00
parent 1546fdc52e
commit d539796283
8 changed files with 384 additions and 96 deletions
+7 -6
View File
@@ -10,7 +10,6 @@ import (
"log" "log"
"gorm.io/driver/mysql" "gorm.io/driver/mysql"
"gorm.io/driver/sqlite"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -39,11 +38,13 @@ var (
func ConnectDatabase() *gorm.DB { func ConnectDatabase() *gorm.DB {
SetupTestDB() SetupTestDB()
db, err := gorm.Open(sqlite.Open(PreparedTestDB), &gorm.Config{}) /*
if err != nil { db, err := gorm.Open(sqlite.Open(PreparedTestDB), &gorm.Config{})
log.Fatal("Failed to connect to database:", err) if err != nil {
} log.Fatal("Failed to connect to database:", err)
DB = db }
DB = db
*/
return DB return DB
} }
func ConnectDatabaseOrig() *gorm.DB { func ConnectDatabaseOrig() *gorm.DB {
@@ -0,0 +1,108 @@
<template>
<div v-if="sessions.length > 0" class="sessions-section">
<div class="section-header">
<h3>{{ $t('characters.list.continue_creation') }}</h3>
</div>
<div class="grid-container grid-2-columns">
<div
v-for="session in sessions"
:key="session.session_id"
class="card session-card"
@click="continueSession(session.session_id)"
>
<div class="session-header">
<h4 class="list-item-title">{{ session.name || $t('characters.list.unnamed_character') }}</h4>
<span class="badge badge-primary progress-badge">{{ $t('characters.list.step') }} {{ session.current_step }}/{{ session.total_steps }}</span>
</div>
<div class="session-details">
<p class="list-item-details"><strong>{{ $t('characters.list.race') }}:</strong> {{ session.rasse || $t('characters.list.not_selected') }}
<span class="list-item-separator">|</span><strong>{{ $t('characters.list.class') }}:</strong> {{ session.typ || $t('characters.list.not_selected') }}
<span class="list-item-separator">|</span><strong>{{ $t('characters.list.current_step') }}:</strong> {{ session.progress_text }}</p>
</div>
<div class="session-meta">
<span class="session-date">{{ $t('characters.list.last_updated') }}: {{ formatDate(session.updated_at) }}</span>
<span class="session-date">{{ $t('characters.list.expires') }}: {{ formatDate(session.expires_at) }}</span>
</div>
<div class="list-item-actions">
<button @click.stop="deleteSession(session.session_id)" class="btn btn-danger btn-small">
{{ $t('characters.list.delete_draft') }}
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { formatDate } from '@/utils/dateUtils'
export default {
name: 'CharacterCreationSessions',
props: {
sessions: {
type: Array,
default: () => []
}
},
methods: {
continueSession(sessionId) {
this.$emit('continue-session', sessionId)
},
deleteSession(sessionId) {
this.$emit('delete-session', sessionId)
},
formatDate
}
}
</script>
<style scoped>
.sessions-section {
margin-bottom: 30px;
}
.session-card {
cursor: pointer;
}
.session-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.session-details {
margin-bottom: 10px;
}
.session-meta {
display: flex;
flex-direction: column;
gap: 5px;
margin-bottom: 10px;
padding-top: 10px;
border-top: 1px solid #eee;
}
.session-date {
font-size: 0.8rem;
color: #888;
}
.btn-small {
padding: 5px 10px;
font-size: 0.8rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.session-header {
flex-direction: column;
align-items: flex-start;
gap: 5px;
}
}
</style>
+25 -90
View File
@@ -1,51 +1,24 @@
<template> <template>
<div class="fullwidth-container"> <div class="fullwidth-container">
<div class="page-header"> <div class="page-header">
<h2>Your Characters</h2> <h2>{{ $t('characters.list.title') }}</h2>
</div> </div>
<!-- Create New Character Button --> <!-- Create New Character Button -->
<div class="create-character-section"> <div class="create-character-section">
<button @click="createNewCharacter" class="btn btn-success btn-large">Create New Character</button> <button @click="createNewCharacter" class="btn btn-success btn-large">{{ $t('characters.list.create_new') }}</button>
</div> </div>
<!-- Active Character Creation Sessions --> <!-- Active Character Creation Sessions -->
<div v-if="creationSessions.length > 0" class="sessions-section"> <CharacterCreationSessions
<div class="section-header"> :sessions="creationSessions"
<h3>Continue Character Creation</h3> @continue-session="continueSession"
</div> @delete-session="handleDeleteSession"
<div class="grid-container grid-2-columns"> />
<div
v-for="session in creationSessions"
:key="session.session_id"
class="card session-card"
@click="continueSession(session.session_id)"
>
<div class="session-header">
<h4 class="list-item-title">{{ session.name || 'Unnamed Character' }}</h4>
<span class="badge badge-primary progress-badge">Step {{ session.current_step }}/{{ session.total_steps }}</span>
</div>
<div class="session-details">
<p class="list-item-details"><strong>Race:</strong> {{ session.rasse || 'Not selected' }}
<span class="list-item-separator">|</span><strong>Class:</strong> {{ session.typ || 'Not selected' }}
<span class="list-item-separator">|</span><strong>Current step:</strong> {{ session.progress_text }}</p>
</div>
<div class="session-meta">
<span class="session-date">Last updated: {{ formatDate(session.updated_at) }}</span>
<span class="session-date">Expires: {{ formatDate(session.expires_at) }}</span>
</div>
<div class="list-item-actions">
<button @click.stop="deleteSession(session.session_id)" class="btn btn-danger btn-small">
Delete Draft
</button>
</div>
</div>
</div>
</div>
<div v-if="characters.length === 0" class="empty-state"> <div v-if="characters.length === 0" class="empty-state">
<h3>No Characters Yet</h3> <h3>{{ $t('characters.list.no_characters') }}</h3>
<p>Create your first character to get started!</p> <p>{{ $t('characters.list.no_characters_description') }}</p>
</div> </div>
<div v-else class="list-container"> <div v-else class="list-container">
@@ -55,24 +28,29 @@
<div class="list-item-details"> <div class="list-item-details">
{{ character.rasse }} <span class="list-item-separator">|</span> {{ character.rasse }} <span class="list-item-separator">|</span>
{{ character.typ }} <span class="list-item-separator">|</span> {{ character.typ }} <span class="list-item-separator">|</span>
{{ character.grad }} <span class="list-item-separator">|</span> {{ $t('characters.list.grade') }}: {{ character.grad }} <span class="list-item-separator">|</span>
{{ character.owner }} <span class="list-item-separator">|</span> {{ $t('characters.list.owner') }}: {{ character.owner }} <span class="list-item-separator">|</span>
<span class="badge" :class="character.public ? 'badge-success' : 'badge-secondary'"> <span class="badge" :class="character.public ? 'badge-success' : 'badge-secondary'">
{{ character.public ? 'Public' : 'Private' }} {{ character.public ? $t('characters.list.public') : $t('characters.list.private') }}
</span> </span>
</div> </div>
</div> </div>
<div class="list-item-actions"> <div class="list-item-actions">
<router-link :to="`/character/${character.id}`" class="btn btn-primary">View Details</router-link> <router-link :to="`/character/${character.id}`" class="btn btn-primary">{{ $t('characters.list.view_details') }}</router-link>
<button @click="goToAusruestung(character.character_id)" class="btn btn-secondary">Manage Equipment</button> <button @click="goToAusruestung(character.character_id)" class="btn btn-secondary">{{ $t('characters.list.manage_equipment') }}</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template><script> </template><script>
import API from '../utils/api' import API from '../utils/api'
import { formatDate } from '@/utils/dateUtils'
import CharacterCreationSessions from './CharacterCreationSessions.vue'
export default { export default {
components: {
CharacterCreationSessions
},
data() { data() {
return { return {
characters: [], characters: [],
@@ -114,8 +92,12 @@ export default {
this.$router.push(`/character/create/${sessionId}`) this.$router.push(`/character/create/${sessionId}`)
}, },
handleDeleteSession(sessionId) {
this.deleteSession(sessionId)
},
async deleteSession(sessionId) { async deleteSession(sessionId) {
if (confirm('Are you sure you want to delete this character draft?')) { if (confirm(this.$t('characters.list.delete_draft_confirm'))) {
try { try {
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
await API.delete(`/api/characters/create-session/${sessionId}`, { await API.delete(`/api/characters/create-session/${sessionId}`, {
@@ -149,10 +131,7 @@ export default {
} }
}, },
formatDate(dateString) { formatDate
if (!dateString) return 'Unknown'
return new Date(dateString).toLocaleDateString()
},
}, },
} }
</script> </script>
@@ -173,52 +152,8 @@ export default {
font-size: 16px; font-size: 16px;
} }
.btn-small {
padding: 5px 10px;
font-size: 0.8rem;
}
.sessions-section {
margin-bottom: 30px;
}
.session-card {
cursor: pointer;
}
.session-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.session-details {
margin-bottom: 10px;
}
.session-meta {
display: flex;
flex-direction: column;
gap: 5px;
margin-bottom: 10px;
padding-top: 10px;
border-top: 1px solid #eee;
}
.session-date {
font-size: 0.8rem;
color: #888;
}
/* Responsive Design */ /* Responsive Design */
@media (max-width: 768px) { @media (max-width: 768px) {
.session-header {
flex-direction: column;
align-items: flex-start;
gap: 5px;
}
.list-item { .list-item {
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
+23
View File
@@ -176,6 +176,29 @@ export default {
total_in_gs: 'Gesamt in GS' total_in_gs: 'Gesamt in GS'
}, },
characters: { characters: {
list: {
title: 'Ihre Charaktere',
create_new: 'Neuen Charakter erstellen',
continue_creation: 'Charaktererstellung fortsetzen',
no_characters: 'Noch keine Charaktere',
no_characters_description: 'Erstellen Sie Ihren ersten Charakter um zu beginnen!',
view_details: 'Details anzeigen',
manage_equipment: 'Ausrüstung verwalten',
delete_draft: 'Entwurf löschen',
delete_draft_confirm: 'Sind Sie sicher, dass Sie diesen Charakterentwurf löschen möchten?',
race: 'Rasse',
class: 'Klasse',
current_step: 'Aktueller Schritt',
step: 'Schritt',
last_updated: 'Zuletzt aktualisiert',
expires: 'Läuft ab',
not_selected: 'Nicht ausgewählt',
unnamed_character: 'Unbenannter Charakter',
public: 'Öffentlich',
private: 'Privat',
grade: 'Grad',
owner: 'Besitzer'
},
create: { create: {
spells: { spells: {
title: 'Zauber auswählen', title: 'Zauber auswählen',
+30
View File
@@ -1,4 +1,11 @@
export default { export default {
DatasheetView:'Datasheet',
SkillView: 'Skills',
WeaponView: 'Weapons',
SpellView: 'Spells',
EquipmentView: 'Equipment',
ExperianceView: 'Experience & Wealth',
DeleteCharView: 'Delete Character',
char:'Char', char:'Char',
stats: { stats: {
strength: 'St', strength: 'St',
@@ -75,6 +82,29 @@ export default {
total_in_gs: 'Total in GS' total_in_gs: 'Total in GS'
}, },
characters: { characters: {
list: {
title: 'Your Characters',
create_new: 'Create New Character',
continue_creation: 'Continue Character Creation',
no_characters: 'No Characters Yet',
no_characters_description: 'Create your first character to get started!',
view_details: 'View Details',
manage_equipment: 'Manage Equipment',
delete_draft: 'Delete Draft',
delete_draft_confirm: 'Are you sure you want to delete this character draft?',
race: 'Race',
class: 'Class',
current_step: 'Current step',
step: 'Step',
last_updated: 'Last updated',
expires: 'Expires',
not_selected: 'Not selected',
unnamed_character: 'Unnamed Character',
public: 'Public',
private: 'Private',
grade: 'Grade',
owner: 'Owner'
},
create: { create: {
spells: { spells: {
title: 'Select Spells', title: 'Select Spells',
+35
View File
@@ -0,0 +1,35 @@
/**
* TypeScript Definitionen für Utility-Funktionen
*/
export interface DateFormatOptions {
year?: 'numeric' | '2-digit'
month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow'
day?: 'numeric' | '2-digit'
hour?: 'numeric' | '2-digit'
minute?: 'numeric' | '2-digit'
second?: 'numeric' | '2-digit'
}
export declare function formatDate(
dateString: string | null | undefined,
locale?: string,
options?: DateFormatOptions
): string
export declare function formatDateTime(
dateString: string | null | undefined,
locale?: string
): string
export declare function formatRelativeDate(
dateString: string | null | undefined,
locale?: string
): string
export declare function safeValue<T>(
value: T | null | undefined,
fallback?: T
): T
export declare function capitalize(str: string): string
+123
View File
@@ -0,0 +1,123 @@
/**
* Gemeinsame Utility-Funktionen für die gesamte Anwendung
*/
/**
* Formatiert ein Datum-String in ein lokales Datumsformat
* @param {string} dateString - ISO-Datum-String
* @param {string} locale - Sprach-/Ländercode (optional, default: browser locale)
* @param {Object} options - Intl.DateTimeFormat Optionen (optional)
* @returns {string} - Formatiertes Datum
*/
export function formatDate(dateString, locale = undefined, options = {}) {
if (!dateString) {
return 'Unknown'
}
try {
const date = new Date(dateString)
// Prüfe ob das Datum gültig ist
if (isNaN(date.getTime())) {
return 'Invalid Date'
}
// Default-Optionen für Datumsformatierung
const defaultOptions = {
year: 'numeric',
month: '2-digit',
day: '2-digit',
...options
}
return date.toLocaleDateString(locale, defaultOptions)
} catch (error) {
console.warn('Error formatting date:', error)
return 'Invalid Date'
}
}
/**
* Formatiert ein Datum-String mit Uhrzeit
* @param {string} dateString - ISO-Datum-String
* @param {string} locale - Sprach-/Ländercode (optional)
* @returns {string} - Formatiertes Datum mit Uhrzeit
*/
export function formatDateTime(dateString, locale = undefined) {
return formatDate(dateString, locale, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
}
/**
* Formatiert ein Datum relativ (z.B. "vor 2 Stunden")
* @param {string} dateString - ISO-Datum-String
* @param {string} locale - Sprach-/Ländercode (optional)
* @returns {string} - Relatives Datum
*/
export function formatRelativeDate(dateString, locale = undefined) {
if (!dateString) {
return 'Unknown'
}
try {
const date = new Date(dateString)
const now = new Date()
if (isNaN(date.getTime())) {
return 'Invalid Date'
}
// Für moderne Browser mit Intl.RelativeTimeFormat
if (typeof Intl !== 'undefined' && Intl.RelativeTimeFormat) {
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' })
const diffTime = date.getTime() - now.getTime()
const diffDays = Math.round(diffTime / (1000 * 60 * 60 * 24))
if (Math.abs(diffDays) < 1) {
const diffHours = Math.round(diffTime / (1000 * 60 * 60))
if (Math.abs(diffHours) < 1) {
const diffMinutes = Math.round(diffTime / (1000 * 60))
return rtf.format(diffMinutes, 'minute')
}
return rtf.format(diffHours, 'hour')
}
return rtf.format(diffDays, 'day')
}
// Fallback für ältere Browser
return formatDate(dateString, locale)
} catch (error) {
console.warn('Error formatting relative date:', error)
return formatDate(dateString, locale)
}
}
/**
* Weitere gemeinsame Utility-Funktionen können hier hinzugefügt werden
*/
/**
* Sicherheits-Check für leere Werte
* @param {*} value - Zu prüfender Wert
* @param {*} fallback - Fallback-Wert
* @returns {*} - Wert oder Fallback
*/
export function safeValue(value, fallback = '-') {
return value != null && value !== '' ? value : fallback
}
/**
* Kapitalisiert den ersten Buchstaben eines Strings
* @param {string} str - Input String
* @returns {string} - String mit großem ersten Buchstaben
*/
export function capitalize(str) {
if (!str || typeof str !== 'string') return str
return str.charAt(0).toUpperCase() + str.slice(1)
}
+33
View File
@@ -0,0 +1,33 @@
/**
* Vue Plugin für globale Utility-Funktionen
*
* Usage in main.js:
* import UtilsPlugin from './utils/utilsPlugin'
* app.use(UtilsPlugin)
*
* Usage in components:
* this.$formatDate(dateString)
* this.$safeValue(value, 'fallback')
*/
import { formatDate, formatDateTime, formatRelativeDate, safeValue, capitalize } from './dateUtils'
export default {
install(app) {
// Globale Properties für Vue 3
app.config.globalProperties.$formatDate = formatDate
app.config.globalProperties.$formatDateTime = formatDateTime
app.config.globalProperties.$formatRelativeDate = formatRelativeDate
app.config.globalProperties.$safeValue = safeValue
app.config.globalProperties.$capitalize = capitalize
// Provide/Inject für Composition API
app.provide('utils', {
formatDate,
formatDateTime,
formatRelativeDate,
safeValue,
capitalize
})
}
}