Merge pull request #8 from Bardioc26/frontend_refactor

Frontend refactor
consolidated css styling
Export allowes now PDF, VTT and Bamort format
added landing page
Added script to update versions
This commit is contained in:
Bardioc26
2025-12-30 22:57:40 +01:00
committed by GitHub
48 changed files with 3234 additions and 3443 deletions
+70
View File
@@ -0,0 +1,70 @@
# Version Management
## Current Version: 0.1.30
The backend version is managed in `/backend/config/version.go`.
## Updating the Version
To update the application version:
1. Edit `/backend/config/version.go`
2. Change the `Version` constant:
```go
const Version = "0.1.31" // Update this
```
## Git Commit Information
The git commit hash is automatically detected at runtime from:
1. `GIT_COMMIT` environment variable (preferred for Docker)
2. Git command output (works in development)
3. Falls back to "unknown" if neither is available
## Setting Git Commit in Docker
### Development Environment
Add to `docker/.env`:
```bash
GIT_COMMIT=$(git rev-parse --short HEAD)
```
Then restart the container:
```bash
cd docker
docker-compose -f docker-compose.dev.yml up -d backend-dev
```
### Production Build
Use build-time variable:
```bash
docker build --build-arg GIT_COMMIT=$(git rev-parse --short HEAD) .
```
Or set in docker-compose.yml:
```yaml
environment:
- GIT_COMMIT=${GIT_COMMIT:-unknown}
```
## API Endpoints
- **Public**: `GET /api/public/version` (no authentication)
- **Protected**: `GET /api/version` (requires JWT token)
Both return:
```json
{
"version": "0.1.30",
"gitCommit": "d0c177b"
}
```
## Frontend Integration
The landing page automatically fetches version information from `/api/public/version` and displays it.
To see the version in the frontend, visit: http://localhost:5173
+2
View File
@@ -82,9 +82,11 @@ func main() {
importer.RegisterRoutes(protected)
pdfrender.RegisterRoutes(protected)
transfer.RegisterRoutes(protected)
config.RegisterRoutes(protected)
// Register public routes (no authentication)
pdfrender.RegisterPublicRoutes(r)
config.RegisterPublicRoutes(r)
logger.Info("API-Routen erfolgreich registriert")
+10
View File
@@ -0,0 +1,10 @@
package config
import (
"github.com/gin-gonic/gin"
)
// Versionsinfo returns version and git commit information
func Versionsinfo(c *gin.Context) {
c.JSON(200, GetInfo())
}
+15
View File
@@ -0,0 +1,15 @@
package config
import "github.com/gin-gonic/gin"
// RegisterRoutes registers config-related routes (protected)
func RegisterRoutes(r *gin.RouterGroup) {
r.GET("/version", Versionsinfo)
}
// RegisterPublicRoutes registers public config routes (no auth required)
func RegisterPublicRoutes(r *gin.Engine) {
// Public version endpoint - no authentication required
public := r.Group("/api/public")
public.GET("/version", Versionsinfo)
}
+60
View File
@@ -0,0 +1,60 @@
package config
// Version is the application version
const Version = "0.1.30"
var (
// GitCommit will be set by build flags or detected at runtime
GitCommit = "unknown"
)
// init detects git commit if not set during build
func init() {
/*
if GitCommit == "" {
// Try environment variable first
if envCommit := os.Getenv("GIT_COMMIT"); envCommit != "" {
GitCommit = envCommit
} else {
// Try to detect from git command
GitCommit = detectGitCommit()
}
}
*/
}
/*
// detectGitCommit tries to get the current git commit hash
func detectGitCommit() string {
cmd := exec.Command("git", "rev-parse", "--short", "HEAD")
output, err := cmd.Output()
if err != nil {
return "unknown"
}
return strings.TrimSpace(string(output))
}
*/
// GetVersion returns the current application version
func GetVersion() string {
return Version
}
/*
// GetGitCommit returns the git commit hash
func GetGitCommit() string {
return GitCommit
}
*/
// Info contains version information
type Info struct {
Version string `json:"version"`
GitCommit string `json:"gitCommit"`
}
// GetInfo returns version information as a struct
func GetInfo() Info {
return Info{
Version: Version,
GitCommit: GitCommit,
}
}
+43
View File
@@ -0,0 +1,43 @@
package config
import (
"testing"
)
func TestGetVersion(t *testing.T) {
version := GetVersion()
if version == "" {
t.Error("Version should not be empty")
}
if version != Version {
t.Errorf("Expected version %s, got %s", Version, version)
}
}
/*
func TestGetGitCommit(t *testing.T) {
commit := GetGitCommit()
if commit == "" {
t.Error("GitCommit should not be empty")
}
// Should be either "unknown" or a valid git hash
if commit != "unknown" && len(commit) < 7 {
t.Errorf("Invalid git commit format: %s", commit)
}
}
*/
func TestGetInfo(t *testing.T) {
info := GetInfo()
if info.Version == "" {
t.Error("Info.Version should not be empty")
}
if info.GitCommit == "" {
t.Error("Info.GitCommit should not be empty")
}
if info.Version != Version {
t.Errorf("Expected info.Version %s, got %s", Version, info.Version)
}
}
+1
View File
@@ -14,6 +14,7 @@ services:
- API_PORT=${API_PORT:-8180}
- TEMPLATES_DIR=${TEMPLATES_DIR:-./templatesx}
- EXPORT_TEMP_DIR=${EXPORT_TEMP_DIR:-./export_tempx}
- GIT_COMMIT=${GIT_COMMIT:-unknown}
depends_on:
mariadb-dev:
condition: service_healthy
+4
View File
@@ -11,6 +11,10 @@ fi
# Gehe ins Docker-Verzeichnis
cd "$(dirname "$0")"
# Get current git commit
export GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
echo "📝 Git Commit: $GIT_COMMIT"
echo "📦 Building and starting development containers..."
# Stoppe vorhandene Container
+87
View File
@@ -0,0 +1,87 @@
# Frontend Version Management
## Current Version: 0.1.30
The frontend version is managed independently from the backend.
## Version Locations
1. **Primary source**: `/frontend/src/version.js`
- Contains the VERSION constant
- Exports version info functions
2. **Package metadata**: `/frontend/package.json`
- Standard npm version field
- Should match version.js
## Updating the Version
### Option 1: Using the update script (Recommended)
```bash
# Updates both backend and frontend
./scripts/update-version.sh 0.1.31
```
### Option 2: Manual update
Edit `/frontend/src/version.js`:
```javascript
export const VERSION = '0.1.31' // Update this
```
And `/frontend/package.json`:
```json
{
"version": "0.1.31" // Update this
}
```
## Git Commit Information
The git commit is injected via environment variable:
- Set `VITE_GIT_COMMIT` in `.env` or at build time
- Falls back to "unknown" if not set
Example `.env`:
```bash
VITE_GIT_COMMIT=d0c177b
```
## Usage in Components
```javascript
import { getVersion, getGitCommit, getVersionInfo } from '@/version'
// Get version string
const version = getVersion() // "0.1.30"
// Get git commit
const commit = getGitCommit() // "d0c177b" or "unknown"
// Get full info object
const info = getVersionInfo() // { version: "0.1.30", gitCommit: "d0c177b" }
```
## Landing Page Display
The landing page shows both:
- **Frontend Version**: From `/frontend/src/version.js`
- **Backend Version**: Fetched from `/api/public/version`
This allows users to see if frontend and backend are in sync.
## Build-time Version Injection
To inject git commit at build time, update `vite.config.js`:
```javascript
import { defineConfig } from 'vite'
import { execSync } from 'child_process'
const gitCommit = execSync('git rev-parse --short HEAD').toString().trim()
export default defineConfig({
define: {
'import.meta.env.VITE_GIT_COMMIT': JSON.stringify(gitCommit)
}
})
```
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "my-app",
"version": "0.0.0",
"name": "bamort-frontend",
"version": "0.1.21",
"private": true,
"type": "module",
"scripts": {
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

File diff suppressed because it is too large Load Diff
+2 -272
View File
@@ -431,276 +431,6 @@ export default {
};
</script>
<style scoped>
.audit-log-view {
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
margin-top: 20px;
}
.audit-log-view h4 {
color: #333;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #007bff;
}
.filter-controls {
display: flex;
flex-wrap: wrap;
gap: 15px;
align-items: center;
margin-bottom: 20px;
padding: 15px;
background: white;
border-radius: 6px;
border: 1px solid #e9ecef;
}
.filter-group {
display: flex;
align-items: center;
gap: 10px;
}
.date-range-group {
display: flex;
align-items: center;
gap: 10px;
margin-left: 10px;
}
.filter-select, .date-input {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
background: white;
}
.btn-refresh {
padding: 8px 16px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
}
.btn-refresh:hover:not(:disabled) {
background: #0056b3;
}
.stats-section {
margin-bottom: 25px;
padding: 15px;
background: white;
border-radius: 6px;
border: 1px solid #e9ecef;
}
.stats-section h5 {
margin-bottom: 15px;
color: #555;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.stat-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background: #f8f9fa;
border-radius: 4px;
}
.stat-label {
font-weight: 500;
color: #666;
}
.stat-value {
font-weight: bold;
font-size: 1.1em;
}
.stat-value.positive {
color: #28a745;
}
.stat-value.negative {
color: #dc3545;
}
.audit-entries {
background: white;
border-radius: 6px;
border: 1px solid #e9ecef;
}
.loading, .no-entries {
padding: 40px;
text-align: center;
color: #666;
font-style: italic;
}
.audit-entry {
padding: 15px;
border-bottom: 1px solid #e9ecef;
transition: background-color 0.2s;
}
.audit-entry:last-child {
border-bottom: none;
}
.audit-entry:hover {
background-color: #f8f9fa;
}
.audit-entry.positive-change {
border-left: 4px solid #28a745;
}
.audit-entry.negative-change {
border-left: 4px solid #dc3545;
}
.entry-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.entry-field {
display: flex;
align-items: center;
gap: 8px;
font-weight: bold;
}
.field-icon {
font-size: 1.2em;
}
.entry-timestamp {
color: #666;
font-size: 0.9em;
text-align: right;
display: flex;
flex-direction: column;
gap: 2px;
}
.timestamp-date {
font-weight: 500;
color: #555;
}
.timestamp-time {
font-size: 0.85em;
color: #888;
font-family: monospace;
}
.timestamp-relative {
font-size: 0.8em;
color: #999;
font-style: italic;
}
.entry-content {
margin-left: 20px;
}
.value-change {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
font-family: monospace;
font-size: 1.1em;
}
.old-value {
color: #666;
}
.arrow {
color: #007bff;
font-weight: bold;
}
.new-value {
font-weight: bold;
}
.difference {
font-weight: bold;
font-size: 0.9em;
}
.difference.positive {
color: #28a745;
}
.difference.negative {
color: #dc3545;
}
.entry-reason, .entry-notes {
display: flex;
gap: 8px;
margin-bottom: 5px;
font-size: 0.9em;
}
.reason-label, .notes-label {
font-weight: 500;
color: #666;
min-width: 50px;
}
.reason-value {
background: #e9ecef;
padding: 2px 8px;
border-radius: 12px;
font-size: 0.85em;
font-weight: 500;
}
.notes-value {
color: #555;
font-style: italic;
}
.date-group {
margin-bottom: 25px;
}
.date-group-header {
background: #007bff;
color: white;
padding: 8px 15px;
margin: 0 0 10px 0;
border-radius: 4px;
font-weight: 500;
font-size: 0.9em;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.checkbox-input {
margin-right: 8px;
}
<style>
/* All styles moved to main.css */
</style>
@@ -35,3 +35,7 @@ export default {
},
}
</script>
<style>
/* All common styles moved to main.css */
</style>
@@ -38,3 +38,7 @@ export default {
},
}
</script>
<style>
/* All common styles moved to main.css */
</style>
+2 -128
View File
@@ -359,132 +359,6 @@ export default {
}
</script>
<style scoped>
.character-creation {
width: 100%;
max-width: none;
margin: 0;
padding: 10px;
}
.creation-content {
width: 100%;
}
.creation-header {
margin-bottom: 30px;
}
.creation-header h1 {
text-align: center;
margin-bottom: 20px;
color: #333;
}
.progress-indicator {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
margin-bottom: 20px;
}
.step {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
border-radius: 8px;
transition: all 0.3s ease;
}
.step.active {
background-color: #e3f2fd;
border: 2px solid #2196f3;
}
.step.completed {
background-color: #e8f5e8;
border: 2px solid #4caf50;
}
.step-number {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border-radius: 50%;
background-color: #ddd;
color: #666;
font-weight: bold;
margin-bottom: 5px;
}
.step.active .step-number {
background-color: #2196f3;
color: white;
}
.step.completed .step-number {
background-color: #4caf50;
color: white;
}
.step.clickable {
cursor: pointer;
transition: all 0.3s ease;
}
.step.clickable:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.step.completed:hover .step-number {
background-color: #45a049;
}
.step.active:hover .step-number {
background-color: #1976d2;
}
.step-title {
font-size: 12px;
color: #666;
text-align: center;
}
.creation-content {
background: white;
border-radius: 8px;
padding: 30px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.session-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
background-color: #f5f5f5;
border-radius: 4px;
font-size: 14px;
color: #666;
}
.delete-btn {
background-color: #f44336;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.delete-btn:hover {
background-color: #d32f2f;
}
<style>
/* All styles moved to main.css */
</style>
@@ -336,22 +336,8 @@ export default {
}
</script>
<style scoped>
.attributes-form {
max-width: 800px;
margin: 0 auto;
display: flex;
flex-direction: column;
min-height: 0;
padding-bottom: 20px;
}
.attributes-form h2 {
text-align: center;
margin-bottom: 10px;
color: #333;
flex-shrink: 0;
}
<style>
/* All common styles moved to main.css */
.instruction {
text-align: center;
@@ -361,63 +347,6 @@ export default {
flex-shrink: 0;
}
.attributes-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 20px;
max-height: 50vh;
overflow-y: auto;
padding: 5px;
border: 1px solid #eee;
border-radius: 8px;
background-color: #fefefe;
}
.attribute-group {
padding: 8px;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
min-width: 0; /* Prevent overflow */
}
.attribute-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.input-with-dice {
display: flex;
gap: 8px;
align-items: center;
}
.attribute-label {
font-weight: bold;
color: #333;
flex: 1;
margin: 0;
}
.attribute-input {
width: 60px;
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
text-align: center;
font-weight: bold;
}
.attribute-input:focus {
outline: none;
border-color: #2196f3;
box-shadow: 0 0 5px rgba(33, 150, 243, 0.3);
}
.attribute-description {
font-size: 11px;
color: #666;
@@ -467,63 +396,10 @@ export default {
background-color: #f57c00;
}
.form-actions {
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.attributes-form-content {
display: flex;
flex-direction: column;
flex: 1;
}
.prev-btn, .next-btn {
padding: 12px 30px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
.prev-btn {
background-color: #6c757d;
color: white;
}
.prev-btn:hover {
background-color: #5a6268;
}
.next-btn {
background-color: #2196f3;
color: white;
}
.next-btn:hover:not(:disabled) {
background-color: #1976d2;
}
.next-btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
/* Responsive Design für sehr kleine Bildschirme */
@media (max-width: 600px) {
.attributes-grid {
grid-template-columns: 1fr;
}
.attribute-group {
padding: 10px;
}
}
</style>
@@ -390,49 +390,8 @@ export default {
}
</script>
<style scoped>
.basic-info-form {
max-width: 600px;
margin: 0 auto;
}
.basic-info-form h2 {
text-align: center;
margin-bottom: 30px;
color: #333;
}
.form-group {
margin-bottom: 20px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
input, select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box;
}
input:focus, select:focus {
outline: none;
border-color: #2196f3;
box-shadow: 0 0 5px rgba(33, 150, 243, 0.3);
}
<style>
/* All common styles moved to main.css */
.belief-search {
position: relative;
@@ -491,63 +450,6 @@ input:focus, select:focus {
color: #f44336;
}
.form-actions {
text-align: center;
margin-top: 30px;
}
.next-btn {
background-color: #2196f3;
color: white;
padding: 12px 30px;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
.next-btn:hover:not(:disabled) {
background-color: #1976d2;
}
.next-btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
/* Social class roll styles */
.input-with-dice {
display: flex;
gap: 8px;
align-items: center;
}
.input-with-dice select {
flex: 1;
}
.dice-btn {
padding: 10px 12px;
background-color: #4caf50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
flex-shrink: 0;
}
.dice-btn:hover:not(:disabled) {
background-color: #45a049;
}
.dice-btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.roll-result {
margin-top: 8px;
padding: 8px;
@@ -557,7 +459,6 @@ input:focus, select:focus {
color: #2e7d32;
}
/* Roll overlay styles */
.roll-overlay {
position: fixed;
top: 0;
@@ -480,3 +480,7 @@ export default {
}
}
</script>
<style>
/* All common styles moved to main.css */
</style>
@@ -705,10 +705,9 @@ export default {
}
</script>
<style scoped>
/* Minimal custom styles - most styling comes from main.css */
<style>
/* All common styles moved to main.css */
/* Override global fullwidth-page padding to achieve true full-width */
.fullwidth-page {
padding: 0 !important;
margin: 0 !important;
@@ -717,35 +716,30 @@ export default {
box-sizing: border-box !important;
}
/* Add minimal padding only where needed */
.page-header {
padding: 15px 20px;
margin-bottom: 20px;
}
/* Full-width three column grid layout */
.three-column-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 30px;
margin: 0 20px 30px 20px; /* Add horizontal margins for content readability */
width: calc(100vw - 40px); /* Use viewport width minus margins */
margin: 0 20px 30px 20px;
width: calc(100vw - 40px);
max-width: calc(100vw - 40px);
box-sizing: border-box;
}
/* Ensure grid takes full available width */
.skills-content {
width: 100%;
max-width: 100%;
}
/* Utility classes for dynamic styling that can't be expressed in main.css */
.opacity-50 {
opacity: 0.6;
}
/* Border color variants for category states */
.border-primary {
border-color: #007bff !important;
background-color: #f8fcff;
@@ -761,7 +755,6 @@ export default {
background-color: #ffebee;
}
/* Responsive behavior for smaller screens */
@media (max-width: 1200px) {
.three-column-grid {
grid-template-columns: 1fr 1fr;
@@ -636,10 +636,9 @@ export default {
}
</script>
<style scoped>
/* Minimal custom styles - most styling comes from main.css */
<style>
/* All common styles moved to main.css */
/* Spell Points Display */
.spell-points-display {
margin: 0 20px 30px 20px;
padding: 16px;
@@ -703,7 +702,6 @@ export default {
font-weight: 600;
}
/* Override global fullwidth-page padding to achieve true full-width */
.fullwidth-page {
padding: 0 !important;
margin: 0 !important;
@@ -712,30 +710,26 @@ export default {
box-sizing: border-box !important;
}
/* Add minimal padding only where needed */
.page-header {
padding: 15px 20px;
margin-bottom: 20px;
}
/* Full-width three column grid layout */
.three-column-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 30px;
margin: 0 20px 30px 20px; /* Add horizontal margins for content readability */
width: calc(100vw - 40px); /* Use viewport width minus margins */
margin: 0 20px 30px 20px;
width: calc(100vw - 40px);
max-width: calc(100vw - 40px);
box-sizing: border-box;
}
/* Ensure grid takes full available width */
.spells-content {
width: 100%;
max-width: 100%;
}
/* Spell-specific styles */
.spell-item {
padding: 12px;
border: 1px solid #ddd;
@@ -828,7 +822,6 @@ export default {
text-align: right;
}
/* Selected spells styles */
.selected-spell-item {
padding: 12px;
border: 1px solid #007bff;
@@ -844,14 +837,6 @@ export default {
margin-bottom: 6px;
}
.spell-header {
display: flex;
justify-content: space-between;
align-items: center;
flex: 1;
margin-right: 8px;
}
.selected-spell-item .spell-category {
color: #6c757d;
font-size: 0.8em;
@@ -880,7 +865,6 @@ export default {
background-color: #c82333;
}
/* Category selection styles */
.category-item {
padding: 12px;
border: 1px solid #ddd;
@@ -924,7 +908,6 @@ export default {
border-radius: 4px;
}
/* Total costs display */
.total-costs {
border-top: 1px solid #dee2e6;
padding-top: 12px;
@@ -948,12 +931,10 @@ export default {
color: #007bff;
}
/* Utility classes for dynamic styling that can't be expressed in main.css */
.opacity-50 {
opacity: 0.6;
}
/* Message styles */
.no-selection-message,
.no-spells-message,
.no-categories-message {
@@ -963,7 +944,6 @@ export default {
padding: 20px;
}
/* Badge styles */
.category-badge,
.count-badge,
.info-badge {
@@ -984,7 +964,6 @@ export default {
color: white;
}
/* Responsive behavior for smaller screens */
@media (max-width: 1200px) {
.three-column-grid {
grid-template-columns: 1fr 1fr;
@@ -58,7 +58,9 @@ export default {
}
</script>
<style scoped>
<style>
/* All common styles moved to main.css */
.sessions-section {
margin-bottom: 30px;
}
@@ -97,7 +99,6 @@ export default {
font-size: 0.8rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.session-header {
flex-direction: column;
+14 -350
View File
@@ -3,7 +3,7 @@
<!-- Character Header -->
<div class="character-header">
<div class="header-content">
<button @click="showExportDialog = true" class="export-button-small" :title="$t('export.exportPDF')">
<button @click="showExportDialog = true" class="export-button-small" :title="$t('export.title')">
📄
</button>
<h2>{{ $t('char') }}: {{ character.name }} ({{ $t(currentView) }})</h2>
@@ -11,44 +11,12 @@
</div>
<!-- Export Dialog -->
<div v-if="showExportDialog" class="modal-overlay" @click.self="showExportDialog = false">
<div class="modal-content">
<div class="modal-header">
<h3>{{ $t('export.exportPDF') }}</h3>
<button @click="showExportDialog = false" class="close-button">&times;</button>
</div>
<div class="modal-body">
<div v-if="isExporting" class="loading-overlay">
<div class="spinner"></div>
<p>{{ $t('export.generating') }}</p>
</div>
<div class="form-group">
<label>{{ $t('export.selectTemplate') }}:</label>
<select v-model="selectedTemplate" class="template-select" :disabled="isExporting">
<option value="">{{ $t('export.pleaseSelectTemplate') }}</option>
<option v-for="template in templates" :key="template.id" :value="template.id">
{{ template.name }}
</option>
</select>
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" v-model="showUserName" :disabled="isExporting">
{{ $t('export.showUserName') }}
</label>
</div>
</div>
<div class="modal-footer">
<button @click="showExportDialog = false" class="btn-cancel" :disabled="isExporting">
{{ $t('export.cancel') }}
</button>
<button @click="exportToPDF" class="btn-export" :disabled="!selectedTemplate || isExporting">
<span v-if="!isExporting">{{ $t('export.export') }}</span>
<span v-else>{{ $t('export.exporting') }}</span>
</button>
</div>
</div>
</div>
<ExportDialog
:characterId="id"
:showDialog="showExportDialog"
@update:showDialog="showExportDialog = $event"
@export-success="handleExportSuccess"
/>
<!-- Submenu Content -->
<!-- <div class="character-aspect"> -->
@@ -69,7 +37,8 @@
</div>
</template>
<style scoped>
<style>
/* Component-specific styles only - global styles in main.css */
.character-details {
width: 100%;
height: 100%;
@@ -78,261 +47,12 @@
display: flex;
flex-direction: column;
}
.character-header {
margin-bottom: 20px;
}
.header-content {
display: flex;
align-items: center;
gap: 15px;
}
.export-button-small {
width: 40px;
height: 40px;
padding: 0;
border: 1px solid #007bff;
border-radius: 8px;
background: #007bff;
color: white;
font-size: 1.2rem;
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
}
.export-button-small:hover {
background: #0056b3;
border-color: #0056b3;
transform: scale(1.05);
}
.character-header h2 {
margin: 0;
color: #333;
font-size: 1.5rem;
border-bottom: 2px solid #007bff;
padding-bottom: 10px;
flex: 1;
}
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
border-radius: 8px;
width: 90%;
max-width: 500px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #dee2e6;
}
.modal-header h3 {
margin: 0;
color: #333;
font-size: 1.25rem;
}
.close-button {
background: none;
border: none;
font-size: 1.5rem;
color: #999;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.close-button:hover {
color: #333;
}
.modal-body {
padding: 20px;
position: relative;
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.95);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
border-radius: 0 0 8px 8px;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-overlay p {
color: #007bff;
font-weight: 500;
margin: 0;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #495057;
}
.template-select {
width: 100%;
padding: 10px 12px;
border: 1px solid #dee2e6;
border-radius: 6px;
background: white;
color: #495057;
font-size: 0.95rem;
cursor: pointer;
}
.template-select:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.checkbox-label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
user-select: none;
}
.checkbox-label input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 20px;
border-top: 1px solid #dee2e6;
}
.btn-cancel {
padding: 10px 20px;
border: 1px solid #dee2e6;
border-radius: 6px;
background: #f8f9fa;
color: #495057;
cursor: pointer;
font-weight: 500;
transition: all 0.2s ease;
}
.btn-cancel:hover {
background: #e9ecef;
border-color: #adb5bd;
}
.btn-export {
padding: 10px 20px;
border: 1px solid #007bff;
border-radius: 6px;
background: #007bff;
color: white;
cursor: pointer;
font-weight: 500;
transition: all 0.2s ease;
}
.btn-export:hover:not(:disabled) {
background: #0056b3;
border-color: #0056b3;
}
.btn-export:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.submenu {
display: flex;
gap: 10px;
margin: 20px 0;
flex-wrap: wrap;
}
.submenu button {
padding: 10px 16px;
border: 1px solid #dee2e6;
border-radius: 6px;
background: #f8f9fa;
color: #495057;
cursor: pointer;
font-weight: 500;
transition: all 0.2s ease;
}
.submenu button:hover {
background: #e9ecef;
border-color: #007bff;
}
.submenu button.active {
background: #007bff;
color: white;
border-color: #007bff;
}
</style>
<script>
import API from '../utils/api'
import ExportDialog from "./ExportDialog.vue";
import DatasheetView from "./DatasheetView.vue"; // Component for character stats
import SkillView from "./SkillView.vue"; // Component for character history
import WeaponView from "./WeaponView.vue"; // Component for character history
@@ -346,6 +66,7 @@ export default {
name: "CharacterDetails",
props: ["id"], // Receive the route parameter as a prop
components: {
ExportDialog,
DatasheetView,
SkillView,
WeaponView,
@@ -359,11 +80,7 @@ export default {
character: {},
currentView: "DatasheetView", // Default view
lastView: "DatasheetView",
templates: [],
selectedTemplate: "",
showUserName: false,
showExportDialog: false,
isExporting: false,
menus: [
{ id: 1, name: "Datasheet", component: "DatasheetView" },
{ id: 2, name: "Skill", component: "SkillView" },
@@ -385,63 +102,10 @@ export default {
headers: { Authorization: `Bearer ${token}` },
})
this.character = response.data
// Load available templates
await this.loadTemplates()
},
methods: {
async loadTemplates() {
try {
const response = await API.get('/api/pdf/templates')
this.templates = response.data
// Auto-select first template if available
if (this.templates.length > 0) {
this.selectedTemplate = this.templates[0].id
}
} catch (error) {
console.error('Failed to load templates:', error)
}
},
async exportToPDF() {
if (!this.selectedTemplate) {
alert(this.$t('export.pleaseSelectTemplate'))
return
}
this.isExporting = true
try {
// Build URL parameters
const params = new URLSearchParams({
template: this.selectedTemplate
})
if (this.showUserName) {
params.append('showUserName', 'true')
}
// Get filename from export API (saves PDF to file)
const response = await API.get(`/api/pdf/export/${this.id}`, {
params: Object.fromEntries(params)
})
const filename = response.data.filename
if (!filename) {
throw new Error('No filename returned from export')
}
// Open PDF in new window using file endpoint
const pdfUrl = `${API.defaults.baseURL}/api/pdf/file/${filename}`
window.open(pdfUrl, '_blank')
// Close dialog on success
this.showExportDialog = false
} catch (error) {
console.error('Failed to export PDF:', error)
alert(this.$t('export.exportFailed') + ': ' + (error.response?.data?.error || error.message))
} finally {
this.isExporting = false
}
handleExportSuccess() {
console.log('PDF exported successfully')
},
changeView(view) {
@@ -466,4 +130,4 @@ export default {
},
},
};
</script>
</script>
+3 -2
View File
@@ -132,8 +132,9 @@ export default {
}
</script>
<style scoped>
/* Spezifische Styles nur für CharacterList */
<style>
/* All common styles moved to main.css */
.create-character-section {
margin-bottom: 30px;
padding: 20px;
+1 -86
View File
@@ -176,92 +176,7 @@
</template>
<style>
/* DatasheetView spezifische Styles */
.datasheet-container {
padding-top: 10px;
}
.info-section {
max-width: none;
white-space: normal;
line-height: 1.6;
}
.info-section p {
margin: 15px 0;
padding: 0;
}
.character-overview {
margin-bottom: 30px;
margin-top: 0;
}
.character-image {
position: relative;
}
.character-image .image-upload-container {
position: absolute;
bottom: 10px;
right: 10px;
}
.character-info {
margin-top: 20px;
}
.editable-value {
cursor: pointer;
padding: 2px 4px;
border-radius: 3px;
transition: background-color 0.2s;
}
.editable-value:hover {
background-color: rgba(0, 123, 255, 0.1);
}
.edit-input {
width: 60px;
padding: 2px 4px;
font-size: inherit;
font-weight: bold;
border: 2px solid var(--primary-color);
border-radius: 3px;
text-align: center;
}
.edit-input:focus {
outline: none;
border-color: #0056b3;
}
.editable-prop {
cursor: pointer;
padding: 1px 3px;
border-radius: 2px;
transition: background-color 0.2s;
display: inline-block;
min-width: 20px;
}
.editable-prop:hover {
background-color: rgba(0, 123, 255, 0.1);
}
.prop-input {
padding: 1px 4px;
font-size: inherit;
border: 1px solid var(--primary-color);
border-radius: 3px;
min-width: 60px;
}
.prop-input:focus {
outline: none;
border-color: #0056b3;
}
/* All styles moved to main.css */
</style>
<script>
+3 -9
View File
@@ -39,13 +39,7 @@ export default {
}
</script>
<style>
/*
.cd-view {
text-align: center;
}
button {
margin: 5px;
}*/
</style>
<style>
/* All common styles moved to main.css */
</style>
+3 -263
View File
@@ -119,272 +119,12 @@
</template>
<style scoped>
.fullwidth-container {
padding: 1rem;
}
.header-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.btn-add-equipment {
padding: 8px 16px;
background: #1da766;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
transition: background 0.2s ease;
}
.btn-add-equipment:hover {
background: #16a085;
}
.cd-table {
width: 100%;
border-collapse: collapse;
}
.cd-table th,
.cd-table td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
}
<style>
/* All styles moved to main.css */
/* Equipment-specific table header color override */
.cd-table th {
background-color: #1da766;
color: white;
font-weight: bold;
}
.empty-state {
text-align: center;
color: #999;
font-style: italic;
padding: 2rem !important;
}
.action-cell {
text-align: center;
}
.btn-delete {
background: none;
border: none;
cursor: pointer;
font-size: 1.2rem;
padding: 4px 8px;
transition: transform 0.2s ease;
}
.btn-delete:hover {
transform: scale(1.2);
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
border-radius: 8px;
width: 90%;
max-width: 600px;
max-height: 80vh;
display: flex;
flex-direction: column;
animation: modalSlideIn 0.3s ease;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: scale(0.9) translateY(-20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.modal-header {
padding: 20px;
border-bottom: 2px solid #1da766;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h3 {
margin: 0;
color: #333;
}
.close-button {
background: none;
border: none;
font-size: 28px;
cursor: pointer;
color: #666;
line-height: 1;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.close-button:hover {
color: #333;
}
.modal-body {
padding: 20px;
overflow-y: auto;
flex: 1;
}
.modal-footer {
padding: 15px 20px;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 10px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
.form-group input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
.loading {
text-align: center;
padding: 2rem;
color: #666;
}
.equipment-list {
max-height: 300px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
margin-bottom: 15px;
}
.equipment-item {
padding: 12px;
border-bottom: 1px solid #eee;
cursor: pointer;
transition: background 0.2s ease;
}
.equipment-item:hover {
background: #f5f5f5;
}
.equipment-item.selected {
background: #e8f5e9;
border-left: 3px solid #1da766;
}
.equipment-name {
font-weight: bold;
color: #333;
margin-bottom: 4px;
}
.equipment-details {
font-size: 0.9em;
color: #666;
}
.no-results {
text-align: center;
padding: 2rem;
color: #999;
font-style: italic;
}
.selected-equipment-details {
background: #f8f9fa;
padding: 15px;
border-radius: 4px;
margin-top: 15px;
}
.selected-equipment-details h4 {
margin-top: 0;
color: #1da766;
}
.selected-equipment-details p {
margin: 8px 0;
}
.btn-confirm {
padding: 8px 20px;
background: #1da766;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background 0.2s ease;
}
.btn-confirm:hover:not(:disabled) {
background: #16a085;
}
.btn-confirm:disabled {
background: #ccc;
cursor: not-allowed;
}
.btn-cancel {
padding: 8px 20px;
background: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s ease;
}
.btn-cancel:hover {
background: #5a6268;
}
</style>
+2 -2
View File
@@ -93,8 +93,8 @@
</div>
</template>
<style scoped>
/* ExperianceView spezifische Styles */
<style>
/* All common styles moved to main.css */
.experience-section,
.wealth-section {
+225
View File
@@ -0,0 +1,225 @@
<template>
<div v-if="showDialog" class="modal-overlay" @click.self="closeDialog">
<div class="modal-content">
<div class="modal-header">
<h3>{{ $t('export.title') }}</h3>
<button @click="closeDialog" class="close-button">&times;</button>
</div>
<div class="modal-body">
<div v-if="isExporting" class="loading-overlay">
<div class="spinner"></div>
<p>{{ $t('export.generating') }}</p>
</div>
<div class="form-group">
<label>{{ $t('export.selectFormat') }}:</label>
<select v-model="selectedFormat" class="template-select" :disabled="isExporting">
<option value="">{{ $t('export.pleaseSelectFormat') }}</option>
<option value="pdf">{{ $t('export.formatPDF') }}</option>
<option value="vtt">{{ $t('export.formatVTT') }}</option>
<option value="bamort">{{ $t('export.formatBamort') }}</option>
</select>
</div>
<div v-if="selectedFormat === 'pdf'" class="form-group">
<label>{{ $t('export.selectTemplate') }}:</label>
<select v-model="selectedTemplate" class="template-select" :disabled="isExporting">
<option value="">{{ $t('export.pleaseSelectTemplate') }}</option>
<option v-for="template in templates" :key="template.id" :value="template.id">
{{ template.name }}
</option>
</select>
</div>
<div v-if="selectedFormat === 'pdf'" class="form-group">
<label class="checkbox-label">
<input type="checkbox" v-model="showUserName" :disabled="isExporting">
{{ $t('export.showUserName') }}
</label>
</div>
</div>
<div class="modal-footer">
<button @click="closeDialog" class="btn-cancel" :disabled="isExporting">
{{ $t('export.cancel') }}
</button>
<button @click="performExport" class="btn-export" :disabled="!canExport || isExporting">
<span v-if="!isExporting">{{ $t('export.export') }}</span>
<span v-else>{{ $t('export.exporting') }}</span>
</button>
</div>
</div>
</div>
</template>
<style>
/* All common styles moved to main.css - no component-specific styles needed */
</style>
<script>
import API from '../utils/api'
export default {
name: "ExportDialog",
props: {
characterId: {
type: [String, Number],
required: true
},
showDialog: {
type: Boolean,
default: false
}
},
data() {
return {
templates: [],
selectedFormat: "",
selectedTemplate: "",
showUserName: false,
isExporting: false
}
},
computed: {
canExport() {
if (!this.selectedFormat) return false
if (this.selectedFormat === 'pdf' && !this.selectedTemplate) return false
return true
}
},
async created() {
await this.loadTemplates()
},
methods: {
async loadTemplates() {
try {
const response = await API.get('/api/pdf/templates')
this.templates = response.data
// Auto-select first template if available
if (this.templates.length > 0) {
this.selectedTemplate = this.templates[0].id
}
} catch (error) {
console.error('Failed to load templates:', error)
}
},
async performExport() {
if (!this.selectedFormat) {
alert(this.$t('export.pleaseSelectFormat'))
return
}
if (this.selectedFormat === 'pdf') {
await this.exportToPDF()
} else if (this.selectedFormat === 'vtt') {
await this.exportToVTT()
} else if (this.selectedFormat === 'bamort') {
await this.exportToBamort()
}
},
async exportToPDF() {
if (!this.selectedTemplate) {
alert(this.$t('export.pleaseSelectTemplate'))
return
}
this.isExporting = true
try {
// Build URL parameters
const params = new URLSearchParams({
template: this.selectedTemplate
})
if (this.showUserName) {
params.append('showUserName', 'true')
}
// Get filename from export API (saves PDF to file)
const response = await API.get(`/api/pdf/export/${this.characterId}`, {
params: Object.fromEntries(params)
})
const filename = response.data.filename
if (!filename) {
throw new Error('No filename returned from export')
}
// Open PDF in new window using file endpoint
const pdfUrl = `${API.defaults.baseURL}/api/pdf/file/${filename}`
window.open(pdfUrl, '_blank')
// Emit success event and close dialog
this.$emit('export-success')
this.closeDialog()
} catch (error) {
console.error('Failed to export PDF:', error)
alert(this.$t('export.exportFailed') + ': ' + (error.response?.data?.error || error.message))
} finally {
this.isExporting = false
}
},
async exportToVTT() {
this.isExporting = true
try {
// Get VTT data and trigger download
const response = await API.get(`/api/importer/export/vtt/${this.characterId}/file`, {
responseType: 'blob'
})
// Create download link
const blob = new Blob([response.data], { type: 'application/json' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `character_${this.characterId}_vtt.json`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
this.$emit('export-success')
this.closeDialog()
} catch (error) {
console.error('Failed to export VTT:', error)
alert(this.$t('export.exportFailed') + ': ' + (error.response?.data?.error || error.message))
} finally {
this.isExporting = false
}
},
async exportToBamort() {
this.isExporting = true
try {
// Get Bamort JSON data and trigger download
const response = await API.get(`/api/transfer/download/${this.characterId}`, {
responseType: 'blob'
})
// Create download link
const blob = new Blob([response.data], { type: 'application/json' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `character_${this.characterId}_bamort.json`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
this.$emit('export-success')
this.closeDialog()
} catch (error) {
console.error('Failed to export Bamort format:', error)
alert(this.$t('export.exportFailed') + ': ' + (error.response?.data?.error || error.message))
} finally {
this.isExporting = false
}
},
closeDialog() {
this.$emit('update:showDialog', false)
}
}
}
</script>
@@ -95,6 +95,6 @@ export default {
}
</script>
<style scoped>
/* No custom CSS needed - using main.css classes */
<style>
/* All common styles moved to main.css */
</style>
+4 -14
View File
@@ -74,7 +74,10 @@
</div>
</template>
<style scoped>
<style>
/* All common styles moved to main.css */
/* ImageUploadCropper specific styles */
.btn-upload {
padding: 8px 16px;
background-color: var(--primary-color);
@@ -151,19 +154,6 @@
border: 1px solid #ccc;
background-color: white;
}
.btn-secondary {
padding: 10px 20px;
background-color: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-secondary:hover {
background-color: #5a6268;
}
</style>
<script>
+2 -2
View File
@@ -90,6 +90,6 @@ export default {
}
</script>
<style scoped>
/* No custom CSS needed - using main.css classes */
<style>
/* All common styles moved to main.css */
</style>
+3
View File
@@ -1,6 +1,9 @@
<template>
<nav class="top-nav"><!---<nav class="menu"> --->
<ul>
<li>
<router-link to="/" active-class="active">{{ $t('menu.Home') }}</router-link>
</li>
<li>
<router-link to="/dashboard" active-class="active">{{ $t('menu.Dashboard') }}</router-link>
</li>
+2
View File
@@ -47,6 +47,8 @@ export default {
</script>
<style>
/* All common styles moved to main.css */
.error {
color: red;
}
+2 -14
View File
@@ -201,18 +201,6 @@ export default {
}
</script>
<style scoped>
.spinner {
width: 20px;
height: 20px;
border: 2px solid #f3f3f3;
border-top: 2px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
<style>
/* All common styles moved to main.css */
</style>
+1 -277
View File
@@ -166,286 +166,10 @@
</template>
<style scoped>
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
border-radius: 8px;
padding: 24px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
animation: modalSlideIn 0.3s ease;
}
.modal-wide {
max-width: 700px;
}
/* Ressourcen-Anzeige im Dialog */
.current-resources {
display: flex;
gap: 15px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.resource-display-card {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
flex: 1;
min-width: 160px;
}
.resource-display-card .resource-icon {
font-size: 20px;
}
.resource-info {
flex: 1;
}
.resource-label {
font-size: 12px;
color: #6c757d;
font-weight: 500;
}
.resource-amount {
font-size: 16px;
font-weight: bold;
color: #495057;
}
.resource-remaining {
margin-top: 4px;
}
.resource-remaining small {
color: #6c757d;
font-weight: normal;
}
.text-warning {
color: #f0ad4e !important;
}
.text-danger {
color: #d9534f !important;
}
/* Lernbare Stufen */
.selection-summary {
background: #e7f3ff;
padding: 12px;
border-radius: 6px;
margin-bottom: 10px;
border-left: 4px solid #007bff;
}
.cost-summary {
color: #28a745;
font-weight: bold;
}
/* Component-specific styles - common styles are in main.css */
.learning-levels {
border: 1px solid #dee2e6;
border-radius: 6px;
max-height: 200px;
overflow-y: auto;
}
.level-option {
padding: 12px 16px;
border-bottom: 1px solid #f1f1f1;
cursor: pointer;
transition: all 0.2s ease;
}
.level-option:last-child {
border-bottom: none;
}
.level-option:hover:not(.disabled) {
background: #f8f9fa;
}
.level-option.selected {
background: #e7f3ff;
border-left: 4px solid #007bff;
}
.level-option.in-sequence:not(.selected) {
background: #f0f8ff;
border-left: 2px solid #87ceeb;
}
.level-option.disabled {
background: #f8f9fa;
color: #6c757d;
cursor: not-allowed;
opacity: 0.6;
}
.level-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 500;
}
.level-target {
color: #495057;
}
.level-cost {
color: #28a745;
font-weight: bold;
}
.level-option.disabled .level-cost {
color: #dc3545;
}
.level-details {
margin-top: 4px;
color: #6c757d;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: scale(0.9) translateY(-20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.modal-content h3 {
margin-top: 0;
margin-bottom: 20px;
color: #333;
border-bottom: 2px solid #1da766;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 15px;
}
.form-row {
display: flex;
gap: 15px;
align-items: flex-start;
}
.form-col {
flex: 1;
min-width: 0;
}
.form-col-main {
flex: 2;
min-width: 200px;
}
.form-col-input {
flex: 1;
min-width: 140px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
.form-group textarea {
height: 80px;
resize: vertical;
}
.help-text {
display: block;
margin-top: 5px;
font-size: 12px;
color: #6c757d;
font-style: italic;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.btn-confirm {
padding: 8px 20px;
background: #1da766;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background 0.2s ease;
}
.btn-confirm:hover:not(:disabled) {
background: #16a085;
}
.btn-confirm:disabled {
background: #ccc;
cursor: not-allowed;
}
.btn-cancel {
padding: 8px 20px;
background: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s ease;
}
.btn-cancel:hover:not(:disabled) {
background: #5a6268;
}
</style>
+4 -699
View File
@@ -740,17 +740,9 @@ export default {
</script>
<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: block;
z-index: 1000;
}
/* Component-specific styles - common styles are in main.css */
/* Fullscreen modal override */
.modal-content {
background: white;
border-radius: 0;
@@ -779,278 +771,22 @@ export default {
}
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px;
border-bottom: 2px solid #1da766;
background: #f8f9fa;
border-radius: 0;
flex-shrink: 0;
z-index: 10;
}
.dialog-header h3 {
margin: 0;
color: #333;
font-size: 1.5rem;
}
.btn-close {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
padding: 0;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.btn-close:hover {
background: #e9ecef;
color: #333;
}
.resources-section {
padding: 20px 24px;
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
flex-shrink: 0;
}
.resources-section h4 {
margin: 0 0 15px 0;
color: #495057;
font-size: 1.1rem;
}
.current-resources {
display: flex;
gap: 15px;
margin-bottom: 20px;
flex-wrap: wrap;
}
/* Component-specific overrides */
.resource-display-card {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: white;
border: 1px solid #dee2e6;
border-radius: 8px;
flex: 1;
min-width: 200px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.resource-display-card .resource-icon {
font-size: 20px;
}
.resource-info {
flex: 1;
}
.resource-label {
font-size: 12px;
color: #6c757d;
font-weight: 500;
}
.resource-amount {
font-size: 16px;
font-weight: bold;
color: #1da766;
}
.resource-remaining {
margin-top: 4px;
}
.resource-remaining small {
color: #6c757d;
font-weight: normal;
}
.text-warning {
color: #f0ad4e !important;
}
.text-danger {
color: #d9534f !important;
}
.text-info {
color: #17a2b8 !important;
}
.reward-method-section {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #dee2e6;
}
.reward-method-section label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #495057;
font-size: 0.95rem;
}
.reward-method-section .form-select {
width: 100%;
padding: 12px 16px;
border: 2px solid #dee2e6;
border-radius: 8px;
font-size: 14px;
font-family: inherit;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
box-sizing: border-box;
background: white;
}
.reward-method-section .form-select:focus {
outline: none;
border-color: #1da766;
box-shadow: 0 0 0 3px rgba(29, 167, 102, 0.1);
}
.reward-method-section .form-hint {
display: block;
margin-top: 4px;
font-size: 0.85rem;
color: #6c757d;
font-style: italic;
}
.form-section {
padding: 24px;
flex: 1;
overflow-y: auto;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #495057;
font-size: 0.95rem;
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
padding: 12px 16px;
border: 2px solid #dee2e6;
border-radius: 8px;
font-size: 14px;
font-family: inherit;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
box-sizing: border-box;
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: #1da766;
box-shadow: 0 0 0 3px rgba(29, 167, 102, 0.1);
}
.form-textarea {
resize: vertical;
min-height: 80px;
}
.form-hint {
display: block;
margin-top: 4px;
font-size: 0.85rem;
color: #6c757d;
font-style: italic;
}
.costs-preview {
padding: 20px 24px;
background: #fff3cd;
border-top: 1px solid #ffeaa7;
border-bottom: 1px solid #ffeaa7;
flex-shrink: 0;
}
.costs-preview h4 {
margin: 0 0 12px 0;
color: #856404;
font-size: 1rem;
}
.cost-breakdown {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.cost-item {
display: flex;
align-items: center;
gap: 8px;
}
.cost-label {
color: #856404;
font-weight: 500;
}
.cost-value {
font-weight: bold;
color: #495057;
background: white;
padding: 4px 8px;
border-radius: 4px;
border: 1px solid #ffeaa7;
}
.modal-actions {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
padding: 20px 24px;
background: #f8f9fa;
border-radius: 0;
border-top: 1px solid #dee2e6;
flex-shrink: 0;
}
.action-info {
flex: 1;
}
.selection-count {
font-size: 0.9rem;
color: #6c757d;
font-weight: 500;
}
.action-buttons {
display: flex;
gap: 12px;
}
/* Button overrides for this specific dialog */
.btn-confirm {
padding: 12px 24px;
background: #1da766;
@@ -1113,418 +849,6 @@ export default {
to { transform: rotate(360deg); }
}
/* Skills Selection Styles */
.skills-selection-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.skills-available,
.skills-selected {
border: 2px solid #dee2e6;
border-radius: 8px;
overflow: hidden;
}
.skills-available h4,
.skills-selected h4 {
margin: 0;
padding: 12px 16px;
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
font-size: 1rem;
color: #495057;
}
.category-filters {
padding: 12px 16px;
border-bottom: 1px solid #dee2e6;
display: flex;
flex-wrap: wrap;
gap: 8px;
background: #f8f9fa;
}
.category-filter-btn {
padding: 6px 12px;
background: white;
border: 2px solid #dee2e6;
border-radius: 20px;
font-size: 0.85rem;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
font-weight: 500;
color: #495057;
}
.category-filter-btn:hover {
border-color: #1da766;
color: #1da766;
}
.category-filter-btn.active {
background: #1da766;
border-color: #1da766;
color: white;
font-weight: 600;
}
.category-filter-btn:first-child {
font-weight: 600;
background: #e9ecef;
border-color: #adb5bd;
}
.category-filter-btn:first-child.active {
background: #495057;
border-color: #495057;
color: white;
}
.search-input {
margin: 0;
font-size: 13px;
}
.sort-and-search-controls {
padding: 12px 16px;
border-bottom: 1px solid #dee2e6;
display: flex;
align-items: center;
gap: 20px;
background: #f8f9fa;
flex-wrap: wrap;
}
.sort-controls {
display: flex;
align-items: center;
gap: 12px;
flex: 0 0 auto;
}
.skills-search {
flex: 1;
min-width: 200px;
}
.sort-label {
font-size: 0.9rem;
color: #495057;
font-weight: 500;
}
.sort-btn {
padding: 6px 12px;
background: white;
border: 2px solid #dee2e6;
border-radius: 6px;
font-size: 0.85rem;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
font-weight: 500;
color: #495057;
display: flex;
align-items: center;
gap: 4px;
}
.sort-btn:hover {
border-color: #1da766;
color: #1da766;
}
.sort-btn.active {
background: #1da766;
border-color: #1da766;
color: white;
font-weight: 600;
}
.sort-icon {
font-size: 0.8rem;
font-weight: bold;
}
.skills-list {
max-height: 60vh;
overflow-y: auto;
background: white;
}
.skill-item {
padding: 12px 16px;
border-bottom: 1px solid #f8f9fa;
display: flex;
align-items: center;
gap: 12px;
cursor: grab;
transition: all 0.2s ease;
}
.skill-item:hover {
background: #f8f9fa;
}
.skill-item:active {
cursor: grabbing;
}
.skill-item.skill-affordable {
border-left: 4px solid #1da766;
}
.skill-item:not(.skill-affordable) {
opacity: 0.6;
cursor: not-allowed;
}
.skill-info {
flex: 1;
}
.skill-main-line {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.skill-name {
font-weight: 600;
color: #333;
font-size: 0.95rem;
min-width: 120px;
}
.skill-category {
font-size: 0.8rem;
color: #6c757d;
font-style: italic;
min-width: 100px;
}
.skill-costs {
display: flex;
gap: 8px;
font-size: 0.85rem;
margin-left: auto;
}
.cost-ep {
color: #1da766;
font-weight: 600;
}
.cost-gold {
color: #ffc107;
font-weight: 600;
}
.skill-actions {
display: flex;
gap: 8px;
}
.btn-select {
width: 32px;
height: 32px;
border: 2px solid #1da766;
background: white;
color: #1da766;
border-radius: 50%;
cursor: pointer;
font-weight: bold;
font-size: 14px;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
.btn-select:hover:not(:disabled) {
background: #1da766;
color: white;
transform: scale(1.1);
}
.btn-select:disabled {
border-color: #6c757d;
color: #6c757d;
cursor: not-allowed;
transform: none;
}
.skills-drop-zone {
min-height: 60vh;
padding: 16px;
background: white;
border: 2px dashed #dee2e6;
margin: 16px;
border-radius: 8px;
transition: all 0.2s ease;
}
.skills-drop-zone.drag-over {
border-color: #1da766;
background: rgba(29, 167, 102, 0.05);
}
.drop-zone-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
min-height: 200px;
color: #6c757d;
text-align: center;
}
.placeholder-icon {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.7;
}
.placeholder-text {
font-size: 0.9rem;
line-height: 1.4;
max-width: 200px;
}
.selected-skills-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.selected-skill-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 6px;
transition: all 0.2s ease;
}
.selected-skill-item:hover {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.selected-skill-info {
flex: 1;
}
.selected-skill-name {
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.selected-skill-costs {
display: flex;
gap: 12px;
font-size: 0.85rem;
}
.btn-remove {
width: 24px;
height: 24px;
border: none;
background: #dc3545;
color: white;
border-radius: 50%;
cursor: pointer;
font-weight: bold;
font-size: 16px;
line-height: 1;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
.btn-remove:hover {
background: #c82333;
transform: scale(1.1);
}
.total-costs {
margin-top: 16px;
padding: 12px;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 6px;
}
.total-costs-header {
font-weight: 600;
color: #856404;
margin-bottom: 8px;
}
.total-costs-breakdown {
display: flex;
gap: 16px;
margin-bottom: 8px;
}
.total-ep,
.total-gold {
font-weight: 600;
font-size: 0.9rem;
}
.total-ep {
color: #1da766;
}
.total-gold {
color: #ffc107;
}
.affordability-check {
font-size: 0.85rem;
font-weight: 600;
}
.text-success {
color: #28a745;
}
.text-danger {
color: #dc3545;
}
.loading-skills {
padding: 20px;
text-align: center;
color: #6c757d;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.simple-input-section {
margin-bottom: 20px;
padding: 16px;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 8px;
}
.simple-input-section .form-group {
margin-bottom: 0;
}
/* Responsive Design */
@media (max-width: 768px) {
.modal-content {
@@ -1533,25 +857,6 @@ export default {
max-width: none;
}
.skills-selection-container {
grid-template-columns: 1fr;
gap: 16px;
}
.resources-display {
flex-direction: column;
gap: 10px;
}
.current-resources {
flex-direction: column;
gap: 10px;
}
.resource-display-card {
min-width: auto;
}
.cost-breakdown {
flex-direction: column;
gap: 10px;
+3 -344
View File
@@ -251,105 +251,18 @@
</template>
<style>
.tables-container {
display: flex;
gap: 1rem;
width: 100%;
}
<style scoped>
/* Component-specific styles - common styles are in main.css */
.table-wrapper-left {
flex: 6;
min-width: 0; /* Prevent table from overflowing */
}
.table-wrapper-right {
flex: 4;
min-width: 0; /* Prevent table from overflowing */
}
.cd-table {
width: 100%;
}
/* Only component-specific overrides remain here */
.cd-table-header {
background-color: #1da766;
font-weight: bold;
}
/* Header mit Lernmodus-Kontrollen */
.header-section {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 15px;
}
.learning-mode-controls {
display: flex;
align-items: center;
gap: 15px;
}
/* Ressourcen-Anzeige */
.resources-display {
display: flex;
gap: 15px;
animation: slideIn 0.3s ease;
}
.resource-item {
display: flex;
align-items: center;
gap: 5px;
padding: 6px 12px;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 6px;
font-weight: bold;
color: #495057;
}
.resource-icon {
font-size: 16px;
}
.resource-value {
font-size: 14px;
white-space: nowrap;
}
/* Lernmodus Toggle Button */
.btn-learning-mode {
padding: 8px 16px;
border: 2px solid #1da766;
background: white;
color: #1da766;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
display: flex;
align-items: center;
gap: 5px;
transition: all 0.3s ease;
position: relative;
}
.btn-learning-mode:hover {
background: #1da766;
color: white;
}
.btn-learning-mode.active {
background: #1da766;
color: white;
}
/* Lernmodus Action Buttons */
.learning-actions {
display: flex;
gap: 5px;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
@@ -360,262 +273,8 @@
transform: translateX(0);
}
}
.btn-learn-new,
.btn-improve,
.btn-add {
width: 40px;
height: 40px;
border: 2px solid #007bff;
background: white;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
transition: all 0.2s ease;
position: relative;
}
.btn-learn-new:hover {
background: #007bff;
color: white;
}
.btn-improve {
border-color: #28a745;
}
.btn-improve:hover {
background: #28a745;
color: white;
}
.btn-add {
border-color: #17a2b8;
}
.btn-add:hover {
background: #17a2b8;
color: white;
}
/* Aktions-Buttons in der Tabelle */
.action-cell {
text-align: center;
padding: 4px;
}
.btn-action {
padding: 4px 8px;
border: 1px solid #28a745;
background: white;
color: #28a745;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: all 0.2s ease;
position: relative;
}
.btn-action:hover {
background: #28a745;
color: white;
}
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
border-radius: 8px;
padding: 24px;
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
animation: modalSlideIn 0.3s ease;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: scale(0.9) translateY(-20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.modal-content h3 {
margin-top: 0;
margin-bottom: 20px;
color: #333;
border-bottom: 2px solid #1da766;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
.form-group textarea {
height: 80px;
resize: vertical;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.btn-confirm {
padding: 8px 20px;
background: #1da766;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background 0.2s ease;
}
.btn-confirm:hover:not(:disabled) {
background: #16a085;
}
.btn-confirm:disabled {
background: #ccc;
cursor: not-allowed;
}
.btn-cancel {
padding: 8px 20px;
background: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s ease;
}
.btn-cancel:hover {
background: #5a6268;
}
.icon {
font-size: 14px;
}
/* PP-Button Styles */
.pp-cell {
padding: 4px 8px;
}
.pp-container {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
}
.pp-btn {
width: 20px;
height: 20px;
border: 1px solid #007bff;
background: white;
color: #007bff;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
line-height: 1;
padding: 0;
}
.pp-btn:hover:not(:disabled) {
background: #007bff;
color: white;
}
.pp-btn:disabled {
border-color: #ccc;
color: #ccc;
cursor: not-allowed;
opacity: 0.5;
}
.pp-btn-plus {
border-color: #28a745;
color: #28a745;
}
.pp-btn-plus:hover:not(:disabled) {
background: #28a745;
color: white;
}
.pp-btn-minus {
border-color: #dc3545;
color: #dc3545;
}
.pp-btn-minus:hover:not(:disabled) {
background: #dc3545;
color: white;
}
.pp-value {
min-width: 20px;
text-align: center;
font-weight: bold;
color: #495057;
font-size: 13px;
}
</style>
<script>
import API from '@/utils/api'
import SkillImproveDialog from './SkillImproveDialog.vue'
+3 -526
View File
@@ -179,7 +179,7 @@
<!-- Ausgewählter Zauber Aktionen und Details -->
<div v-if="selectedSpell" class="form-group">
<div class="spell-details-section">
<!---
<!--
<div class="selection-summary">
<div class="spell-actions">
<strong>Ausgewählt:</strong> {{ selectedSpell.name }}
@@ -198,7 +198,7 @@
</button>
</div>
</div>
--->
-->
<!-- Detaillierte Zauber-Informationen -->
<div v-if="isLoadingSpellDetails" class="loading-spell-details">
<span>Lade Zauber-Details...</span>
@@ -612,541 +612,18 @@ export default {
}
};
</script>
<style scoped>
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: flex-start;
align-items: flex-start;
z-index: 1000;
}
/* Component-specific styles - common styles are in main.css */
.modal-content {
background: white;
border-radius: 8px;
padding: 24px;
width: 100vw;
height: 100vh;
max-width: 100vw;
max-height: 100vh;
overflow-y: auto;
animation: modalSlideIn 0.3s ease;
box-sizing: border-box;
}
.modal-wide {
max-width: 100vw;
}
/* Ressourcen-Anzeige im Dialog */
.current-resources {
display: flex;
gap: 15px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.resource-display-card {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
flex: 1;
min-width: 160px;
}
.resource-display-card .resource-icon {
font-size: 20px;
}
.resource-info {
flex: 1;
}
.resource-label {
font-size: 12px;
color: #6c757d;
font-weight: 500;
}
.resource-amount {
font-size: 16px;
font-weight: bold;
color: #495057;
}
.resource-remaining {
margin-top: 4px;
}
.resource-remaining small {
color: #6c757d;
font-weight: normal;
}
.text-warning {
color: #f0ad4e !important;
}
.text-danger {
color: #d9534f !important;
}
/* Zweispaltiges Layout */
.spells-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 15px;
}
.available-spells-section,
.learning-list-section {
min-height: 300px;
}
.learning-item {
background: #f0f8ff !important;
border-left: 3px solid #007bff !important;
}
.learning-item .level-header {
position: relative;
}
.remove-btn {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
background: #dc3545;
color: white;
border: none;
border-radius: 50%;
width: 24px;
height: 24px;
cursor: pointer;
font-size: 16px;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
}
.remove-btn:hover {
background: #c82333;
}
.already-selected {
opacity: 0.5;
pointer-events: none;
}
.spell-actions-inline {
display: flex;
align-items: center;
gap: 10px;
}
.btn-add-inline {
background: #28a745;
color: white;
border: none;
border-radius: 50%;
width: 28px;
height: 28px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
flex-shrink: 0;
}
.btn-add-inline:hover:not(:disabled) {
background: #218838;
transform: scale(1.1);
}
.btn-add-inline:disabled {
background: #6c757d;
cursor: not-allowed;
transform: none;
}
.already-selected .btn-add-inline {
background: #17a2b8;
}
.total-costs {
margin-top: 10px;
padding: 10px;
background: #e7f3ff;
border-radius: 4px;
border-left: 4px solid #007bff;
}
.spell-actions {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.cost-info {
color: #28a745;
font-weight: bold;
}
.btn-add-spell {
padding: 4px 12px;
background: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: background 0.2s ease;
}
.btn-add-spell:hover:not(:disabled) {
background: #218838;
}
.btn-add-spell:disabled {
background: #6c757d;
cursor: not-allowed;
}
@media (max-width: 1024px) {
.spells-container {
grid-template-columns: 1fr;
}
}
/* Filter und Sortierung */
.school-buttons {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 5px;
}
.school-btn {
padding: 6px 12px;
border: 1px solid #dee2e6;
border-radius: 4px;
background: white;
color: #495057;
cursor: pointer;
font-size: 14px;
transition: all 0.2s ease;
}
.school-btn:hover {
background: #f8f9fa;
border-color: #007bff;
}
.school-btn.active {
background: #007bff;
color: white;
border-color: #007bff;
}
/* Zauber-Auswahl und Details */
.spell-details-section {
background: #e7f3ff;
padding: 16px;
border-radius: 6px;
margin-bottom: 10px;
border-left: 4px solid #007bff;
}
.loading-spell-details {
text-align: center;
padding: 20px;
color: #6c757d;
font-style: italic;
}
.spell-details-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
margin-top: 15px;
}
.spell-detail-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 6px;
padding: 12px;
}
.spell-detail-card h4 {
margin: 0 0 10px 0;
color: #495057;
font-size: 14px;
font-weight: bold;
border-bottom: 1px solid #e9ecef;
padding-bottom: 5px;
}
.detail-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
font-size: 13px;
}
.detail-row:last-child {
margin-bottom: 0;
}
.detail-label {
color: #6c757d;
font-weight: 500;
flex: 0 0 auto;
margin-right: 10px;
}
.detail-value {
color: #495057;
text-align: right;
flex: 1 1 auto;
}
.spell-description {
background: white;
border: 1px solid #dee2e6;
border-radius: 6px;
padding: 12px;
margin-top: 15px;
}
.spell-description h4 {
margin: 0 0 8px 0;
color: #495057;
font-size: 14px;
font-weight: bold;
}
.spell-description p {
margin: 0;
color: #495057;
font-size: 13px;
line-height: 1.4;
}
.selection-summary {
background: #e7f3ff;
padding: 12px;
border-radius: 6px;
margin-bottom: 10px;
border-left: 4px solid #007bff;
}
.cost-summary {
color: #28a745;
font-weight: bold;
}
.learning-levels {
border: 1px solid #dee2e6;
border-radius: 6px;
max-height: 300px;
overflow-y: auto;
}
.level-option {
padding: 12px 16px;
border-bottom: 1px solid #f1f1f1;
cursor: pointer;
transition: all 0.2s ease;
}
.level-option:last-child {
border-bottom: none;
}
.level-option:hover:not(.disabled) {
background: #f8f9fa;
}
.level-option.selected {
background: #e7f3ff;
border-left: 4px solid #007bff;
}
.level-option.disabled {
background: #f8f9fa;
color: #6c757d;
cursor: not-allowed;
opacity: 0.6;
}
.level-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 500;
}
.level-target {
color: #495057;
}
.level-cost {
color: #28a745;
font-weight: bold;
}
.level-option.disabled .level-cost {
color: #dc3545;
}
.no-spells {
text-align: center;
padding: 20px;
color: #6c757d;
font-style: italic;
}
.loading-message {
text-align: center;
padding: 20px;
color: #6c757d;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: scale(0.9) translateY(-20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.modal-content h3 {
margin-top: 0;
margin-bottom: 20px;
color: #333;
border-bottom: 2px solid #1da766;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 15px;
}
.form-row {
display: flex;
gap: 15px;
align-items: flex-start;
}
.form-col {
flex: 1;
min-width: 0;
}
.form-col-main {
flex: 2;
min-width: 200px;
}
.form-col-input {
flex: 1;
min-width: 140px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
.form-group textarea {
height: 80px;
resize: vertical;
}
.help-text {
display: block;
margin-top: 5px;
font-size: 12px;
color: #6c757d;
font-style: italic;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.btn-confirm {
padding: 8px 20px;
background: #1da766;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background 0.2s ease;
}
.btn-confirm:hover:not(:disabled) {
background: #16a085;
}
.btn-confirm:disabled {
background: #ccc;
cursor: not-allowed;
}
.btn-cancel {
padding: 8px 20px;
background: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s ease;
}
.btn-cancel:hover:not(:disabled) {
background: #5a6268;
}
</style>
+3 -136
View File
@@ -191,7 +191,9 @@
</template>
<style scoped>
<style>
/* All common styles moved to main.css */
.cd-view {
padding: 1rem;
}
@@ -302,107 +304,6 @@
font-family: inherit;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
border-radius: 8px;
width: 90%;
max-width: 600px;
max-height: 80vh;
display: flex;
flex-direction: column;
animation: modalSlideIn 0.3s ease;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: scale(0.9) translateY(-20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
.modal-header {
padding: 20px;
border-bottom: 2px solid #1da766;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h3 {
margin: 0;
color: #333;
}
.close-button {
background: none;
border: none;
font-size: 28px;
cursor: pointer;
color: #666;
line-height: 1;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.close-button:hover {
color: #333;
}
.modal-body {
padding: 20px;
overflow-y: auto;
flex: 1;
}
.modal-footer {
padding: 15px 20px;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 10px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
.form-group input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
box-sizing: border-box;
}
.loading {
text-align: center;
padding: 2rem;
@@ -466,40 +367,6 @@
.selected-weapon-details p {
margin: 8px 0;
}
.btn-confirm {
padding: 8px 20px;
background: #1da766;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background 0.2s ease;
}
.btn-confirm:hover:not(:disabled) {
background: #16a085;
}
.btn-confirm:disabled {
background: #ccc;
cursor: not-allowed;
}
.btn-cancel {
padding: 8px 20px;
background: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s ease;
}
.btn-cancel:hover {
background: #5a6268;
}
</style>
<script>
+19 -2
View File
@@ -44,6 +44,17 @@ export default {
Register:'Registrieren',
Maintenance:'Wartung',
UserManagement:'Benutzerverwaltung',
Home:'Startseite',
},
landing:{
title:'BaMoRT - Charakterverwaltung für mein Lieblingsrollenspielsystem',
description:'Bamort ist ein Werkzeug zur Charakterverwaltung für Rollenspiele. Es bietet Funktionen zur Charaktererstellung, -entwicklung und -verwaltung mit Unterstützung für Fertigkeiten, Zauber, Ausrüstung und mehr. Viele Ausrüstungsteile, Fertikeiten und Zauber fehlen noch, da das Projekt noch in der Entwicklung ist.',
frontendVersion:'Frontend Version',
backendVersion:'Backend Version',
version:'Version',
commit:'Commit',
login:'Zum Login',
github:'Projekt auf GitHub',
},
Equipment:'Ausrüstung',
equipment:{
@@ -433,15 +444,21 @@ export default {
notes: 'Notizen'
},
export: {
title: 'Figur exportieren',
selectFormat: 'Format wählen',
formatPDF: 'PDF',
formatVTT: 'VTT Format',
formatBamort: 'Bamort Format (JSON)',
selectTemplate: 'Vorlage',
exportPDF: 'PDF Export',
exporting: 'Exportiere...',
pleaseSelectTemplate: 'Bitte Vorlage auswählen',
exportFailed: 'PDF Export fehlgeschlagen',
pleaseSelectFormat: 'Bitte Format auswählen',
exportFailed: 'Export fehlgeschlagen',
showUserName: 'Benutzername anzeigen',
cancel: 'Abbrechen',
export: 'Exportieren',
generating: 'PDF wird generiert...',
generating: 'Datei wird generiert...',
pleaseWait: 'Bitte warten, dies kann einen Moment dauern',
popupBlocked: 'Popup wurde blockiert. Bitte erlauben Sie Popups für diese Seite.'
},
+21 -3
View File
@@ -42,7 +42,19 @@ export default {
ImportData:'Import Data',
Logout:'Logout',
Register:'Register',
Maintenance:'Maintenance', UserManagement:'User Management', },
Maintenance:'Maintenance', UserManagement:'User Management',
Home:'Home',
},
landing:{
title:'BaMoRT - Character Management for Role-Playing Games',
description:'Bamort is a modern character management tool for role-playing games. It provides comprehensive features for character creation, development, and management with support for skills, spells, equipment, and more.',
frontendVersion:'Frontend Version',
backendVersion:'Backend Version',
version:'Version',
commit:'Commit',
login:'Login',
github:'Project on GitHub',
},
Equipment:'Equipment',
equipment:{
id:'ID',
@@ -429,15 +441,21 @@ export default {
notes: 'Notes'
},
export: {
title: 'Export Character',
selectFormat: 'Select Format',
formatPDF: 'PDF',
formatVTT: 'VTT Format',
formatBamort: 'Bamort Format (JSON)',
selectTemplate: 'Template',
exportPDF: 'Export PDF',
exporting: 'Exporting...',
pleaseSelectTemplate: 'Please select a template',
exportFailed: 'PDF export failed',
pleaseSelectFormat: 'Please select a format',
exportFailed: 'Export failed',
showUserName: 'Show username',
cancel: 'Cancel',
export: 'Export',
generating: 'Generating PDF...',
generating: 'Generating file...',
pleaseWait: 'Please wait, this may take a moment',
popupBlocked: 'Popup was blocked. Please allow popups for this site.'
},
+3 -1
View File
@@ -1,5 +1,6 @@
import { createRouter, createWebHistory } from "vue-router";
import { isLoggedIn } from "../utils/auth"; // Import the helper function
import LandingView from "../views/LandingView.vue";
import LoginView from "../views/LoginView.vue";
import RegisterView from "../views/RegisterView.vue";
import ForgotPasswordView from "../views/ForgotPasswordView.vue";
@@ -18,7 +19,8 @@ import CharacterCreation from "@/components/CharacterCreation.vue";
const routes = [
{ path: "/", name: "Login", component: LoginView },
{ path: "/", name: "Landing", component: LandingView },
{ path: "/login", name: "Login", component: LoginView },
{ path: "/register", name: "Register", component: RegisterView },
{ path: "/forgot-password", name: "ForgotPassword", component: ForgotPasswordView },
{ path: "/reset-password", name: "ResetPassword", component: ResetPasswordView },
+20
View File
@@ -0,0 +1,20 @@
// Frontend version information
export const VERSION = '0.1.21'
// Git commit will be injected at build time or detected from env
export const GIT_COMMIT = import.meta.env.VITE_GIT_COMMIT || 'unknown'
export function getVersion() {
return VERSION
}
export function getGitCommit() {
return GIT_COMMIT
}
export function getVersionInfo() {
return {
version: VERSION,
gitCommit: GIT_COMMIT
}
}
+70
View File
@@ -0,0 +1,70 @@
<template>
<div class="landing-page">
<div class="landing-content">
<div class="dragon-container">
<img src="/Drache.png" alt="Bamort Dragon" class="dragon-image" />
</div>
<div class="info-container">
<h1>{{ $t('landing.title') }}</h1>
<p class="description">{{ $t('landing.description') }}</p>
<div class="version-info">
<p>{{ $t('landing.frontendVersion') }}: {{ frontendVersion }}<!-- ({{ frontendCommit }})--> </p>
<p>{{ $t('landing.backendVersion') }}: {{ backendVersion }}<!-- ({{ backendCommit }})--> </p>
</div>
<div class="action-links">
<router-link to="/login" class="btn btn-primary">
{{ $t('landing.login') }}
</router-link>
<a :href="githubUrl" target="_blank" rel="noopener noreferrer" class="btn btn-secondary">
{{ $t('landing.github') }}
</a>
</div>
</div>
</div>
</div>
</template>
<style scoped>
/* Component-specific styles moved to main.css as per project conventions */
</style>
<script>
import axios from 'axios'
import { getVersion, getGitCommit } from '../version'
export default {
name: "LandingView",
data() {
return {
frontendVersion: getVersion(),
frontendCommit: getGitCommit(),
backendVersion: "Loading...",
backendCommit: "Loading...",
githubUrl: "https://github.com/Bardioc26/bamort"
}
},
mounted() {
this.fetchBackendVersion()
},
methods: {
async fetchBackendVersion() {
try {
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:8180'
const response = await axios.get(`${apiUrl}/api/public/version`)
if (response.data) {
this.backendVersion = response.data.version || "Unknown"
this.backendCommit = response.data.gitCommit || "Unknown"
}
} catch (error) {
console.warn("Could not fetch backend version:", error)
this.backendVersion = "Unavailable"
this.backendCommit = "N/A"
}
}
}
}
</script>
+3 -44
View File
@@ -95,7 +95,9 @@
</div>
</template>
<style scoped>
<style>
/* All common styles moved to main.css */
.user-profile {
padding: var(--padding-lg);
margin-top: 2%;
@@ -164,49 +166,6 @@ h1 {
gap: var(--margin-md);
}
.form-group {
display: flex;
flex-direction: column;
gap: var(--margin-xs);
}
.form-group label {
font-weight: bold;
color: var(--color-text-secondary);
}
.form-group input {
padding: var(--padding-sm);
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
font-size: 1em;
}
.form-group input:focus {
outline: none;
border-color: var(--color-primary);
}
.btn-primary {
background-color: var(--color-primary);
color: white;
padding: var(--padding-sm) var(--padding-md);
border: none;
border-radius: var(--border-radius);
cursor: pointer;
font-size: 1em;
align-self: flex-start;
}
.btn-primary:hover:not(:disabled) {
background-color: var(--color-primary-dark);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.badge-role-standard {
background-color: #6c757d;
color: white;
+76
View File
@@ -0,0 +1,76 @@
#!/bin/bash
# Script to update version number across the project
set -e
if [ -z "$1" ]; then
echo "Usage: $0 <backend_version> [frontend_version]"
echo "Example: $0 0.1.31 # Sets both to 0.1.31"
echo "Example: $0 0.1.31 0.2.0 # Sets different versions"
exit 1
fi
BACKEND_VERSION="$1"
FRONTEND_VERSION="${2:-$1}" # Use backend version if frontend not specified
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"
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"
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"
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"
if [ -f "$FRONTEND_VERSION_MD" ]; then
sed -i "s/## Current Version: .*/## Current Version: $FRONTEND_VERSION/" "$FRONTEND_VERSION_MD"
echo "✓ Updated $FRONTEND_VERSION_MD to $FRONTEND_VERSION"
fi
echo ""
echo "✅ Version update complete!"
echo " Backend: $BACKEND_VERSION"
echo " Frontend: $FRONTEND_VERSION"
echo ""
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"
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"
fi
echo "4. Push: git push && git push --tags"