CreateSessions aus characterlist ausgegliedert
common functions un utils.js ausgelagert
This commit is contained in:
@@ -10,7 +10,6 @@ import (
|
||||
"log"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -39,11 +38,13 @@ var (
|
||||
|
||||
func ConnectDatabase() *gorm.DB {
|
||||
SetupTestDB()
|
||||
db, err := gorm.Open(sqlite.Open(PreparedTestDB), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatal("Failed to connect to database:", err)
|
||||
}
|
||||
DB = db
|
||||
/*
|
||||
db, err := gorm.Open(sqlite.Open(PreparedTestDB), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatal("Failed to connect to database:", err)
|
||||
}
|
||||
DB = db
|
||||
*/
|
||||
return 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>
|
||||
@@ -1,51 +1,24 @@
|
||||
<template>
|
||||
<div class="fullwidth-container">
|
||||
<div class="page-header">
|
||||
<h2>Your Characters</h2>
|
||||
<h2>{{ $t('characters.list.title') }}</h2>
|
||||
</div>
|
||||
|
||||
<!-- Create New Character Button -->
|
||||
<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>
|
||||
|
||||
<!-- Active Character Creation Sessions -->
|
||||
<div v-if="creationSessions.length > 0" class="sessions-section">
|
||||
<div class="section-header">
|
||||
<h3>Continue Character Creation</h3>
|
||||
</div>
|
||||
<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>
|
||||
<CharacterCreationSessions
|
||||
:sessions="creationSessions"
|
||||
@continue-session="continueSession"
|
||||
@delete-session="handleDeleteSession"
|
||||
/>
|
||||
|
||||
<div v-if="characters.length === 0" class="empty-state">
|
||||
<h3>No Characters Yet</h3>
|
||||
<p>Create your first character to get started!</p>
|
||||
<h3>{{ $t('characters.list.no_characters') }}</h3>
|
||||
<p>{{ $t('characters.list.no_characters_description') }}</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="list-container">
|
||||
@@ -55,24 +28,29 @@
|
||||
<div class="list-item-details">
|
||||
{{ character.rasse }} <span class="list-item-separator">|</span>
|
||||
{{ character.typ }} <span class="list-item-separator">|</span>
|
||||
{{ character.grad }} <span class="list-item-separator">|</span>
|
||||
{{ character.owner }} <span class="list-item-separator">|</span>
|
||||
{{ $t('characters.list.grade') }}: {{ character.grad }} <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'">
|
||||
{{ character.public ? 'Public' : 'Private' }}
|
||||
{{ character.public ? $t('characters.list.public') : $t('characters.list.private') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-item-actions">
|
||||
<router-link :to="`/character/${character.id}`" class="btn btn-primary">View Details</router-link>
|
||||
<button @click="goToAusruestung(character.character_id)" class="btn btn-secondary">Manage Equipment</button>
|
||||
<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">{{ $t('characters.list.manage_equipment') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template><script>
|
||||
import API from '../utils/api'
|
||||
import { formatDate } from '@/utils/dateUtils'
|
||||
import CharacterCreationSessions from './CharacterCreationSessions.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CharacterCreationSessions
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
characters: [],
|
||||
@@ -114,8 +92,12 @@ export default {
|
||||
this.$router.push(`/character/create/${sessionId}`)
|
||||
},
|
||||
|
||||
handleDeleteSession(sessionId) {
|
||||
this.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 {
|
||||
const token = localStorage.getItem('token')
|
||||
await API.delete(`/api/characters/create-session/${sessionId}`, {
|
||||
@@ -149,10 +131,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return 'Unknown'
|
||||
return new Date(dateString).toLocaleDateString()
|
||||
},
|
||||
formatDate
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -173,52 +152,8 @@ export default {
|
||||
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 */
|
||||
@media (max-width: 768px) {
|
||||
.session-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
@@ -176,6 +176,29 @@ export default {
|
||||
total_in_gs: 'Gesamt in GS'
|
||||
},
|
||||
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: {
|
||||
spells: {
|
||||
title: 'Zauber auswählen',
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
export default {
|
||||
DatasheetView:'Datasheet',
|
||||
SkillView: 'Skills',
|
||||
WeaponView: 'Weapons',
|
||||
SpellView: 'Spells',
|
||||
EquipmentView: 'Equipment',
|
||||
ExperianceView: 'Experience & Wealth',
|
||||
DeleteCharView: 'Delete Character',
|
||||
char:'Char',
|
||||
stats: {
|
||||
strength: 'St',
|
||||
@@ -75,6 +82,29 @@ export default {
|
||||
total_in_gs: 'Total in GS'
|
||||
},
|
||||
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: {
|
||||
spells: {
|
||||
title: 'Select Spells',
|
||||
|
||||
Vendored
+35
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user