Initial commit - Help Service for Coolify
This commit is contained in:
54
.dockerignore
Normal file
54
.dockerignore
Normal file
@@ -0,0 +1,54 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
.env
|
||||
.venv
|
||||
venv/
|
||||
ENV/
|
||||
.eggs
|
||||
*.egg-info/
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
.mypy_cache/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Docker
|
||||
Dockerfile*
|
||||
docker-compose*.yml
|
||||
.docker/
|
||||
|
||||
# Documentation
|
||||
docs/
|
||||
LICENSE
|
||||
|
||||
# Tests
|
||||
tests/
|
||||
test_*.py
|
||||
*_test.py
|
||||
conftest.py
|
||||
|
||||
# CI/CD
|
||||
.github/
|
||||
.gitlab-ci.yml
|
||||
.travis.yml
|
||||
Jenkinsfile
|
||||
|
||||
# Misc
|
||||
*.log
|
||||
*.tmp
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
68
Dockerfile
Normal file
68
Dockerfile
Normal file
@@ -0,0 +1,68 @@
|
||||
# Help Service - Bulletproof Production Dockerfile
|
||||
# Security-hardened, multi-stage build
|
||||
|
||||
# =============================================================================
|
||||
# Stage 1: Build dependencies
|
||||
# =============================================================================
|
||||
FROM python:3.13-slim-bookworm AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir --user --no-warn-script-location -r requirements.txt
|
||||
|
||||
# =============================================================================
|
||||
# Stage 2: Production image
|
||||
# =============================================================================
|
||||
FROM python:3.13-slim-bookworm
|
||||
|
||||
# OCI Labels
|
||||
LABEL org.opencontainers.image.title="Help Service"
|
||||
LABEL org.opencontainers.image.description="Documentation help service for Kurs-Booking"
|
||||
LABEL org.opencontainers.image.vendor="webideas24"
|
||||
LABEL org.opencontainers.image.version="1.0.0"
|
||||
LABEL org.opencontainers.image.source="https://git.islandpferde-melanieworbs.de/webideas24/help-service"
|
||||
|
||||
# Security: Install tini and minimal runtime dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
tini \
|
||||
curl \
|
||||
ca-certificates \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
|
||||
&& rm -rf /root/.cache
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy Python packages from builder
|
||||
COPY --from=builder /root/.local /home/helpuser/.local
|
||||
|
||||
# Security: Create non-root user with no shell
|
||||
RUN groupadd -r -g 1000 helpuser && \
|
||||
useradd -r -u 1000 -g helpuser -s /usr/sbin/nologin -d /home/helpuser helpuser && \
|
||||
chown -R helpuser:helpuser /app /home/helpuser
|
||||
|
||||
# Copy application files
|
||||
COPY --chown=helpuser:helpuser app.py ./
|
||||
COPY --chown=helpuser:helpuser content/ ./content/
|
||||
COPY --chown=helpuser:helpuser templates/ ./templates/
|
||||
COPY --chown=helpuser:helpuser static/ ./static/
|
||||
|
||||
# Switch to non-root user
|
||||
USER helpuser
|
||||
|
||||
# Environment
|
||||
ENV PATH=/home/helpuser/.local/bin:$PATH \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PIP_NO_CACHE_DIR=1
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
STOPSIGNAL SIGTERM
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
||||
CMD curl -fsS http://localhost:5000/health || exit 1
|
||||
|
||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "--threads", "4", "--timeout", "30", "--graceful-timeout", "10", "app:app"]
|
||||
551
app.py
Normal file
551
app.py
Normal file
@@ -0,0 +1,551 @@
|
||||
"""
|
||||
Kurs-Booking Help Service
|
||||
Python Flask App with Bootstrap GUI for plugin documentation.
|
||||
|
||||
Supports Markdown files with YAML frontmatter for easy content editing.
|
||||
Hot-reload enabled - no container restart needed for content changes.
|
||||
|
||||
@package KursBooking
|
||||
@since 1.4.0
|
||||
"""
|
||||
|
||||
import os
|
||||
import glob
|
||||
from datetime import datetime
|
||||
from functools import wraps
|
||||
from flask import Flask, render_template, jsonify, request, Response
|
||||
from flask_cors import CORS
|
||||
import re
|
||||
import yaml
|
||||
import markdown
|
||||
import frontmatter
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
||||
# Internal area credentials (can be overridden via environment variables)
|
||||
INTERN_USER = os.environ.get('INTERN_USER', 'admin')
|
||||
INTERN_PASS = os.environ.get('INTERN_PASS', 'kurs2024!')
|
||||
|
||||
|
||||
def check_intern_auth(username: str, password: str) -> bool:
|
||||
"""Verify internal area credentials."""
|
||||
return username == INTERN_USER and password == INTERN_PASS
|
||||
|
||||
|
||||
def require_intern_auth(f):
|
||||
"""Decorator for password-protected internal routes."""
|
||||
@wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
auth = request.authorization
|
||||
if not auth or not check_intern_auth(auth.username, auth.password):
|
||||
return Response(
|
||||
'Zugang zum internen Bereich erfordert Anmeldung.',
|
||||
401,
|
||||
{'WWW-Authenticate': 'Basic realm="Interner Bereich"'}
|
||||
)
|
||||
return f(*args, **kwargs)
|
||||
return decorated
|
||||
|
||||
# Content directory (supports DOCS_PATH env var for Coolify bind mounts)
|
||||
CONTENT_DIR = os.environ.get('DOCS_PATH', os.path.join(os.path.dirname(__file__), 'content'))
|
||||
|
||||
# Cache for content (with timestamp for hot-reload)
|
||||
_content_cache = {
|
||||
'data': None,
|
||||
'timestamp': 0
|
||||
}
|
||||
|
||||
|
||||
def generate_toc_from_html(html_content: str) -> tuple[str, list[dict]]:
|
||||
"""
|
||||
Extract headings from HTML and generate TOC.
|
||||
Also adds IDs to headings if missing.
|
||||
|
||||
Returns:
|
||||
tuple: (modified_html, toc_list)
|
||||
"""
|
||||
toc = []
|
||||
heading_pattern = re.compile(r'<(h[2-3])([^>]*)>(.*?)</\1>', re.IGNORECASE | re.DOTALL)
|
||||
|
||||
def add_id_to_heading(match):
|
||||
tag = match.group(1)
|
||||
attrs = match.group(2)
|
||||
text = match.group(3)
|
||||
|
||||
# Strip HTML tags from text for ID generation
|
||||
clean_text = re.sub(r'<[^>]+>', '', text)
|
||||
|
||||
# Check if ID already exists
|
||||
id_match = re.search(r'id=["\']([^"\']+)["\']', attrs)
|
||||
if id_match:
|
||||
heading_id = id_match.group(1)
|
||||
else:
|
||||
# Generate ID from text
|
||||
heading_id = re.sub(r'[^\w\s-]', '', clean_text.lower())
|
||||
heading_id = re.sub(r'[\s]+', '-', heading_id.strip())
|
||||
heading_id = heading_id[:50] # Limit length
|
||||
attrs = f' id="{heading_id}"{attrs}'
|
||||
|
||||
# Add to TOC
|
||||
level = 2 if tag.lower() == 'h2' else 3
|
||||
toc.append({
|
||||
'id': heading_id,
|
||||
'title': clean_text.strip(),
|
||||
'level': level
|
||||
})
|
||||
|
||||
return f'<{tag}{attrs}>{text}</{tag}>'
|
||||
|
||||
modified_html = heading_pattern.sub(add_id_to_heading, html_content)
|
||||
return modified_html, toc
|
||||
|
||||
|
||||
def get_content_mtime():
|
||||
"""Get the latest modification time of content files (including subfolders)."""
|
||||
mtime = 0
|
||||
|
||||
# Check config file
|
||||
config_file = os.path.join(CONTENT_DIR, '_config.yaml')
|
||||
if os.path.exists(config_file):
|
||||
mtime = max(mtime, os.path.getmtime(config_file))
|
||||
|
||||
# Check all markdown files recursively (including subfolders)
|
||||
for md_file in glob.glob(os.path.join(CONTENT_DIR, '**/*.md'), recursive=True):
|
||||
mtime = max(mtime, os.path.getmtime(md_file))
|
||||
|
||||
return mtime
|
||||
|
||||
|
||||
def load_help_content():
|
||||
"""
|
||||
Load help content from Markdown files with hot-reload support.
|
||||
|
||||
Structure:
|
||||
- content/_config.yaml: Navigation/sections configuration
|
||||
- content/*.md: Topic files with YAML frontmatter
|
||||
- content/topic/*.md: Subpage files (e.g., sevdesk/rechnungsnummern.md)
|
||||
"""
|
||||
global _content_cache
|
||||
|
||||
# Check if cache is valid (hot-reload)
|
||||
current_mtime = get_content_mtime()
|
||||
if _content_cache['data'] and _content_cache['timestamp'] >= current_mtime:
|
||||
return _content_cache['data']
|
||||
|
||||
# Load configuration
|
||||
config_file = os.path.join(CONTENT_DIR, '_config.yaml')
|
||||
config = {}
|
||||
if os.path.exists(config_file):
|
||||
with open(config_file, 'r', encoding='utf-8') as f:
|
||||
config = yaml.safe_load(f) or {}
|
||||
|
||||
# Load all markdown topics recursively (including subfolders)
|
||||
topics = {}
|
||||
md_extensions = ['tables', 'fenced_code', 'codehilite', 'toc', 'attr_list']
|
||||
|
||||
for md_file in glob.glob(os.path.join(CONTENT_DIR, '**/*.md'), recursive=True):
|
||||
try:
|
||||
post = frontmatter.load(md_file)
|
||||
|
||||
# Generate path-based ID: sevdesk/rechnungsnummern.md -> sevdesk/rechnungsnummern
|
||||
rel_path = os.path.relpath(md_file, CONTENT_DIR)
|
||||
path_id = os.path.splitext(rel_path)[0].replace(os.sep, '/')
|
||||
|
||||
# Use frontmatter id if provided, otherwise use path-based id
|
||||
topic_id = post.get('id', path_id)
|
||||
|
||||
# Determine parent topic (sevdesk/rechnungsnummern -> sevdesk)
|
||||
parent_id = os.path.dirname(path_id) if '/' in path_id else None
|
||||
|
||||
# Convert markdown to HTML
|
||||
html_content = markdown.markdown(
|
||||
post.content,
|
||||
extensions=md_extensions,
|
||||
output_format='html5'
|
||||
)
|
||||
|
||||
# Auto-generate TOC from headings (or use manual if provided)
|
||||
manual_toc = post.get('toc', [])
|
||||
if manual_toc:
|
||||
toc = manual_toc
|
||||
else:
|
||||
html_content, toc = generate_toc_from_html(html_content)
|
||||
|
||||
topics[topic_id] = {
|
||||
'id': topic_id,
|
||||
'path_id': path_id,
|
||||
'parent': parent_id,
|
||||
'children': [], # Will be populated after all topics loaded
|
||||
'title': post.get('title', topic_id.split('/')[-1].replace('-', ' ').title()),
|
||||
'icon': post.get('icon', 'file-text'),
|
||||
'description': post.get('description', ''),
|
||||
'section': post.get('section', 'Allgemein'),
|
||||
'tags': post.get('tags', []),
|
||||
'related': post.get('related', []),
|
||||
'tips': post.get('tips', []),
|
||||
'toc': toc,
|
||||
'content': html_content,
|
||||
'order': post.get('order', 999)
|
||||
}
|
||||
except Exception as e:
|
||||
app.logger.error(f"Error loading {md_file}: {e}")
|
||||
|
||||
# Build parent-child relationships
|
||||
for topic in topics.values():
|
||||
parent_id = topic.get('parent')
|
||||
if parent_id and parent_id in topics:
|
||||
topics[parent_id]['children'].append(topic['id'])
|
||||
|
||||
# Build sections from config or auto-generate
|
||||
sections = config.get('sections', [])
|
||||
|
||||
if sections:
|
||||
# Use config-defined sections
|
||||
for section in sections:
|
||||
section_topics = []
|
||||
for topic_id in section.get('topics', []):
|
||||
if topic_id in topics:
|
||||
section_topics.append(topics[topic_id])
|
||||
section['topics'] = sorted(section_topics, key=lambda x: x.get('order', 999))
|
||||
else:
|
||||
# Auto-generate sections from topic metadata
|
||||
section_map = {}
|
||||
for topic in topics.values():
|
||||
section_name = topic.get('section', 'Allgemein')
|
||||
if section_name not in section_map:
|
||||
section_map[section_name] = {
|
||||
'title': section_name,
|
||||
'icon': 'folder',
|
||||
'topics': []
|
||||
}
|
||||
section_map[section_name]['topics'].append(topic)
|
||||
|
||||
sections = list(section_map.values())
|
||||
for section in sections:
|
||||
section['topics'] = sorted(section['topics'], key=lambda x: x.get('order', 999))
|
||||
|
||||
result = {
|
||||
'sections': sections,
|
||||
'all_topics': topics,
|
||||
'last_updated': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
# Update cache
|
||||
_content_cache['data'] = result
|
||||
_content_cache['timestamp'] = current_mtime
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def find_topic(topic_id):
|
||||
"""Find a topic by ID."""
|
||||
content = load_help_content()
|
||||
return content.get('all_topics', {}).get(topic_id)
|
||||
|
||||
|
||||
@app.route('/health')
|
||||
def health():
|
||||
"""Health check endpoint for Coolify/Docker."""
|
||||
return jsonify({'status': 'healthy', 'service': 'help-service'})
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Main help page."""
|
||||
content = load_help_content()
|
||||
return render_template('index.html', content=content)
|
||||
|
||||
|
||||
@app.route('/topic/<path:topic_id>')
|
||||
def topic(topic_id):
|
||||
"""Single topic page (supports subpages like /topic/sevdesk/rechnungsnummern)."""
|
||||
content = load_help_content()
|
||||
topic_data = find_topic(topic_id)
|
||||
|
||||
if not topic_data:
|
||||
return render_template('404.html', content=content, topic_id=topic_id), 404
|
||||
|
||||
# Get parent topic for breadcrumb
|
||||
parent_topic = None
|
||||
if topic_data.get('parent'):
|
||||
parent_topic = find_topic(topic_data['parent'])
|
||||
|
||||
# Get child topics
|
||||
child_topics = []
|
||||
for child_id in topic_data.get('children', []):
|
||||
child = find_topic(child_id)
|
||||
if child:
|
||||
child_topics.append(child)
|
||||
# Sort children by order
|
||||
child_topics = sorted(child_topics, key=lambda x: x.get('order', 999))
|
||||
|
||||
# Get related topics
|
||||
related_topics = []
|
||||
for related_id in topic_data.get('related', []):
|
||||
related = find_topic(related_id)
|
||||
if related:
|
||||
related_topics.append(related)
|
||||
|
||||
return render_template(
|
||||
'topic.html',
|
||||
topic=topic_data,
|
||||
parent_topic=parent_topic,
|
||||
child_topics=child_topics,
|
||||
related_topics=related_topics,
|
||||
content=content
|
||||
)
|
||||
|
||||
|
||||
@app.route('/search')
|
||||
def search():
|
||||
"""Search help content."""
|
||||
query = request.args.get('q', '').lower().strip()
|
||||
content = load_help_content()
|
||||
results = []
|
||||
|
||||
if query and len(query) >= 2:
|
||||
for topic in content.get('all_topics', {}).values():
|
||||
score = 0
|
||||
|
||||
# Title match (highest priority)
|
||||
if query in topic.get('title', '').lower():
|
||||
score += 100
|
||||
|
||||
# Tag match
|
||||
for tag in topic.get('tags', []):
|
||||
if query in tag.lower():
|
||||
score += 50
|
||||
|
||||
# Description match
|
||||
if query in topic.get('description', '').lower():
|
||||
score += 30
|
||||
|
||||
# Content match
|
||||
if query in topic.get('content', '').lower():
|
||||
score += 10
|
||||
|
||||
if score > 0:
|
||||
results.append({
|
||||
'id': topic.get('id'),
|
||||
'title': topic.get('title'),
|
||||
'description': topic.get('description', ''),
|
||||
'section': topic.get('section'),
|
||||
'score': score
|
||||
})
|
||||
|
||||
# Sort by score
|
||||
results = sorted(results, key=lambda x: x['score'], reverse=True)
|
||||
|
||||
return render_template('search.html', query=query, results=results, content=content)
|
||||
|
||||
|
||||
@app.route('/api/content')
|
||||
def api_content():
|
||||
"""API endpoint for help content."""
|
||||
content = load_help_content()
|
||||
return jsonify(content)
|
||||
|
||||
|
||||
@app.route('/api/topics')
|
||||
def api_topics():
|
||||
"""API endpoint for all topics."""
|
||||
content = load_help_content()
|
||||
return jsonify(list(content.get('all_topics', {}).values()))
|
||||
|
||||
|
||||
@app.route('/api/topic/<path:topic_id>')
|
||||
def api_topic(topic_id):
|
||||
"""API endpoint for single topic (supports subpages)."""
|
||||
topic = find_topic(topic_id)
|
||||
if not topic:
|
||||
return jsonify({'error': 'Topic not found'}), 404
|
||||
return jsonify(topic)
|
||||
|
||||
|
||||
@app.route('/api/search')
|
||||
def api_search():
|
||||
"""API endpoint for search."""
|
||||
query = request.args.get('q', '').lower()
|
||||
content = load_help_content()
|
||||
results = []
|
||||
|
||||
if query:
|
||||
for topic in content.get('all_topics', {}).values():
|
||||
if (query in topic.get('title', '').lower() or
|
||||
query in topic.get('content', '').lower() or
|
||||
any(query in tag.lower() for tag in topic.get('tags', []))):
|
||||
results.append({
|
||||
'id': topic.get('id'),
|
||||
'title': topic.get('title'),
|
||||
'section': topic.get('section')
|
||||
})
|
||||
|
||||
return jsonify({'query': query, 'results': results})
|
||||
|
||||
|
||||
@app.route('/api/reload')
|
||||
def api_reload():
|
||||
"""Force reload content cache."""
|
||||
global _content_cache
|
||||
_content_cache = {'data': None, 'timestamp': 0}
|
||||
content = load_help_content()
|
||||
return jsonify({
|
||||
'status': 'reloaded',
|
||||
'topics_count': len(content.get('all_topics', {})),
|
||||
'sections_count': len(content.get('sections', []))
|
||||
})
|
||||
|
||||
|
||||
# ============================================
|
||||
# INTERNAL AREA (Password Protected)
|
||||
# ============================================
|
||||
|
||||
# Internal content directory (relative to CONTENT_DIR parent)
|
||||
INTERN_CONTENT_DIR = os.path.join(os.path.dirname(CONTENT_DIR), 'content-intern')
|
||||
|
||||
# Cache for internal content
|
||||
_intern_cache = {
|
||||
'data': None,
|
||||
'timestamp': 0
|
||||
}
|
||||
|
||||
|
||||
def get_intern_mtime():
|
||||
"""Get the latest modification time of internal content files."""
|
||||
mtime = 0
|
||||
config_file = os.path.join(INTERN_CONTENT_DIR, '_config.yaml')
|
||||
if os.path.exists(config_file):
|
||||
mtime = max(mtime, os.path.getmtime(config_file))
|
||||
for md_file in glob.glob(os.path.join(INTERN_CONTENT_DIR, '**/*.md'), recursive=True):
|
||||
mtime = max(mtime, os.path.getmtime(md_file))
|
||||
return mtime
|
||||
|
||||
|
||||
def load_intern_content():
|
||||
"""Load internal help content with hot-reload support."""
|
||||
global _intern_cache
|
||||
|
||||
# Check if directory exists
|
||||
if not os.path.exists(INTERN_CONTENT_DIR):
|
||||
return {'sections': [], 'all_topics': {}, 'last_updated': None}
|
||||
|
||||
# Check if cache is valid
|
||||
current_mtime = get_intern_mtime()
|
||||
if _intern_cache['data'] and _intern_cache['timestamp'] >= current_mtime:
|
||||
return _intern_cache['data']
|
||||
|
||||
# Load configuration
|
||||
config_file = os.path.join(INTERN_CONTENT_DIR, '_config.yaml')
|
||||
config = {}
|
||||
if os.path.exists(config_file):
|
||||
with open(config_file, 'r', encoding='utf-8') as f:
|
||||
config = yaml.safe_load(f) or {}
|
||||
|
||||
# Load markdown topics
|
||||
topics = {}
|
||||
md_extensions = ['tables', 'fenced_code', 'codehilite', 'toc', 'attr_list']
|
||||
|
||||
for md_file in glob.glob(os.path.join(INTERN_CONTENT_DIR, '**/*.md'), recursive=True):
|
||||
try:
|
||||
post = frontmatter.load(md_file)
|
||||
rel_path = os.path.relpath(md_file, INTERN_CONTENT_DIR)
|
||||
path_id = os.path.splitext(rel_path)[0].replace(os.sep, '/')
|
||||
topic_id = post.get('id', path_id)
|
||||
parent_id = os.path.dirname(path_id) if '/' in path_id else None
|
||||
|
||||
html_content = markdown.markdown(
|
||||
post.content,
|
||||
extensions=md_extensions,
|
||||
output_format='html5'
|
||||
)
|
||||
html_content, toc = generate_toc_from_html(html_content)
|
||||
|
||||
topics[topic_id] = {
|
||||
'id': topic_id,
|
||||
'path_id': path_id,
|
||||
'parent': parent_id,
|
||||
'children': [],
|
||||
'title': post.get('title', topic_id.split('/')[-1].replace('-', ' ').title()),
|
||||
'icon': post.get('icon', 'file-lock'),
|
||||
'description': post.get('description', ''),
|
||||
'toc': toc,
|
||||
'content': html_content,
|
||||
'order': post.get('order', 999)
|
||||
}
|
||||
except Exception as e:
|
||||
app.logger.error(f"Error loading intern {md_file}: {e}")
|
||||
|
||||
# Build parent-child relationships
|
||||
for topic in topics.values():
|
||||
parent_id = topic.get('parent')
|
||||
if parent_id and parent_id in topics:
|
||||
topics[parent_id]['children'].append(topic['id'])
|
||||
|
||||
# Build sections from config
|
||||
sections = config.get('sections', [])
|
||||
if sections:
|
||||
for section in sections:
|
||||
section_topics = []
|
||||
for topic_id in section.get('topics', []):
|
||||
if topic_id in topics:
|
||||
section_topics.append(topics[topic_id])
|
||||
section['topics'] = sorted(section_topics, key=lambda x: x.get('order', 999))
|
||||
|
||||
result = {
|
||||
'sections': sections,
|
||||
'all_topics': topics,
|
||||
'last_updated': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
_intern_cache['data'] = result
|
||||
_intern_cache['timestamp'] = current_mtime
|
||||
return result
|
||||
|
||||
|
||||
def find_intern_topic(topic_id):
|
||||
"""Find an internal topic by ID."""
|
||||
content = load_intern_content()
|
||||
return content.get('all_topics', {}).get(topic_id)
|
||||
|
||||
|
||||
@app.route('/intern/')
|
||||
@require_intern_auth
|
||||
def intern_index():
|
||||
"""Internal area main page."""
|
||||
content = load_intern_content()
|
||||
return render_template('intern/index.html', content=content)
|
||||
|
||||
|
||||
@app.route('/intern/topic/<path:topic_id>')
|
||||
@require_intern_auth
|
||||
def intern_topic(topic_id):
|
||||
"""Internal topic page."""
|
||||
content = load_intern_content()
|
||||
topic_data = find_intern_topic(topic_id)
|
||||
|
||||
if not topic_data:
|
||||
return render_template('intern/404.html', content=content, topic_id=topic_id), 404
|
||||
|
||||
parent_topic = None
|
||||
if topic_data.get('parent'):
|
||||
parent_topic = find_intern_topic(topic_data['parent'])
|
||||
|
||||
child_topics = []
|
||||
for child_id in topic_data.get('children', []):
|
||||
child = find_intern_topic(child_id)
|
||||
if child:
|
||||
child_topics.append(child)
|
||||
child_topics = sorted(child_topics, key=lambda x: x.get('order', 999))
|
||||
|
||||
return render_template(
|
||||
'intern/topic.html',
|
||||
topic=topic_data,
|
||||
parent_topic=parent_topic,
|
||||
child_topics=child_topics,
|
||||
content=content
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
90
content/_config.yaml
Normal file
90
content/_config.yaml
Normal file
@@ -0,0 +1,90 @@
|
||||
# Kurs-Booking Help Service Configuration
|
||||
# Defines sections and their topics for navigation
|
||||
|
||||
sections:
|
||||
- title: "Erste Schritte"
|
||||
icon: "rocket-takeoff"
|
||||
topics:
|
||||
- schnellstart
|
||||
|
||||
- title: "Grundeinstellungen"
|
||||
icon: "gear"
|
||||
topics:
|
||||
- general
|
||||
- modules
|
||||
- masterdata
|
||||
|
||||
- title: "Buchung & Preise"
|
||||
icon: "cart"
|
||||
topics:
|
||||
- booking
|
||||
- buchungsfelder-uebersicht
|
||||
- fields
|
||||
- feldtypen
|
||||
- prices
|
||||
- preisvarianten
|
||||
- payments
|
||||
- bank
|
||||
|
||||
- title: "Kursarten"
|
||||
icon: "tags"
|
||||
topics:
|
||||
- dienstleistungen
|
||||
- produktarten-filter
|
||||
- kursarten/uebersicht
|
||||
- kursarten/online-termine
|
||||
|
||||
- title: "Rechtliches"
|
||||
icon: "file-earmark-text"
|
||||
topics:
|
||||
- legal
|
||||
- cancellation
|
||||
- stornierung-workflow
|
||||
|
||||
- title: "E-Mail System"
|
||||
icon: "envelope"
|
||||
topics:
|
||||
- emails
|
||||
- email_templates
|
||||
- email-platzhalter
|
||||
|
||||
- title: "Integrationen"
|
||||
icon: "plug"
|
||||
topics:
|
||||
- sevdesk
|
||||
- video
|
||||
- zoom
|
||||
- import
|
||||
|
||||
- title: "Theme"
|
||||
icon: "palette"
|
||||
topics:
|
||||
- theme/slider
|
||||
- theme/hero-banner
|
||||
|
||||
- title: "Marketing"
|
||||
icon: "megaphone"
|
||||
topics:
|
||||
- popup-neuigkeiten
|
||||
|
||||
- title: "Sicherheit & Extras"
|
||||
icon: "shield"
|
||||
topics:
|
||||
- spam
|
||||
- portal
|
||||
|
||||
- title: "Tipps & Tricks"
|
||||
icon: "lightbulb"
|
||||
topics:
|
||||
- features
|
||||
- shortcodes
|
||||
- kurs-vorlagen
|
||||
- best-practices
|
||||
- erweiterte-optionen
|
||||
- troubleshooting
|
||||
|
||||
- title: "Entwicklung"
|
||||
icon: "code-slash"
|
||||
topics:
|
||||
- entwicklung/sprint-uebersicht
|
||||
- entwicklung/next-session
|
||||
25
content/bank.md
Normal file
25
content/bank.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
id: bank
|
||||
title: Bankverbindung
|
||||
icon: bank
|
||||
description: IBAN und Kontodaten fuer Ueberweisungen
|
||||
section: Buchung & Preise
|
||||
tags: [Bank, IBAN, Ueberweisung, Konto]
|
||||
related: [payments, prices]
|
||||
order: 15
|
||||
---
|
||||
|
||||
# Bankverbindung
|
||||
|
||||
Diese Daten erscheinen in Buchungsbestaetigungen und Rechnungen.
|
||||
|
||||
## Bankdaten
|
||||
|
||||
| Feld | Beschreibung |
|
||||
|------|--------------|
|
||||
| **Kontoinhaber** | Name wie bei der Bank registriert |
|
||||
| **IBAN** | Internationale Kontonummer |
|
||||
| **BIC** | Bank-Identifikationscode |
|
||||
| **Bank** | Name der Bank (optional) |
|
||||
|
||||
> **Tipp:** Die Bankdaten werden automatisch in die Zahlungshinweise der Buchungsbestaetigung eingefuegt.
|
||||
57
content/best-practices.md
Normal file
57
content/best-practices.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
id: best-practices
|
||||
title: Best Practices
|
||||
icon: star
|
||||
description: Tipps fuer optimale Nutzung
|
||||
section: Tipps & Support
|
||||
tags: [Tipps, Best Practices, Empfehlungen]
|
||||
related: [general, booking]
|
||||
order: 60
|
||||
---
|
||||
|
||||
# Best Practices
|
||||
|
||||
Empfehlungen fuer die optimale Nutzung des Buchungssystems.
|
||||
|
||||
## Einrichtung
|
||||
|
||||
| Empfehlung | Grund |
|
||||
|------------|-------|
|
||||
| **Rechtliche Seiten zuerst** | AGB, Datenschutz, Widerruf vor Go-Live |
|
||||
| **E-Mails testen** | Test-Buchung mit eigener E-Mail |
|
||||
| **sevDesk verbinden** | Vor der ersten echten Buchung |
|
||||
|
||||
## Kurse anlegen
|
||||
|
||||
| Empfehlung | Grund |
|
||||
|------------|-------|
|
||||
| **Aussagekraeftige Titel** | Fuer Suche und E-Mails |
|
||||
| **Produktart richtig waehlen** | Steuert Felder und Logik |
|
||||
| **Teilnehmerlimit setzen** | Verhindert Ueberbuchung |
|
||||
| **Preisvarianten nutzen** | Fruehbucher, Ermaessigungen |
|
||||
|
||||
## Buchungsformular
|
||||
|
||||
| Empfehlung | Grund |
|
||||
|------------|-------|
|
||||
| **Weniger Felder** | Hoehere Conversion |
|
||||
| **Pflichtfelder minimieren** | Nur wirklich Notwendiges |
|
||||
| **Mobile testen** | Viele Nutzer buchen mobil |
|
||||
|
||||
## E-Mails
|
||||
|
||||
| Empfehlung | Grund |
|
||||
|------------|-------|
|
||||
| **Klare Betreffzeilen** | Sofort erkennbar |
|
||||
| **Alle Infos enthalten** | Weniger Rueckfragen |
|
||||
| **Bankdaten bei Ueberweisung** | Direkt in der E-Mail |
|
||||
|
||||
## Wartung
|
||||
|
||||
| Empfehlung | Grund |
|
||||
|------------|-------|
|
||||
| **Regelmaessige Backups** | Buchungen sind wertvoll |
|
||||
| **Alte Kurse archivieren** | Uebersichtlichkeit |
|
||||
| **E-Mail-Zustellung pruefen** | Spam-Ordner checken |
|
||||
|
||||
> **Tipp:** Starten Sie mit wenigen Optionen und erweitern Sie bei Bedarf.
|
||||
44
content/booking.md
Normal file
44
content/booking.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
id: booking
|
||||
title: Buchungseinstellungen
|
||||
icon: calendar-check
|
||||
description: Buchungsnummern, Tickets und Optionen
|
||||
section: Buchung & Preise
|
||||
tags: [Buchung, Tickets, Einstellungen]
|
||||
related: [fields, prices, payments]
|
||||
order: 10
|
||||
---
|
||||
|
||||
# Buchungseinstellungen
|
||||
|
||||
## Buchungsnummern
|
||||
|
||||
| Einstellung | Beschreibung |
|
||||
|-------------|--------------|
|
||||
| **Praefix** | Wird vor die Nummer gesetzt (z.B. "BK" ergibt BK-0001) |
|
||||
| **Naechste Nummer** | Fortlaufende Nummer fuer neue Buchungen |
|
||||
|
||||
## Ticket-Einstellungen
|
||||
|
||||
| Einstellung | Beschreibung |
|
||||
|-------------|--------------|
|
||||
| **Standard-Ticketname** | z.B. "Ticket", "Teilnahme", "Platz" |
|
||||
| **Max. Tickets pro Buchung** | Begrenzt die Anzahl (Standard: 10) |
|
||||
|
||||
## Bestaetigungs-Einstellungen
|
||||
|
||||
| Einstellung | Beschreibung |
|
||||
|-------------|--------------|
|
||||
| **Gueltigkeit** | Stunden bis der Bestaetigungslink ablaeuft (Standard: 48h) |
|
||||
| **Admin-E-Mail** | Empfaenger fuer Buchungsbenachrichtigungen |
|
||||
| **BCC an Admin** | Kopie aller Kunden-E-Mails an Admin |
|
||||
|
||||
## Online-Kurse mit Zoom
|
||||
|
||||
Fuer Online-Kurse (Produktarten G, H, I, L) kann die **Zoom-Integration** aktiviert werden:
|
||||
|
||||
- Automatisches Anwesenheits-Tracking
|
||||
- Meeting-Status wird live aktualisiert
|
||||
- Aufnahmen werden automatisch zugewiesen
|
||||
|
||||
→ **Einrichtung:** [Zoom Webhooks](/topic/zoom)
|
||||
214
content/buchungsfelder-uebersicht.md
Normal file
214
content/buchungsfelder-uebersicht.md
Normal file
@@ -0,0 +1,214 @@
|
||||
---
|
||||
id: buchungsfelder-uebersicht
|
||||
title: Buchungsfelder Uebersicht
|
||||
icon: table
|
||||
description: Komplette Uebersicht aller Buchungsfelder und deren Konfiguration
|
||||
section: Buchung & Preise
|
||||
tags: [Felder, Uebersicht, Konfiguration, Pflichtfelder]
|
||||
related: [fields, feldtypen, produktarten-filter, booking]
|
||||
order: 10
|
||||
---
|
||||
|
||||
# Buchungsfelder Uebersicht
|
||||
|
||||
Diese Seite zeigt alle verfuegbaren Buchungsfelder und deren Standardkonfiguration.
|
||||
|
||||
---
|
||||
|
||||
## Standard-Buchungsfelder
|
||||
|
||||
### Kontaktdaten (Basis)
|
||||
|
||||
| Feld | Typ | Pflicht | Produktarten | Beschreibung |
|
||||
|------|-----|---------|--------------|--------------|
|
||||
| **Name** | text | Ja | Alle | Vor- und Nachname des Kunden |
|
||||
| **E-Mail** | email | Ja | Alle | Fuer Buchungsbestaetigung |
|
||||
| **Telefon** | tel | Ja | Alle | Fuer Rueckfragen |
|
||||
|
||||
### Adressdaten
|
||||
|
||||
| Feld | Typ | Pflicht | Produktarten | Beschreibung |
|
||||
|------|-----|---------|--------------|--------------|
|
||||
| **Strasse** | text | Optional | A, B, D1 | Rechnungsadresse |
|
||||
| **PLZ** | text | Optional | A, B, D1 | Postleitzahl |
|
||||
| **Ort** | text | Optional | A, B, D1 | Stadt/Gemeinde |
|
||||
| **Land** | select | Optional | A, B, D1 | Laenderauswahl |
|
||||
|
||||
### Kurs-spezifische Felder
|
||||
|
||||
| Feld | Typ | Pflicht | Produktarten | Beschreibung |
|
||||
|------|-----|---------|--------------|--------------|
|
||||
| **Reiterfahrung** | select | Optional | A, B | Jahre Erfahrung |
|
||||
| **Eigenes Pferd** | radio | Optional | A | Ja/Nein |
|
||||
| **Pferdename** | text | Bedingt | A | Wenn eigenes Pferd = Ja |
|
||||
| **Leihpferd benoetigt** | checkbox | Optional | A | Reservierung |
|
||||
| **Gastbox benoetigt** | checkbox | Optional | A | Unterkunft fuers Pferd |
|
||||
|
||||
### Rechtliche Felder
|
||||
|
||||
| Feld | Typ | Pflicht | Produktarten | Beschreibung |
|
||||
|------|-----|---------|--------------|--------------|
|
||||
| **AGB akzeptiert** | agreement | Ja | Alle | Link zu AGB |
|
||||
| **Datenschutz** | agreement | Ja | Alle | DSGVO-Einwilligung |
|
||||
| **Widerrufsverzicht** | agreement | Ja | E, F | Fuer Video-Kurse |
|
||||
| **Newsletter** | checkbox | Nein | Alle | Marketing-Einwilligung |
|
||||
|
||||
---
|
||||
|
||||
## Feld-Kategorien
|
||||
|
||||
### Basis-Felder (nicht loeschbar)
|
||||
|
||||
Diese Felder sind systemrelevant und koennen nicht entfernt werden:
|
||||
|
||||
- Name
|
||||
- E-Mail
|
||||
- Telefon
|
||||
- AGB akzeptiert
|
||||
- Datenschutz
|
||||
|
||||
### Optionale Felder
|
||||
|
||||
Diese Felder koennen aktiviert/deaktiviert werden:
|
||||
|
||||
- Alle Adressfelder
|
||||
- Alle kurs-spezifischen Felder
|
||||
- Newsletter
|
||||
- Anmerkungen
|
||||
|
||||
### Benutzerdefinierte Felder
|
||||
|
||||
Sie koennen eigene Felder hinzufuegen unter **Einstellungen > Buchungsfelder**.
|
||||
|
||||
---
|
||||
|
||||
## Pflichtfeld-Logik
|
||||
|
||||
### Immer Pflicht
|
||||
|
||||
| Feld | Grund |
|
||||
|------|-------|
|
||||
| Name | Identifikation |
|
||||
| E-Mail | Kommunikation |
|
||||
| AGB | Rechtlich erforderlich |
|
||||
| Datenschutz | DSGVO |
|
||||
|
||||
### Bedingt Pflicht
|
||||
|
||||
| Feld | Bedingung |
|
||||
|------|-----------|
|
||||
| Widerrufsverzicht | Nur bei Video-Kursen (Produktart E/F) |
|
||||
| Pferdename | Nur wenn "Eigenes Pferd = Ja" |
|
||||
| Gastbox-Tage | Nur wenn "Gastbox benoetigt = Ja" |
|
||||
|
||||
### Optional
|
||||
|
||||
Alle anderen Felder koennen als Pflichtfeld markiert werden oder nicht.
|
||||
|
||||
---
|
||||
|
||||
## Produktarten-Filter
|
||||
|
||||
Felder koennen auf bestimmte Produktarten beschraenkt werden:
|
||||
|
||||
| Produktart | Code | Typische Zusatzfelder |
|
||||
|------------|------|----------------------|
|
||||
| Praesenz-Kurs | A | Reiterfahrung, Pferd, Gastbox |
|
||||
| Workshop | B | Vorkenntnisse |
|
||||
| Webinar | C | Technische Anforderungen |
|
||||
| Gruppencoaching | D | Coaching-Ziele |
|
||||
| Einzelcoaching | D1 | Terminwuensche, Themen |
|
||||
| Video-Kurs | E | Widerrufsverzicht |
|
||||
| Video-Paket | F | Widerrufsverzicht |
|
||||
|
||||
---
|
||||
|
||||
## Bedingte Felder
|
||||
|
||||
Felder koennen basierend auf anderen Feldern ein-/ausgeblendet werden.
|
||||
|
||||
### Syntax
|
||||
|
||||
```
|
||||
feldname:wert
|
||||
```
|
||||
|
||||
### Beispiele
|
||||
|
||||
| Bedingung | Bedeutung |
|
||||
|-----------|-----------|
|
||||
| `eigenes_pferd:ja` | Zeige wenn "Eigenes Pferd" = Ja |
|
||||
| `gastbox:1` | Zeige wenn Gastbox-Checkbox aktiviert |
|
||||
| `land:AT` | Zeige wenn Land = Oesterreich |
|
||||
|
||||
### Anwendungsbeispiel
|
||||
|
||||
```
|
||||
Feld: "Anzahl Gastbox-Tage"
|
||||
Typ: number
|
||||
Bedingung: gastbox_benoetigt:1
|
||||
```
|
||||
|
||||
Das Feld erscheint nur, wenn der Kunde "Gastbox benoetigt" angekreuzt hat.
|
||||
|
||||
---
|
||||
|
||||
## Kurs-spezifische Felder (Override)
|
||||
|
||||
Zusaetzlich zu den globalen Feldern kann jeder Kurs eigene Felder haben:
|
||||
|
||||
### Im Kurs-Editor
|
||||
|
||||
1. Bearbeiten Sie einen Kurs
|
||||
2. Scrollen Sie zu "Buchungsfelder"
|
||||
3. Toggle: **Globale Felder verwenden** (Ja/Nein)
|
||||
4. Bei "Nein": Eigene Felder per Drag & Drop hinzufuegen
|
||||
|
||||
### Anwendungsfaelle
|
||||
|
||||
- Spezielle Fragen nur fuer einen Kurs
|
||||
- Abfrage von Vorkenntnissen
|
||||
- Allergien/Unvertraeglichkeiten bei Verpflegung
|
||||
- Sonderwuensche
|
||||
|
||||
---
|
||||
|
||||
## Feld-Reihenfolge
|
||||
|
||||
Die Felder erscheinen im Buchungsformular in dieser Reihenfolge:
|
||||
|
||||
1. **Kontaktdaten** (Name, E-Mail, Telefon)
|
||||
2. **Adressdaten** (wenn aktiviert)
|
||||
3. **Kurs-spezifische Felder**
|
||||
4. **Benutzerdefinierte Felder**
|
||||
5. **Anmerkungen** (wenn aktiviert)
|
||||
6. **Rechtliche Felder** (AGB, Datenschutz, Newsletter)
|
||||
|
||||
> **Tipp:** Die Reihenfolge kann per Drag & Drop in den Einstellungen angepasst werden.
|
||||
|
||||
---
|
||||
|
||||
## Validierung
|
||||
|
||||
### Automatische Validierung
|
||||
|
||||
| Feldtyp | Pruefung |
|
||||
|---------|----------|
|
||||
| E-Mail | Gueltiges E-Mail-Format |
|
||||
| Telefon | Mindestens 6 Ziffern |
|
||||
| PLZ | 4-5 Ziffern (AT/DE) |
|
||||
| Datum | Gueltiges Datumsformat |
|
||||
| Zahl | Nur Zahlen, Min/Max |
|
||||
|
||||
### Fehlermeldungen
|
||||
|
||||
Fehlermeldungen erscheinen direkt am Feld in Rot. Der Kunde muss alle Fehler beheben, bevor er fortfahren kann.
|
||||
|
||||
---
|
||||
|
||||
## Siehe auch
|
||||
|
||||
- [Buchungsfelder verwalten](/topic/fields)
|
||||
- [Feldtypen im Detail](/topic/feldtypen)
|
||||
- [Produktarten-Filter](/topic/produktarten-filter)
|
||||
- [Erweiterte Optionen](/topic/erweiterte-optionen)
|
||||
30
content/cancellation.md
Normal file
30
content/cancellation.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id: cancellation
|
||||
title: Stornierung
|
||||
icon: x-circle
|
||||
description: Stornierungsfristen und -gebuehren
|
||||
section: Rechtliches
|
||||
tags: [Stornierung, Fristen, Gebuehren]
|
||||
related: [legal, booking]
|
||||
order: 21
|
||||
---
|
||||
|
||||
# Stornierung
|
||||
|
||||
## Stornierungsregeln
|
||||
|
||||
Definieren Sie gestaffelte Stornierungsgebuehren:
|
||||
|
||||
| Zeitraum vor Kursbeginn | Gebuehr | Beispiel |
|
||||
|-------------------------|---------|----------|
|
||||
| Mehr als 14 Tage | 0% | Kostenlose Stornierung |
|
||||
| 7-14 Tage | 50% | Halber Preis |
|
||||
| Weniger als 7 Tage | 100% | Voller Preis |
|
||||
|
||||
## Einstellungen
|
||||
|
||||
| Option | Beschreibung |
|
||||
|--------|--------------|
|
||||
| **Stornierung erlauben** | Kunden koennen selbst stornieren |
|
||||
| **Frist (Tage)** | Bis wann ist kostenlose Stornierung moeglich? |
|
||||
| **Staffelung** | Prozentuale Gebuehren je nach Zeitpunkt |
|
||||
46
content/dienstleistungen.md
Normal file
46
content/dienstleistungen.md
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
id: dienstleistungen
|
||||
title: Dienstleistungen
|
||||
icon: grid-3x3
|
||||
description: Produktarten und Kategorien verwalten
|
||||
section: Erweitert
|
||||
tags: [Dienstleistungen, Produktarten, Kategorien]
|
||||
related: [produktarten-filter, modules]
|
||||
order: 50
|
||||
---
|
||||
|
||||
# Dienstleistungen
|
||||
|
||||
Verwalten Sie Ihre Produktarten und Dienstleistungskategorien.
|
||||
|
||||
## Produktarten
|
||||
|
||||
| Typ | Beschreibung |
|
||||
|-----|--------------|
|
||||
| **A - Praesenz-Kurs** | Kurs vor Ort mit festen Terminen |
|
||||
| **B - Workshop** | Flexibles Format (Vor Ort/Online/Hybrid) |
|
||||
| **C - Webinar** | Online per Zoom mit Video-Aufzeichnung |
|
||||
| **D - Gruppencoaching** | Online-Gruppen mit festem Termin |
|
||||
| **D1 - Einzelcoaching** | Individuelle Termine, Kontaktanfrage |
|
||||
| **E - Video-Kurs** | Nur Video-on-Demand |
|
||||
|
||||
## Matrix-Uebersicht
|
||||
|
||||
| Merkmal | A | B | C | D | D1 | E |
|
||||
|---------|---|---|---|---|----|----|
|
||||
| Praesenz | ✓ | ✓ | - | - | - | - |
|
||||
| Online | - | ✓ | ✓ | ✓ | ✓ | - |
|
||||
| Video | - | Opt. | ✓ | - | - | ✓ |
|
||||
| Fester Termin | ✓ | ✓ | ✓ | ✓ | - | - |
|
||||
| Direktbuchung | ✓ | ✓ | ✓ | ✓ | - | ✓ |
|
||||
|
||||
## Eigene Dienstleistungen
|
||||
|
||||
Erstellen Sie eigene Dienstleistungstypen:
|
||||
|
||||
1. Neuen Eintrag hinzufuegen
|
||||
2. Name und Beschreibung eingeben
|
||||
3. Relevante Buchungsfelder zuweisen
|
||||
4. Speichern
|
||||
|
||||
> **Tipp:** Weniger ist mehr - halten Sie die Anzahl der Produktarten uebersichtlich.
|
||||
144
content/e-rechnung.md
Normal file
144
content/e-rechnung.md
Normal file
@@ -0,0 +1,144 @@
|
||||
---
|
||||
id: e-rechnung
|
||||
title: E-Rechnung (ZUGFeRD/XRechnung)
|
||||
icon: file-invoice
|
||||
description: Elektronische Rechnungen im ZUGFeRD-Format fuer sevDesk
|
||||
section: Integrationen
|
||||
tags: [E-Rechnung, ZUGFeRD, XRechnung, sevDesk, B2B, Pflicht 2025]
|
||||
related: [sevdesk, prices, booking]
|
||||
order: 41
|
||||
---
|
||||
|
||||
# E-Rechnung (ZUGFeRD/XRechnung)
|
||||
|
||||
Ab 2025 sind E-Rechnungen fuer B2B-Geschaefte in Deutschland Pflicht. Das Kurs-Booking Plugin unterstuetzt E-Rechnungen im ZUGFeRD-Format ueber die sevDesk-Integration.
|
||||
|
||||
## Was ist eine E-Rechnung?
|
||||
|
||||
Eine E-Rechnung ist eine Rechnung in einem strukturierten elektronischen Format. Im Gegensatz zu einer PDF-Rechnung enthaelt sie maschinenlesbare Daten.
|
||||
|
||||
| Format | Beschreibung |
|
||||
|--------|--------------|
|
||||
| **ZUGFeRD 2.1** | PDF mit eingebettetem XML (Hybrid-Format) |
|
||||
| **XRechnung** | Reines XML-Format (fuer oeffentliche Auftraggeber) |
|
||||
|
||||
> **sevDesk** erstellt Rechnungen im **ZUGFeRD 2.1** Format - das PDF sieht normal aus, enthaelt aber eingebettetes XML fuer automatische Verarbeitung.
|
||||
|
||||
## E-Rechnung aktivieren
|
||||
|
||||
### Schritt 1: Voraussetzungen in sevDesk pruefen
|
||||
|
||||
Bevor Sie E-Rechnungen aktivieren, muessen folgende Daten in sevDesk hinterlegt sein:
|
||||
|
||||
| Pflichtfeld | Wo in sevDesk |
|
||||
|-------------|---------------|
|
||||
| **Firmenadresse** | Einstellungen > Unternehmensdaten > Adresse |
|
||||
| **IBAN & Bank** | Einstellungen > Unternehmensdaten > Bankverbindung |
|
||||
| **Steuernummer** | Einstellungen > Unternehmensdaten > Steuernummer |
|
||||
| **USt-IdNr.** | Einstellungen > Unternehmensdaten > USt-IdNr. (optional, fuer EU) |
|
||||
|
||||
### Schritt 2: Im Plugin aktivieren
|
||||
|
||||
1. Gehen Sie zu **Kurse > Einstellungen > sevDesk**
|
||||
2. Aktivieren Sie **"E-Rechnungen im ZUGFeRD-Format erstellen"**
|
||||
3. Speichern Sie die Einstellungen
|
||||
|
||||
## So funktioniert es
|
||||
|
||||
```
|
||||
Buchung bestaetigt
|
||||
|
|
||||
v
|
||||
+------------------+
|
||||
| sevDesk API |
|
||||
| mit |
|
||||
| propertyIs |
|
||||
| EInvoice: true |
|
||||
+------------------+
|
||||
|
|
||||
v
|
||||
+------------------+
|
||||
| ZUGFeRD PDF |
|
||||
| (PDF + XML) |
|
||||
+------------------+
|
||||
|
|
||||
+-----> PDF fuer Menschen (normale Ansicht)
|
||||
|
|
||||
+-----> XML fuer Maschinen (eingebettet)
|
||||
```
|
||||
|
||||
### Was der Kunde erhaelt
|
||||
|
||||
- **Normale PDF-Rechnung** - Sieht aus wie jede andere Rechnung
|
||||
- **Eingebettetes XML** - Unsichtbar, aber maschinenlesbar
|
||||
- **Automatische Verarbeitung** - Buchhaltungssoftware kann XML lesen
|
||||
|
||||
## Technische Details
|
||||
|
||||
### Gespeicherte Meta-Daten
|
||||
|
||||
Bei jeder E-Rechnung wird gespeichert:
|
||||
|
||||
| Meta-Feld | Beschreibung |
|
||||
|-----------|--------------|
|
||||
| `_sevdesk_invoice_id` | sevDesk Rechnungs-ID |
|
||||
| `_sevdesk_invoice_number` | Rechnungsnummer |
|
||||
| `_sevdesk_is_e_invoice` | "1" wenn E-Rechnung |
|
||||
|
||||
### XML abrufen (fuer Entwickler)
|
||||
|
||||
```php
|
||||
// XML einer E-Rechnung abrufen
|
||||
$xml = Kurs_Booking_SevDesk::get_invoice_xml( $invoice_id );
|
||||
|
||||
if ( is_wp_error( $xml ) ) {
|
||||
echo $xml->get_error_message();
|
||||
} else {
|
||||
// XML verarbeiten
|
||||
echo $xml;
|
||||
}
|
||||
|
||||
// Pruefen ob Rechnung eine E-Rechnung ist
|
||||
$is_e_invoice = Kurs_Booking_SevDesk::is_e_invoice( $invoice_id );
|
||||
```
|
||||
|
||||
## Haeufige Fragen
|
||||
|
||||
### Muss ich E-Rechnungen nutzen?
|
||||
|
||||
- **B2B (Geschaeftskunden):** Ab 2025 Pflicht in Deutschland
|
||||
- **B2C (Privatkunden):** Nicht verpflichtend, aber moeglich
|
||||
- **B2G (Oeffentliche Hand):** Bereits jetzt Pflicht (XRechnung)
|
||||
|
||||
### Was passiert mit alten Rechnungen?
|
||||
|
||||
Bestehende Rechnungen bleiben unveraendert. Die E-Rechnung-Option gilt nur fuer **neue** Rechnungen nach der Aktivierung.
|
||||
|
||||
### Kann der Kunde die E-Rechnung lesen?
|
||||
|
||||
Ja! Das PDF sieht ganz normal aus. Das XML ist nur fuer die automatische Verarbeitung eingebettet.
|
||||
|
||||
### Was wenn sevDesk einen Fehler meldet?
|
||||
|
||||
Pruefen Sie:
|
||||
1. Sind alle Pflichtfelder in sevDesk ausgefuellt?
|
||||
2. Ist die Firmenadresse vollstaendig (Strasse, PLZ, Ort)?
|
||||
3. Ist eine gueltige IBAN hinterlegt?
|
||||
4. Ist Steuernummer oder USt-IdNr. eingetragen?
|
||||
|
||||
## Rechnungsstatus
|
||||
|
||||
| Status | Beschreibung |
|
||||
|--------|--------------|
|
||||
| **Entwurf (100)** | Rechnung zur manuellen Pruefung, noch keine Rechnungsnummer |
|
||||
| **Offen (200)** | Sofort finalisiert mit Rechnungsnummer |
|
||||
|
||||
> **Tipp:** Bei aktivierter E-Rechnung empfehlen wir Status "Entwurf", um Rechnungen vor dem Versand zu pruefen.
|
||||
|
||||
## Rechtliche Hinweise
|
||||
|
||||
- E-Rechnungen muessen 10 Jahre aufbewahrt werden
|
||||
- Das XML muss unveraendert bleiben
|
||||
- sevDesk kuemmert sich um die korrekte Archivierung
|
||||
|
||||
> **Hinweis:** Diese Dokumentation ersetzt keine steuerliche Beratung. Bei Fragen wenden Sie sich an Ihren Steuerberater.
|
||||
59
content/email-platzhalter.md
Normal file
59
content/email-platzhalter.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
id: email-platzhalter
|
||||
title: E-Mail Platzhalter
|
||||
icon: code-square
|
||||
description: Dynamische Inhalte in E-Mails
|
||||
section: E-Mail System
|
||||
tags: [E-Mail, Platzhalter, Variablen]
|
||||
related: [email_templates, emails]
|
||||
order: 32
|
||||
---
|
||||
|
||||
# E-Mail Platzhalter
|
||||
|
||||
## Teilnehmer-Daten
|
||||
|
||||
| Platzhalter | Beschreibung |
|
||||
|-------------|--------------|
|
||||
| `{vorname}` | Vorname des Teilnehmers |
|
||||
| `{nachname}` | Nachname des Teilnehmers |
|
||||
| `{email}` | E-Mail-Adresse |
|
||||
| `{telefon}` | Telefonnummer |
|
||||
| `{adresse}` | Vollstaendige Adresse |
|
||||
|
||||
## Kurs-Daten
|
||||
|
||||
| Platzhalter | Beschreibung |
|
||||
|-------------|--------------|
|
||||
| `{kurs_titel}` | Titel des Kurses |
|
||||
| `{kurs_datum}` | Datum des Kurses |
|
||||
| `{kurs_uhrzeit}` | Uhrzeit (Beginn - Ende) |
|
||||
| `{kurs_ort}` | Veranstaltungsort |
|
||||
| `{dozent}` | Name des Dozenten |
|
||||
|
||||
## Buchungs-Daten
|
||||
|
||||
| Platzhalter | Beschreibung |
|
||||
|-------------|--------------|
|
||||
| `{buchungs_nr}` | Eindeutige Buchungsnummer |
|
||||
| `{buchungs_datum}` | Datum der Buchung |
|
||||
| `{preis}` | Gesamtpreis |
|
||||
| `{anzahlung}` | Anzahlungsbetrag |
|
||||
| `{restbetrag}` | Noch zu zahlender Betrag |
|
||||
|
||||
## Firmen-Daten
|
||||
|
||||
| Platzhalter | Beschreibung |
|
||||
|-------------|--------------|
|
||||
| `{firma}` | Firmenname |
|
||||
| `{website}` | Website-URL |
|
||||
| `{kontakt_email}` | Kontakt-E-Mail |
|
||||
| `{bankverbindung}` | IBAN und BIC |
|
||||
|
||||
## Video-Zugang (Produktart C/D)
|
||||
|
||||
| Platzhalter | Beschreibung |
|
||||
|-------------|--------------|
|
||||
| `{video_link}` | Link zum Video-Portal |
|
||||
| `{video_passwort}` | Zugangspasswort |
|
||||
| `{video_gueltig_bis}` | Ablaufdatum des Zugangs |
|
||||
338
content/email_templates.md
Normal file
338
content/email_templates.md
Normal file
@@ -0,0 +1,338 @@
|
||||
---
|
||||
id: email_templates
|
||||
title: E-Mail Vorlagen
|
||||
icon: file-earmark-text
|
||||
description: Vorlagen fuer automatische E-Mails gestalten und anpassen
|
||||
section: E-Mail System
|
||||
tags: [E-Mail, Vorlagen, Templates, Styling, Design]
|
||||
related: [emails, email-platzhalter, booking, general]
|
||||
order: 31
|
||||
---
|
||||
|
||||
# E-Mail Vorlagen
|
||||
|
||||
## Verfuegbare Vorlagen
|
||||
|
||||
| Vorlage | Wird gesendet bei |
|
||||
|---------|-------------------|
|
||||
| **Buchungsbestaetigung** | Neue Buchung eingegangen |
|
||||
| **Zahlungsbestaetigung** | Zahlung erhalten |
|
||||
| **Stornierungsbestaetigung** | Buchung storniert |
|
||||
| **Erinnerung** | X Tage vor Kursbeginn |
|
||||
| **Video-Zugang** | Nach Freischaltung (Produktart C/D) |
|
||||
| **Admin-Benachrichtigung** | Neue Buchung (an Admin) |
|
||||
|
||||
---
|
||||
|
||||
## Automatischer Header & Footer
|
||||
|
||||
Alle E-Mails erhalten automatisch einen professionellen Header und Footer.
|
||||
|
||||
### Header (automatisch)
|
||||
|
||||
Der Header zeigt Ihr **Firmenlogo** zentriert an. Das Logo wird in dieser Reihenfolge gesucht:
|
||||
|
||||
1. **E-Mail Logo** - Einstellungen > Allgemein > E-Mail Logo
|
||||
2. **Firmenlogo** - Einstellungen > Allgemein > Firmenlogo
|
||||
3. **WordPress Logo** - Theme-Customizer > Website-Identitaet > Logo
|
||||
|
||||
> **Tipp:** Fuer beste Ergebnisse nutzen Sie ein Logo mit transparentem Hintergrund (PNG) in mindestens 400px Breite.
|
||||
|
||||
### Footer (automatisch)
|
||||
|
||||
Der Footer enthaelt automatisch:
|
||||
|
||||
- Firmenname
|
||||
- E-Mail-Adresse
|
||||
- Telefonnummer (falls hinterlegt)
|
||||
- Copyright mit aktuellem Jahr
|
||||
|
||||
Diese Daten werden aus **Einstellungen > Allgemein** bezogen.
|
||||
|
||||
---
|
||||
|
||||
## E-Mail Struktur
|
||||
|
||||
Jede E-Mail hat folgenden Aufbau:
|
||||
|
||||
```
|
||||
+----------------------------------+
|
||||
| [LOGO] | <- Automatischer Header
|
||||
+----------------------------------+
|
||||
| |
|
||||
| Ihr E-Mail-Inhalt | <- Vorlage-Editor
|
||||
| (frei gestaltbar) |
|
||||
| |
|
||||
+----------------------------------+
|
||||
| Firmenname | kontakt@email.de | <- Automatischer Footer
|
||||
| (c) 2025 |
|
||||
+----------------------------------+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTML-Styling im Editor
|
||||
|
||||
### Erlaubte HTML-Elemente
|
||||
|
||||
Sie koennen folgende HTML-Tags im Vorlagen-Editor verwenden:
|
||||
|
||||
| Kategorie | Tags |
|
||||
|-----------|------|
|
||||
| **Text** | `<p>`, `<br>`, `<strong>`, `<b>`, `<em>`, `<i>`, `<u>`, `<s>` |
|
||||
| **Ueberschriften** | `<h1>`, `<h2>`, `<h3>`, `<h4>` |
|
||||
| **Listen** | `<ul>`, `<ol>`, `<li>` |
|
||||
| **Links** | `<a href="...">` |
|
||||
| **Bilder** | `<img src="..." alt="...">` |
|
||||
| **Tabellen** | `<table>`, `<tr>`, `<td>`, `<th>`, `<thead>`, `<tbody>` |
|
||||
| **Container** | `<div>`, `<span>` |
|
||||
| **Trennlinien** | `<hr>` |
|
||||
|
||||
### Inline-Styles verwenden
|
||||
|
||||
E-Mail-Clients unterstuetzen **keine externen CSS-Dateien**. Nutzen Sie daher immer Inline-Styles:
|
||||
|
||||
```html
|
||||
<!-- RICHTIG: Inline-Style -->
|
||||
<p style="color: #333; font-size: 16px;">
|
||||
Ihr Text hier
|
||||
</p>
|
||||
|
||||
<!-- FALSCH: CSS-Klasse (wird ignoriert) -->
|
||||
<p class="text-gray">
|
||||
Ihr Text hier
|
||||
</p>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Styling-Beispiele
|
||||
|
||||
### Hervorgehobene Box
|
||||
|
||||
```html
|
||||
<div style="background-color: #f0f7ff; border-left: 4px solid #0066cc; padding: 15px; margin: 20px 0;">
|
||||
<strong>Wichtig:</strong> Bitte bringen Sie bequeme Kleidung mit.
|
||||
</div>
|
||||
```
|
||||
|
||||
**Ergebnis:**
|
||||
> Blaue Info-Box mit linkem Rand
|
||||
|
||||
### Tabelle fuer Buchungsdetails
|
||||
|
||||
```html
|
||||
<table style="width: 100%; border-collapse: collapse; margin: 20px 0;">
|
||||
<tr style="background-color: #f5f5f5;">
|
||||
<td style="padding: 10px; border: 1px solid #ddd;"><strong>Kurs</strong></td>
|
||||
<td style="padding: 10px; border: 1px solid #ddd;">{kurs_titel}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px; border: 1px solid #ddd;"><strong>Datum</strong></td>
|
||||
<td style="padding: 10px; border: 1px solid #ddd;">{kurs_datum}</td>
|
||||
</tr>
|
||||
<tr style="background-color: #f5f5f5;">
|
||||
<td style="padding: 10px; border: 1px solid #ddd;"><strong>Preis</strong></td>
|
||||
<td style="padding: 10px; border: 1px solid #ddd;">{gesamtpreis}</td>
|
||||
</tr>
|
||||
</table>
|
||||
```
|
||||
|
||||
### Button-Link
|
||||
|
||||
```html
|
||||
<p style="text-align: center; margin: 30px 0;">
|
||||
<a href="{bestaetigung_link}"
|
||||
style="background-color: #0066cc; color: #ffffff; padding: 12px 30px;
|
||||
text-decoration: none; border-radius: 5px; display: inline-block;">
|
||||
Buchung bestaetigen
|
||||
</a>
|
||||
</p>
|
||||
```
|
||||
|
||||
### Farbige Ueberschrift
|
||||
|
||||
```html
|
||||
<h2 style="color: #0066cc; border-bottom: 2px solid #0066cc; padding-bottom: 10px;">
|
||||
Ihre Buchungsbestaetigung
|
||||
</h2>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Allgemeine Regeln
|
||||
|
||||
| Regel | Empfehlung |
|
||||
|-------|------------|
|
||||
| **Breite** | Max. 600px (wird automatisch gesetzt) |
|
||||
| **Schriftgroesse** | Min. 14px fuer Fliesstext |
|
||||
| **Zeilenhoehe** | 1.5 bis 1.6 fuer bessere Lesbarkeit |
|
||||
| **Farben** | Ausreichend Kontrast (dunkel auf hell) |
|
||||
| **Bilder** | Immer `alt`-Text angeben |
|
||||
|
||||
### Empfohlene Farbpalette
|
||||
|
||||
| Verwendung | Farbe | Code |
|
||||
|------------|-------|------|
|
||||
| **Primaer** | Blau | `#0066cc` |
|
||||
| **Erfolg** | Gruen | `#28a745` |
|
||||
| **Warnung** | Gelb | `#ffc107` |
|
||||
| **Fehler** | Rot | `#dc3545` |
|
||||
| **Text** | Dunkelgrau | `#333333` |
|
||||
| **Hintergrund** | Hellgrau | `#f5f5f5` |
|
||||
|
||||
### Schriftarten
|
||||
|
||||
Nutzen Sie **websichere Schriftarten**, da individuelle Fonts nicht geladen werden:
|
||||
|
||||
```html
|
||||
<p style="font-family: Arial, Helvetica, sans-serif;">
|
||||
Ihr Text
|
||||
</p>
|
||||
```
|
||||
|
||||
**Empfohlene Fonts:**
|
||||
- Arial, Helvetica, sans-serif
|
||||
- Georgia, Times New Roman, serif
|
||||
- Verdana, Geneva, sans-serif
|
||||
|
||||
---
|
||||
|
||||
## Komplettes Beispiel-Template
|
||||
|
||||
```html
|
||||
<h2 style="color: #0066cc; margin-bottom: 20px;">
|
||||
Hallo {vorname},
|
||||
</h2>
|
||||
|
||||
<p style="font-size: 16px; line-height: 1.6; color: #333;">
|
||||
vielen Dank fuer Ihre Buchung! Hier sind Ihre Details:
|
||||
</p>
|
||||
|
||||
<table style="width: 100%; border-collapse: collapse; margin: 25px 0;">
|
||||
<tr style="background-color: #0066cc; color: #ffffff;">
|
||||
<th style="padding: 12px; text-align: left;" colspan="2">
|
||||
Buchungsdetails
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee; width: 40%;">
|
||||
<strong>Kurs:</strong>
|
||||
</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">
|
||||
{kurs_titel}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">
|
||||
<strong>Datum:</strong>
|
||||
</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">
|
||||
{kurs_datum}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">
|
||||
<strong>Uhrzeit:</strong>
|
||||
</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">
|
||||
{kurs_zeit}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">
|
||||
<strong>Ort:</strong>
|
||||
</td>
|
||||
<td style="padding: 10px; border-bottom: 1px solid #eee;">
|
||||
{kurs_ort}
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="background-color: #f9f9f9;">
|
||||
<td style="padding: 10px;">
|
||||
<strong>Gesamtpreis:</strong>
|
||||
</td>
|
||||
<td style="padding: 10px; font-size: 18px; color: #0066cc;">
|
||||
<strong>{gesamtpreis}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div style="background-color: #fff3cd; border: 1px solid #ffc107;
|
||||
padding: 15px; border-radius: 5px; margin: 20px 0;">
|
||||
<strong>Hinweis:</strong> Bitte bestaetigen Sie Ihre Buchung
|
||||
innerhalb von 48 Stunden.
|
||||
</div>
|
||||
|
||||
<p style="text-align: center; margin: 30px 0;">
|
||||
<a href="{bestaetigung_link}"
|
||||
style="background-color: #28a745; color: #ffffff;
|
||||
padding: 15px 40px; text-decoration: none;
|
||||
border-radius: 5px; font-size: 16px; display: inline-block;">
|
||||
Jetzt bestaetigen
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p style="font-size: 14px; color: #666; margin-top: 30px;">
|
||||
Bei Fragen stehen wir Ihnen gerne zur Verfuegung.
|
||||
</p>
|
||||
|
||||
<p style="font-size: 16px; color: #333;">
|
||||
Mit freundlichen Gruessen<br>
|
||||
<strong>{firma}</strong>
|
||||
</p>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Haeufige Probleme
|
||||
|
||||
### Styles werden nicht angezeigt
|
||||
|
||||
**Problem:** Formatierungen erscheinen nicht in der E-Mail.
|
||||
|
||||
**Loesung:** Pruefen Sie, ob Sie Inline-Styles verwenden. CSS-Klassen funktionieren nicht.
|
||||
|
||||
### Bilder werden nicht geladen
|
||||
|
||||
**Problem:** Eingefuegte Bilder erscheinen nicht.
|
||||
|
||||
**Loesungen:**
|
||||
1. Verwenden Sie **vollstaendige URLs** (https://...)
|
||||
2. Bilder muessen oeffentlich zugaenglich sein
|
||||
3. Fuegen Sie immer einen `alt`-Text hinzu
|
||||
|
||||
### Layout ist verschoben
|
||||
|
||||
**Problem:** Spalten/Tabellen sehen falsch aus.
|
||||
|
||||
**Loesung:** Nutzen Sie feste Pixelwerte statt Prozent und testen Sie in verschiedenen E-Mail-Clients.
|
||||
|
||||
---
|
||||
|
||||
## Platzhalter verwenden
|
||||
|
||||
In allen Vorlagen koennen Sie Platzhalter nutzen:
|
||||
|
||||
```
|
||||
Hallo {vorname} {nachname},
|
||||
|
||||
Ihre Buchung fuer "{kurs_titel}" am {kurs_datum} wurde bestaetigt.
|
||||
|
||||
Mit freundlichen Gruessen
|
||||
{firma}
|
||||
```
|
||||
|
||||
> **Siehe auch:** [E-Mail Platzhalter](/topic/email-platzhalter) fuer eine vollstaendige Liste aller verfuegbaren Platzhalter.
|
||||
|
||||
---
|
||||
|
||||
## Testen
|
||||
|
||||
Testen Sie Ihre Vorlagen vor dem Live-Einsatz:
|
||||
|
||||
1. Erstellen Sie eine **Test-Buchung** mit Ihrer eigenen E-Mail
|
||||
2. Pruefen Sie die E-Mail in verschiedenen Clients (Gmail, Outlook, Apple Mail)
|
||||
3. Testen Sie auch die **mobile Ansicht**
|
||||
|
||||
40
content/emails.md
Normal file
40
content/emails.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
id: emails
|
||||
title: E-Mail Einstellungen
|
||||
icon: envelope
|
||||
description: Absender, Format und Versand
|
||||
section: E-Mail System
|
||||
tags: [E-Mail, Absender, SMTP]
|
||||
related: [email_templates, email-platzhalter]
|
||||
order: 30
|
||||
---
|
||||
|
||||
# E-Mail Einstellungen
|
||||
|
||||
## Absender-Daten
|
||||
|
||||
| Feld | Beschreibung |
|
||||
|------|--------------|
|
||||
| **Absender-Name** | Name der in E-Mails erscheint |
|
||||
| **Absender-E-Mail** | Reply-To Adresse |
|
||||
| **BCC-Adresse** | Erhaelt Kopie aller Buchungs-E-Mails |
|
||||
|
||||
## E-Mail Format
|
||||
|
||||
| Option | Beschreibung |
|
||||
|--------|--------------|
|
||||
| **HTML-E-Mails** | Formatierte E-Mails mit Design |
|
||||
| **Plain Text** | Nur Text ohne Formatierung |
|
||||
|
||||
## SMTP-Konfiguration (optional)
|
||||
|
||||
Fuer zuverlaessigen Versand koennen Sie einen SMTP-Server konfigurieren:
|
||||
|
||||
| Einstellung | Beispiel |
|
||||
|-------------|----------|
|
||||
| **SMTP-Server** | smtp.example.com |
|
||||
| **Port** | 587 |
|
||||
| **Benutzername** | mail@example.com |
|
||||
| **Passwort** | *** |
|
||||
|
||||
> **Tipp:** Ohne SMTP wird die WordPress-Standard-Funktion `wp_mail()` verwendet.
|
||||
241
content/entwicklung/next-session.md
Normal file
241
content/entwicklung/next-session.md
Normal file
@@ -0,0 +1,241 @@
|
||||
---
|
||||
id: next-session
|
||||
title: Naechste Session
|
||||
icon: calendar-check
|
||||
description: Aktuelle Aufgaben und Session-Zusammenfassung
|
||||
section: Entwicklung
|
||||
tags: [Session, Aufgaben, Aktuell, Todo]
|
||||
related: [entwicklung/sprint-uebersicht, features]
|
||||
order: 92
|
||||
---
|
||||
|
||||
# Naechste Session
|
||||
|
||||
Quick-Start Guide fuer die naechste Entwicklungs-Session.
|
||||
|
||||
---
|
||||
|
||||
## Aktueller Stand
|
||||
|
||||
**Letztes Update:** 16. Dezember 2025 (Nacht)
|
||||
|
||||
**Letzte Session:** Sprint 11 - Custom Database Tables
|
||||
|
||||
**Status:** Kurs-Booking 100% | PRODUCTION READY
|
||||
|
||||
---
|
||||
|
||||
## DEPLOYMENT MORGEN (17.12.2025)
|
||||
|
||||
### Status: BEREIT
|
||||
|
||||
| Komponente | Status |
|
||||
|------------|--------|
|
||||
| Kurs-Booking auf 8200 | ✅ Installiert |
|
||||
| Redis Object Cache | ✅ Aktiv |
|
||||
| Nginx FastCGI Cache | ✅ Aktiv |
|
||||
| Coolify Config | ✅ Erstellt |
|
||||
| Deployment-Checkliste | ✅ Fertig |
|
||||
|
||||
### Deployment-Dateien
|
||||
|
||||
```
|
||||
coolify/
|
||||
├── docker-compose.yml
|
||||
├── nginx/nginx.conf
|
||||
├── nginx/default.conf
|
||||
├── docker-entrypoint-permissions.sh
|
||||
└── DEPLOYMENT-CHECKLIST.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Session 16.12.2025 (Nacht) - Sprint 11
|
||||
|
||||
### Erledigte Aufgaben
|
||||
|
||||
| Aufgabe | Status | Details |
|
||||
|---------|--------|---------|
|
||||
| Custom Database Tables | ✅ | Performance-Optimierung |
|
||||
| class-database.php | ✅ | Schema, Migration, Installer |
|
||||
| class-buchung-repository.php | ✅ | Optimierte Query-Methoden |
|
||||
| Sync-Hooks | ✅ | Auto-Sync bei CRUD |
|
||||
| Admin-UI | ✅ | Fortschrittsbalken, Migration-Button |
|
||||
| AJAX Migration | ✅ | Batch-Migration via Button |
|
||||
| Sync 8200 | ✅ | Alles synchronisiert |
|
||||
|
||||
### Neue Dateien
|
||||
|
||||
- `includes/class-database.php` - Schema + Migration
|
||||
- `includes/class-buchung-repository.php` - Repository-Pattern
|
||||
|
||||
### Custom Table Schema
|
||||
|
||||
```sql
|
||||
wp_kurs_buchungen (
|
||||
id, post_id, kurs_id, variante_id, status,
|
||||
name, email, phone, anzahl, total_price,
|
||||
deposit_amount, payment_status, sevdesk_*,
|
||||
confirm_token, created_at, confirmed_at, cancelled_at
|
||||
)
|
||||
-- Indizes: post_id, kurs_id, status, email, created_at
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Session 16.12.2025 (Spaet) - Sprint 10
|
||||
|
||||
### Erledigte Aufgaben
|
||||
|
||||
| Aufgabe | Status | Details |
|
||||
|---------|--------|---------|
|
||||
| Kontext-Hilfe-System | ✅ | Info-Icons mit Tooltips |
|
||||
| Helper-Methoden | ✅ | `render_help_icon()` + `help_icon()` |
|
||||
| Tooltip CSS | ✅ | 4 Positionen, Responsive, Animiert |
|
||||
| Info-Icons eingebaut | ✅ | Module, Preise, Rechtliches, Bank |
|
||||
| Help-Topic Links | ✅ | "Mehr erfahren" im Tooltip |
|
||||
| Sync 8200 | ✅ | Alles synchronisiert |
|
||||
|
||||
### Neue Funktionen
|
||||
|
||||
```php
|
||||
// Verwendung in Settings-Tabs:
|
||||
self::help_icon(
|
||||
'Erklaerungstext fuer dieses Feld',
|
||||
'help-topic-slug', // Optional: Link zu Help-Service
|
||||
'right' // Position: top, bottom, left, right
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Session 16.12.2025 (Abend) - Sprint 9
|
||||
|
||||
### Erledigte Aufgaben
|
||||
|
||||
| Aufgabe | Status | Details |
|
||||
|---------|--------|---------|
|
||||
| Pop-up Neuigkeiten | ✅ | Komplettes Marketing-Popup-System |
|
||||
| class-neuigkeit.php | ✅ | ~830 Zeilen, Post-Type + Frontend |
|
||||
| class-subscriber.php | ✅ | Fuer spaeteren Newsletter |
|
||||
| Kurs-Auswahl-Modi | ✅ | Einzeln, Kategorie, Produktart, Alle |
|
||||
| Zufalls-Selektion | ✅ | Zufaelliger Kurs aus Pool |
|
||||
| Statistiken | ✅ | Views, Klicks, Schliessungen |
|
||||
| Help-Service Doku | ✅ | popup-neuigkeiten.md + Marketing-Sektion |
|
||||
| Sync 8200 | ✅ | Alles synchronisiert |
|
||||
|
||||
### Neue Dateien
|
||||
|
||||
- `includes/class-neuigkeit.php` - Popup-Logik
|
||||
- `includes/class-subscriber.php` - Newsletter (spaeter)
|
||||
- `help-service/content/popup-neuigkeiten.md` - Dokumentation
|
||||
|
||||
---
|
||||
|
||||
## Session 16.12.2025 (Vormittag) - Production Deployment
|
||||
|
||||
### Erledigte Aufgaben
|
||||
|
||||
| Aufgabe | Status | Details |
|
||||
|---------|--------|---------|
|
||||
| Backup 8200 | ✅ | WordPress + DB gesichert |
|
||||
| Kurs-Booking Migration | ✅ | Plugin + Daten von 8300 → 8200 |
|
||||
| MEC Cleanup | ✅ | Alle MEC-Plugins geloescht |
|
||||
| Permission-Script | ✅ | Optimiert (50s → 1.4s) |
|
||||
| Redis Setup | ✅ | WP_REDIS_* konfiguriert |
|
||||
| FastCGI Cache | ✅ | nginx.conf erstellt |
|
||||
| Coolify Config | ✅ | Komplettes Deployment-Paket |
|
||||
| Portal-Fix | ✅ | JavaScript-Redirect |
|
||||
|
||||
### Caching-Stack (verifiziert)
|
||||
|
||||
| Layer | Test |
|
||||
|-------|------|
|
||||
| Redis Object Cache | `redis-cli ping` → PONG |
|
||||
| Nginx FastCGI Cache | `X-FastCGI-Cache: HIT` |
|
||||
|
||||
### Bugfixes
|
||||
|
||||
| Problem | Loesung |
|
||||
|---------|---------|
|
||||
| Duplicator Pro 500 | PHP memory_limit auf 1024M |
|
||||
| 502 Bad Gateway | Docker DNS Resolver |
|
||||
| Kundenportal-Link | JavaScript window.open() |
|
||||
| FastCGI Cache fehlt | cache_path in http-Block |
|
||||
|
||||
---
|
||||
|
||||
## Naechste Aufgaben
|
||||
|
||||
### Prioritaet 1: Coolify Deployment (17.12.2025)
|
||||
|
||||
1. [ ] Domain in .env eintragen
|
||||
2. [ ] Sichere Passwoerter generieren
|
||||
3. [ ] Datenbank exportieren + URLs ersetzen
|
||||
4. [ ] In Coolify deployen
|
||||
5. [ ] WordPress einrichten
|
||||
6. [ ] Plugins aktivieren
|
||||
7. [ ] Redis aktivieren
|
||||
8. [ ] Finale Tests
|
||||
|
||||
### Prioritaet 2: Nach Go-Live
|
||||
|
||||
- [ ] SSL/HTTPS pruefen
|
||||
- [ ] E-Mail-Versand testen
|
||||
- [ ] Buchungsformular testen
|
||||
- [ ] sevDesk Integration pruefen
|
||||
|
||||
---
|
||||
|
||||
## Test-URLs
|
||||
|
||||
| Seite | URL |
|
||||
|-------|-----|
|
||||
| **Production Ready** | http://192.168.100.93:8200 |
|
||||
| Staging (Kadence) | http://192.168.100.93:8300 |
|
||||
| Original (Backup) | http://192.168.100.93:8600 |
|
||||
| Help-Service | http://192.168.100.93:5050 |
|
||||
|
||||
---
|
||||
|
||||
## Cache-Test Befehle
|
||||
|
||||
```bash
|
||||
# FastCGI Cache Status
|
||||
curl -sI http://192.168.100.93:8200/ | grep X-FastCGI-Cache
|
||||
|
||||
# Redis Status
|
||||
docker exec mec_redis_refactoring redis-cli ping
|
||||
|
||||
# Cache leeren
|
||||
docker exec mec_nginx_refactoring rm -rf /var/cache/nginx/fastcgi/*
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Coolify Environment Variables
|
||||
|
||||
```bash
|
||||
PROJECT_NAME=kursbooking
|
||||
DOMAIN=islandpferde-melanieworbs.de
|
||||
DB_NAME=wordpress_kursbooking
|
||||
DB_USER=wordpress
|
||||
DB_PASSWORD=<SICHERES_PASSWORT>
|
||||
DB_ROOT_PASSWORD=<SICHERES_PASSWORT>
|
||||
TABLE_PREFIX=kb_
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Links
|
||||
|
||||
| Ressource | Beschreibung |
|
||||
|-----------|--------------|
|
||||
| [Sprint-Uebersicht](entwicklung/sprint-uebersicht) | Alle Sprints |
|
||||
| [Shortcodes](shortcodes) | Shortcode-Dokumentation |
|
||||
| [Buchungsfelder](buchungsfelder-uebersicht) | Feld-Konfiguration |
|
||||
|
||||
---
|
||||
|
||||
**Session beendet:** 16.12.2025
|
||||
**Naechste Session:** Production Deployment auf Coolify
|
||||
214
content/entwicklung/sprint-uebersicht.md
Normal file
214
content/entwicklung/sprint-uebersicht.md
Normal file
@@ -0,0 +1,214 @@
|
||||
---
|
||||
id: sprint-uebersicht
|
||||
title: Sprint-Uebersicht
|
||||
icon: kanban
|
||||
description: Aktueller Entwicklungsstand und Sprint-Status
|
||||
section: Entwicklung
|
||||
tags: [Sprints, Entwicklung, Status, Roadmap]
|
||||
related: [entwicklung/next-session, features]
|
||||
order: 91
|
||||
---
|
||||
|
||||
# Sprint-Uebersicht
|
||||
|
||||
Aktueller Entwicklungsstand des Kurs-Booking Plugins.
|
||||
|
||||
---
|
||||
|
||||
## Status Gesamt
|
||||
|
||||
**Fortschritt:** 95% - PRODUCTION READY
|
||||
|
||||
**Letztes Update:** 16. Dezember 2025
|
||||
|
||||
**Deployment:** 17. Dezember 2025 via Coolify
|
||||
|
||||
---
|
||||
|
||||
## Sprint-Tabelle
|
||||
|
||||
| Sprint | Thema | Status |
|
||||
|--------|-------|--------|
|
||||
| **1** | Grundgeruest | ✅ Erledigt |
|
||||
| **2** | Buchungsflow | ✅ Erledigt |
|
||||
| **2.5** | Einstellungen komplett | ✅ Erledigt |
|
||||
| **2.6** | Settings-Integration | ✅ Erledigt |
|
||||
| **3** | sevDesk Integration | ✅ Erledigt |
|
||||
| **4.1** | Video Post-Type | ✅ Erledigt |
|
||||
| **4.2** | Video-Service (Python) | ✅ Erledigt |
|
||||
| **4.3** | Kurstypen-Matrix | ✅ Erledigt |
|
||||
| **4.6** | Standalone Video-Verkauf | ✅ Erledigt |
|
||||
| **4.7** | Video-Upload Admin UI | ✅ Erledigt |
|
||||
| **4.9** | Video-Bundles | ✅ Erledigt |
|
||||
| **4.10** | Flexible Preisvarianten | ✅ Erledigt |
|
||||
| **4.11** | Kurs-spezifische Buchungsfelder | ✅ Erledigt |
|
||||
| **4.12** | Video-Streaming E2E | ✅ Erledigt |
|
||||
| **5** | Kadence Migration | 🔄 Pausiert |
|
||||
| **6** | Stornierung + E-Mail | ✅ Erledigt |
|
||||
| **6.6** | Portal-Feldsynchronisation | ✅ Erledigt |
|
||||
| **7** | E-Mail Template Editor | ✅ Erledigt |
|
||||
| **7.5** | Stammdaten + Vorlagen | ✅ Erledigt |
|
||||
| **7.6** | Admin-Optimierungen | ✅ Erledigt |
|
||||
| **7.7** | Modul-System Integration | ✅ Erledigt |
|
||||
| **7.8** | Frontend-Anzeige-Optionen | ✅ Erledigt |
|
||||
| **7.9** | Admin-Menue-Optimierung | ✅ Erledigt |
|
||||
| **8** | Dienstleistungs-Matrix | ✅ Erledigt |
|
||||
| **8.6** | Feldbaum + Shortcodes | ✅ Erledigt |
|
||||
| **PROD** | Production Deployment | ✅ Bereit |
|
||||
| **9** | Pop-up Neuigkeiten | ✅ Erledigt |
|
||||
| **10** | Kontext-Hilfe-System | ✅ Erledigt |
|
||||
| **11** | Custom Database Tables | ✅ Erledigt |
|
||||
|
||||
---
|
||||
|
||||
## Production Deployment (16.12.2025)
|
||||
|
||||
### Erledigte Aufgaben
|
||||
|
||||
| Task | Status |
|
||||
|------|--------|
|
||||
| Kurs-Booking auf Port 8200 | ✅ |
|
||||
| Redis Object Cache | ✅ |
|
||||
| Nginx FastCGI Cache | ✅ |
|
||||
| Coolify Config erstellt | ✅ |
|
||||
| MEC-Plugins entfernt | ✅ |
|
||||
| Permission-Script optimiert | ✅ |
|
||||
|
||||
### Caching-Stack
|
||||
|
||||
| Layer | Funktion |
|
||||
|-------|----------|
|
||||
| Redis Object Cache | DB-Query Caching |
|
||||
| Nginx FastCGI Cache | HTML ohne PHP |
|
||||
|
||||
### Deployment-Dateien
|
||||
|
||||
```
|
||||
coolify/
|
||||
├── docker-compose.yml
|
||||
├── nginx/nginx.conf
|
||||
├── nginx/default.conf
|
||||
├── docker-entrypoint-permissions.sh
|
||||
└── DEPLOYMENT-CHECKLIST.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Abgeschlossene Meilensteine
|
||||
|
||||
### Sprint PROD: Production Deployment (16.12.2025)
|
||||
|
||||
- Kurs-Booking von Staging → Production (Port 8200)
|
||||
- Redis Object Cache konfiguriert
|
||||
- Nginx FastCGI Cache konfiguriert
|
||||
- Coolify docker-compose.yml erstellt
|
||||
- MEC-Plugins entfernt
|
||||
- Bugfixes: 502 Gateway, Portal-Link, Duplicator Pro
|
||||
|
||||
### Sprint 8.6: Feldbaum + Shortcodes (11.12.2025)
|
||||
|
||||
- Feldbaum-Visualisierung mit Export (JSON/MD/CSV)
|
||||
- `[kurs_field]` Shortcode fuer alle Felder
|
||||
- Shortcode-Dokumentation im Help-Service
|
||||
- Unterstuetzung fuer eigene Produktarten
|
||||
|
||||
### Sprint 8: Dienstleistungs-Matrix (08.12.2025)
|
||||
|
||||
- Selbst definierbare Produktarten (A-L + eigene)
|
||||
- class-dienstleistungen.php fuer CRUD
|
||||
- class-field-filter.php fuer bedingte Felder
|
||||
- Matrix-Popup fuer Produktart-Zuordnung
|
||||
|
||||
### Sprint 7.7: Modul-System (04.12.2025)
|
||||
|
||||
- 9 Feature-Module ein-/ausschaltbar
|
||||
- `is_module_active()` Helper-Funktion
|
||||
- Bedingte UI-Elemente
|
||||
|
||||
---
|
||||
|
||||
## Abgeschlossen: Sprint 9
|
||||
|
||||
### Sprint 9: Pop-up Neuigkeiten (16.12.2025)
|
||||
|
||||
Marketing-Popup-System fuer Kursbewerbung:
|
||||
- Freie Kursauswahl (einzeln, Kategorie, Produktart, alle)
|
||||
- Zufaellige Anzeige aus Kurs-Pool
|
||||
- Cookie-basierte Steuerung
|
||||
- Statistiken (Views, Klicks, Schliessungen)
|
||||
- Neue "Marketing" Sektion im Help-Service
|
||||
|
||||
### Sprint 10: Kontext-Hilfe-System (16.12.2025)
|
||||
|
||||
Info-Icons mit Tooltips direkt in der Admin-UI:
|
||||
- Helper-Methode `render_help_icon()` / `help_icon()`
|
||||
- CSS Tooltip-Styles mit 4 Positionsoptionen
|
||||
- Info-Icons bei wichtigen Feldern (Kleinunternehmer, Module, Rechtliches, IBAN)
|
||||
- Link zu ausfuehrlicher Dokumentation im Tooltip
|
||||
|
||||
### Sprint 11: Custom Database Tables (16.12.2025)
|
||||
|
||||
Performance-Optimierung bei >5.000 Buchungen:
|
||||
- Custom Table `wp_kurs_buchungen` mit optimierten Indizes
|
||||
- Repository-Pattern fuer schnelle Abfragen
|
||||
- Denormalisierte Speicherung fuer Aggregationen
|
||||
- Admin-UI fuer Migration mit Fortschrittsbalken
|
||||
- Automatische Sync bei Buchungs-Aenderungen
|
||||
|
||||
### Geplante Sprints
|
||||
|
||||
(Derzeit keine weiteren Sprints geplant)
|
||||
|
||||
---
|
||||
|
||||
## Produktarten-Matrix
|
||||
|
||||
| ID | Name | Buchungsart | Terminart |
|
||||
|----|------|-------------|-----------|
|
||||
| A | Praesenz-Kurs | Direkt | Fix |
|
||||
| B | Workshop | Direkt | Fix |
|
||||
| C | Webinar | Direkt | Fix |
|
||||
| D | Mental Coaching | Direkt | Fix |
|
||||
| D1 | Einzel-Coaching | Anfrage | Individuell |
|
||||
| E | Video-Kurs | Direkt | Unbegrenzt |
|
||||
| F | Video-Paket | Direkt | Unbegrenzt |
|
||||
| G | Webinar Live | Direkt | Fix |
|
||||
| H | Workshop Online | Direkt | Fix |
|
||||
| I | Coaching Online | Anfrage | Individuell |
|
||||
| J | Online Unterricht | Anfrage | Individuell |
|
||||
| K | Video-Analyse | Anfrage | - |
|
||||
| L | Beratung | Anfrage | Individuell |
|
||||
|
||||
---
|
||||
|
||||
## Plugin-Struktur
|
||||
|
||||
```
|
||||
kurs-booking/
|
||||
├── includes/
|
||||
│ ├── class-kurs.php
|
||||
│ ├── class-buchung.php
|
||||
│ ├── class-settings.php
|
||||
│ ├── class-dienstleistungen.php
|
||||
│ ├── class-field-filter.php
|
||||
│ ├── class-field-tree.php
|
||||
│ ├── class-sevdesk.php
|
||||
│ └── ...
|
||||
├── templates/
|
||||
├── assets/
|
||||
└── help-service/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test-URLs
|
||||
|
||||
| Umgebung | URL |
|
||||
|----------|-----|
|
||||
| **Production Ready** | http://192.168.100.93:8200 |
|
||||
| Staging (Kadence) | http://192.168.100.93:8300 |
|
||||
| Original (Backup) | http://192.168.100.93:8600 |
|
||||
|
||||
---
|
||||
|
||||
**Detaillierte Dokumentation:** Siehe `docs/kurs-booking/entwicklung/sprints/SPRINT-UEBERSICHT.md`
|
||||
61
content/erweiterte-optionen.md
Normal file
61
content/erweiterte-optionen.md
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
id: erweiterte-optionen
|
||||
title: Erweiterte Optionen
|
||||
icon: sliders
|
||||
description: Fortgeschrittene Konfiguration
|
||||
section: Tipps & Support
|
||||
tags: [Erweitert, Optionen, Hooks, Filter]
|
||||
related: [best-practices, general]
|
||||
order: 61
|
||||
---
|
||||
|
||||
# Erweiterte Optionen
|
||||
|
||||
Fortgeschrittene Konfigurationsmoeglichkeiten fuer Entwickler.
|
||||
|
||||
## WordPress Hooks
|
||||
|
||||
### Actions
|
||||
|
||||
```php
|
||||
// Nach Buchungsbestaetigung
|
||||
do_action( 'kurs_booking_confirmed', $buchung_id );
|
||||
|
||||
// Nach Stornierung
|
||||
do_action( 'kurs_booking_cancelled', $buchung_id );
|
||||
|
||||
// Vor E-Mail-Versand
|
||||
do_action( 'kurs_booking_before_email', $buchung_id, $email_type );
|
||||
```
|
||||
|
||||
### Filters
|
||||
|
||||
```php
|
||||
// E-Mail-Inhalt anpassen
|
||||
add_filter( 'kurs_booking_email_content', function( $content, $buchung_id ) {
|
||||
return $content . "\n\nZusaetzlicher Text";
|
||||
}, 10, 2 );
|
||||
|
||||
// Preis-Format aendern
|
||||
add_filter( 'kurs_booking_format_price', function( $formatted, $price ) {
|
||||
return number_format( $price, 2, ',', '.' ) . ' EUR';
|
||||
}, 10, 2 );
|
||||
```
|
||||
|
||||
## REST API
|
||||
|
||||
| Endpoint | Methode | Beschreibung |
|
||||
|----------|---------|--------------|
|
||||
| `/wp-json/kurs-booking/v1/kurse` | GET | Liste aller Kurse |
|
||||
| `/wp-json/kurs-booking/v1/buchung` | POST | Neue Buchung erstellen |
|
||||
| `/wp-json/kurs-booking/v1/verify` | POST | Token verifizieren |
|
||||
|
||||
## Debugging
|
||||
|
||||
| Option | Beschreibung |
|
||||
|--------|--------------|
|
||||
| **WP_DEBUG** | WordPress Debug-Modus |
|
||||
| **Log-Dateien** | `wp-content/debug.log` |
|
||||
| **E-Mail-Log** | Optional in Einstellungen |
|
||||
|
||||
> **Warnung:** Debug-Optionen nur in Entwicklungsumgebungen aktivieren!
|
||||
289
content/features.md
Normal file
289
content/features.md
Normal file
@@ -0,0 +1,289 @@
|
||||
---
|
||||
id: features
|
||||
title: Feature-Uebersicht
|
||||
icon: list-check
|
||||
description: Alle Funktionen des Kurs-Booking Plugins
|
||||
section: Tipps & Support
|
||||
tags: [Features, Funktionen, Uebersicht]
|
||||
related: [general, best-practices]
|
||||
order: 62
|
||||
---
|
||||
|
||||
# Feature-Uebersicht
|
||||
|
||||
**Version:** 1.7 | **Stand:** Dezember 2025 | **Fortschritt:** 98%
|
||||
|
||||
---
|
||||
|
||||
## Kernfunktionen
|
||||
|
||||
### Post Types
|
||||
|
||||
| Post Type | Beschreibung | Status |
|
||||
|-----------|--------------|--------|
|
||||
| `kurs` | Veranstaltungen/Kurse | ✓ Fertig |
|
||||
| `kurs-buchung` | Buchungsverwaltung | ✓ Fertig |
|
||||
| `kurs-video` | Video-Inhalte | ✓ Grundgeruest |
|
||||
|
||||
---
|
||||
|
||||
## Produktarten (Dienstleistungs-Matrix)
|
||||
|
||||
| ID | Label | Name | Buchungsart | Terminart |
|
||||
|----|-------|------|-------------|-----------|
|
||||
| `kurs` | A) | Praesenz-Kurs | direkt | fix |
|
||||
| `workshop` | B) | Workshop | direkt | fix |
|
||||
| `webinar` | C) | Webinar | direkt | fix |
|
||||
| `gruppencoaching` | D) | Mental Coaching | direkt | fix |
|
||||
| `einzelcoaching` | D1) | Einzel-Coaching | anfrage | individuell |
|
||||
| `videokurs` | E) | Video-Kurs | direkt | unbegrenzt |
|
||||
| `video_paket` | F) | Video-Paket | direkt | unbegrenzt |
|
||||
|
||||
### Bedingte Felder pro Produktart
|
||||
|
||||
| Produktart | Location | Online/Zoom | Video |
|
||||
|------------|----------|-------------|-------|
|
||||
| A) Kurs | ✓ | - | - |
|
||||
| B) Workshop | ✓/- | ✓/- | - |
|
||||
| C) Webinar | - | ✓ | - |
|
||||
| D) Gruppencoaching | - | ✓ | - |
|
||||
| D1) Einzelcoaching | ✓ | - | - |
|
||||
| E) Video-Kurs | - | - | ✓ |
|
||||
|
||||
---
|
||||
|
||||
## Buchungssystem
|
||||
|
||||
### 3-Schritt Buchungsflow
|
||||
|
||||
```
|
||||
1. Tickets waehlen → 2. Daten eingeben → 3. Bestaetigen
|
||||
↓ ↓ ↓
|
||||
Ticketauswahl Kundendaten Double-Opt-In
|
||||
Live-Preis Zusatzfelder E-Mail-Versand
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
| Feature | Beschreibung |
|
||||
|---------|--------------|
|
||||
| 3-Schritt Modal | Wizard im Overlay |
|
||||
| Ticketauswahl | +/- Buttons, Live-Preis |
|
||||
| Double-Opt-In | Token-basierte Bestaetigung |
|
||||
| Buchungsnummer | Prefix + Jahr + fortlaufend |
|
||||
|
||||
---
|
||||
|
||||
## E-Mail System
|
||||
|
||||
### Vorlagen
|
||||
|
||||
| Vorlage | Ausloeser |
|
||||
|---------|-----------|
|
||||
| Buchungseingang | Formular abgesendet |
|
||||
| Buchungsbestaetigung | Double-Opt-In bestaetigt |
|
||||
| Admin-Benachrichtigung | Neue Buchung |
|
||||
| Storno-Bestaetigung | Buchung storniert |
|
||||
|
||||
### Platzhalter
|
||||
|
||||
```
|
||||
{kunde_name} {kunde_email} {kunde_telefon}
|
||||
{kurs_name} {kurs_datum} {kurs_zeit}
|
||||
{kurs_ort} {buchungsnummer} {betrag}
|
||||
{firma_name} {firma_adresse} {bestaetigung_link}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stornierung
|
||||
|
||||
### Gebuehren-Staffelung
|
||||
|
||||
| Zeitraum | Gebuehr |
|
||||
|----------|---------|
|
||||
| >= free_days | 0% (kostenfrei) |
|
||||
| >= partial_days | partial_percent% |
|
||||
| < partial_days | 100% |
|
||||
| Kurs gestartet | Nicht moeglich |
|
||||
| Video-Kurs | Nicht moeglich |
|
||||
|
||||
---
|
||||
|
||||
## Flexible Preisvarianten
|
||||
|
||||
### Optionen pro Variante
|
||||
|
||||
| Option | Beschreibung |
|
||||
|--------|--------------|
|
||||
| **Pro Tag berechnen** | Preis × Kurstage |
|
||||
| **Automatisch verrechnen** | Auf sevDesk-Rechnung |
|
||||
| **Hinweis** | Info fuer manuelle Zahlung |
|
||||
|
||||
### Beispiel
|
||||
|
||||
```
|
||||
Kurs-Teilnahme: 350 EUR (auto_invoice)
|
||||
Gastbox: 60 EUR (20/Tag × 3 Tage) *
|
||||
Leihpferd: 50 EUR *
|
||||
─────────────────────────────────────────
|
||||
Gesamt: 460 EUR
|
||||
davon Rechnung: 350 EUR
|
||||
|
||||
* Wird vor Ort verrechnet
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Kurs-spezifische Felder
|
||||
|
||||
| Feature | Beschreibung |
|
||||
|---------|--------------|
|
||||
| Globale Felder Override | Pro Kurs ein-/ausschalten |
|
||||
| Kurs-spezifische Felder | Repeater mit Drag & Drop |
|
||||
| Feldtypen | text, email, tel, number, date, textarea, select, radio |
|
||||
| Varianten-Zusatzfelder | Menge oder Info pro Variante |
|
||||
|
||||
---
|
||||
|
||||
## sevDesk Integration
|
||||
|
||||
| Funktion | Beschreibung |
|
||||
|----------|--------------|
|
||||
| Kontakt-Suche | Pruefen ob Kunde existiert |
|
||||
| Kontakt-Anlage | Automatisch bei neuer E-Mail |
|
||||
| Rechnungserstellung | Nach Buchungsbestaetigung |
|
||||
| MwSt-Logik | Brutto/Netto/Kleinunternehmer |
|
||||
|
||||
---
|
||||
|
||||
## Kurs-Vorlagen
|
||||
|
||||
| Feature | Beschreibung |
|
||||
|---------|--------------|
|
||||
| Vorlage-Toggle | Checkbox in Sidebar |
|
||||
| Aus Vorlage erstellen | Button ueber Liste |
|
||||
| Meta-Kopie | Alle Felder werden uebernommen |
|
||||
| Taxonomie-Kopie | Kategorien werden uebernommen |
|
||||
|
||||
---
|
||||
|
||||
## Stammdaten
|
||||
|
||||
| Liste | Verwendung |
|
||||
|-------|------------|
|
||||
| Reitlehrer/innen | Dropdown im Kurs |
|
||||
| Pferde | Dropdown mit "Andere" |
|
||||
| Veranstaltungsorte | Dropdown mit Auto-Fill URL |
|
||||
|
||||
---
|
||||
|
||||
## Buchungsfelder
|
||||
|
||||
### Feldtypen
|
||||
|
||||
| Typ | Beschreibung |
|
||||
|-----|--------------|
|
||||
| `text` | Einzeiliges Textfeld |
|
||||
| `email` | E-Mail mit Validierung |
|
||||
| `tel` | Telefonnummer |
|
||||
| `number` | Zahlenfeld |
|
||||
| `date` | Datumsauswahl |
|
||||
| `textarea` | Mehrzeiliger Text |
|
||||
| `select` | Dropdown |
|
||||
| `radio` | Optionsfelder |
|
||||
| `checkbox` | Einzelne Checkbox |
|
||||
| `agreement` | Zustimmung mit Link |
|
||||
|
||||
### Bedingte Felder
|
||||
|
||||
Format: `feldname:wert`
|
||||
|
||||
```
|
||||
Feld: "Anzahl Gastboxen"
|
||||
Bedingung: gastboxen-benoetigt:1
|
||||
→ Wird nur angezeigt wenn Checkbox aktiviert
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Multi-Layer Spam-Schutz
|
||||
|
||||
| Layer | Mechanismus |
|
||||
|-------|-------------|
|
||||
| 1 | Cloudflare Turnstile |
|
||||
| 2 | Honeypot-Felder |
|
||||
| 2b | Zeit-Pruefung (min. 3 Sek.) |
|
||||
| 3 | Inhaltsfilter (Spam-Keywords) |
|
||||
| 4 | Rate-Limiting (10/h pro IP) |
|
||||
|
||||
---
|
||||
|
||||
## Einstellungs-Tabs
|
||||
|
||||
| Tab | Beschreibung |
|
||||
|-----|--------------|
|
||||
| Allgemein | Firmendaten, Logo |
|
||||
| Module | Feature-Module ein/aus |
|
||||
| Stammdaten | Reitlehrer, Pferde, Orte |
|
||||
| Buchung | Buchungsnummer, Token |
|
||||
| Preise & MwSt | Steuer, Waehrung |
|
||||
| Stornierung | Fristen, Gebuehren |
|
||||
| Rechtliches | AGB, Widerruf |
|
||||
| Buchungsfelder | Dynamische Felder |
|
||||
| E-Mails | Absender-Einstellungen |
|
||||
| Bankverbindung | IBAN, BIC |
|
||||
| Zahlungen | Aktive Methoden |
|
||||
| Spam-Schutz | Turnstile, Honeypot |
|
||||
| sevDesk | API-Token |
|
||||
| Video-Service | API-URL |
|
||||
| E-Mail Vorlagen | Template-Editor |
|
||||
|
||||
**Gesamt: 82 Einstellungsfelder**
|
||||
|
||||
---
|
||||
|
||||
## Video-Kurs System
|
||||
|
||||
| Feature | Beschreibung |
|
||||
|---------|--------------|
|
||||
| Video-Zuordnung | Checkbox-Liste |
|
||||
| Zugangs-Dauer | Konfigurierbar |
|
||||
| Widerrufsverzicht | Separate Checkbox |
|
||||
| Shortcode | `[kurs_video_access]` |
|
||||
|
||||
---
|
||||
|
||||
## Frontend-Anzeige
|
||||
|
||||
| Feature | Beschreibung |
|
||||
|---------|--------------|
|
||||
| Aehnliche Kurse | Auto oder manuell |
|
||||
| Kategorie-Badge | Klickbar auf Cards |
|
||||
| Taxonomie-Archiv | Card-Grid pro Kategorie |
|
||||
|
||||
---
|
||||
|
||||
## Kundenportal-Integration
|
||||
|
||||
| Feature | Beschreibung |
|
||||
|---------|--------------|
|
||||
| E-Mail-Pruefung | Automatisch bei Eingabe |
|
||||
| "Ich bin Kunde" Button | Manueller OTP-Flow |
|
||||
| Daten-Prefill | Name, E-Mail, Telefon |
|
||||
| Consent-Schutz | Einwilligungen nicht ueberschreiben |
|
||||
|
||||
---
|
||||
|
||||
## Geplante Features
|
||||
|
||||
| Feature | Status |
|
||||
|---------|--------|
|
||||
| Kadence Frontend-Templates | In Arbeit |
|
||||
| Video-Trailer/Vorschau | Geplant |
|
||||
| Video-Pakete/Bundles | Entscheidung ausstehend |
|
||||
| Zoom-Felder | Geplant |
|
||||
|
||||
---
|
||||
|
||||
**Kurs-Booking Plugin v1.7**
|
||||
40
content/feldtypen.md
Normal file
40
content/feldtypen.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
id: feldtypen
|
||||
title: Feldtypen
|
||||
icon: input-cursor-text
|
||||
description: Alle verfuegbaren Feldtypen im Detail
|
||||
section: Buchung & Preise
|
||||
tags: [Felder, Typen, Formular]
|
||||
related: [fields, erweiterte-optionen]
|
||||
order: 12
|
||||
---
|
||||
|
||||
# Feldtypen
|
||||
|
||||
## Eingabefelder
|
||||
|
||||
| Typ | Beschreibung | Beispiel |
|
||||
|-----|--------------|----------|
|
||||
| **Text** | Einzeiliges Textfeld | Vorname, Strasse |
|
||||
| **E-Mail** | Mit Validierung | kunde@beispiel.de |
|
||||
| **Telefon** | Mobil-optimiert | +43 123 456789 |
|
||||
| **Textarea** | Mehrzeilig | Anmerkungen |
|
||||
| **Zahl** | Numerisch | Anzahl, Alter |
|
||||
| **Datum** | Kalender-Popup | Geburtsdatum |
|
||||
|
||||
## Auswahlfelder
|
||||
|
||||
| Typ | Beschreibung | Hinweis |
|
||||
|-----|--------------|---------|
|
||||
| **Select** | Dropdown-Menue | Optionen durch Komma trennen |
|
||||
| **Radio** | Einfachauswahl | Nur eine Option waehlbar |
|
||||
| **Checkbox** | Ja/Nein | Newsletter abonnieren |
|
||||
|
||||
## Spezialfelder
|
||||
|
||||
| Typ | Beschreibung | Hinweis |
|
||||
|-----|--------------|---------|
|
||||
| **Zustimmung** | Pflicht-Checkbox mit Link | AGB, Datenschutz |
|
||||
| **Ueberschrift** | Abschnitts-Titel | Kein Eingabefeld |
|
||||
| **Info** | Hinweistext | Kein Eingabefeld |
|
||||
| **Trennlinie** | Horizontale Linie | Visuelle Trennung |
|
||||
41
content/fields.md
Normal file
41
content/fields.md
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
id: fields
|
||||
title: Buchungsfelder
|
||||
icon: ui-checks-grid
|
||||
description: Formularfelder fuer das Buchungsformular
|
||||
section: Buchung & Preise
|
||||
tags: [Felder, Formular, Grundlagen]
|
||||
related: [feldtypen, produktarten-filter, booking]
|
||||
tips:
|
||||
- Drag & Drop zum Sortieren
|
||||
- Deaktivierte Felder werden nicht angezeigt
|
||||
- Speichern nicht vergessen!
|
||||
order: 11
|
||||
---
|
||||
|
||||
# Buchungsfelder
|
||||
|
||||
Die Buchungsfelder definieren, welche Informationen Kunden beim Buchen eingeben muessen.
|
||||
|
||||
## Grundfunktionen
|
||||
|
||||
| Funktion | Beschreibung |
|
||||
|----------|--------------|
|
||||
| **Drag & Drop** | Ziehen Sie Felder am Verschiebe-Icon (links) in die gewuenschte Reihenfolge |
|
||||
| **Aktivieren/Deaktivieren** | Toggle-Schalter links vom Feldnamen |
|
||||
| **Pflichtfeld** | Checkbox - Kunde muss dieses Feld ausfuellen |
|
||||
| **Optionen-Popup** | Zahnrad-Icon oeffnet erweiterte Einstellungen |
|
||||
|
||||
## Header-Badges
|
||||
|
||||
Die farbigen Badges zeigen an, bei welchen Produktarten das Feld erscheint:
|
||||
|
||||
- 🟢 **Alle** - Bei allen Produktarten
|
||||
- ⚫ **Basis** - Basis-Feld (immer sichtbar)
|
||||
- 🔵 **Kurs** - Nur bei dieser Produktart
|
||||
|
||||
## Footer-Filter
|
||||
|
||||
Waehlen Sie eine Produktart, um nur die relevanten Felder anzuzeigen. Das hilft bei der Uebersicht, wenn Sie viele Felder haben.
|
||||
|
||||
> **Tipp:** Nutzen Sie den Footer-Filter, um zu pruefen, welche Felder bei einer bestimmten Produktart angezeigt werden.
|
||||
34
content/general.md
Normal file
34
content/general.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
id: general
|
||||
title: Allgemein
|
||||
icon: building
|
||||
description: Firmendaten und grundlegende Einstellungen
|
||||
section: Grundeinstellungen
|
||||
tags: [Firma, Einstellungen, Grundlagen]
|
||||
related: [modules, legal]
|
||||
order: 1
|
||||
---
|
||||
|
||||
# Allgemeine Einstellungen
|
||||
|
||||
## Firmenangaben
|
||||
|
||||
Diese Daten erscheinen in E-Mails und Rechnungen:
|
||||
|
||||
| Feld | Beschreibung |
|
||||
|------|--------------|
|
||||
| **Logo** | Wird in E-Mail-Kopfzeile verwendet |
|
||||
| **Firmenname** | Offizieller Name Ihres Unternehmens |
|
||||
| **Adresse** | Strasse, PLZ, Ort |
|
||||
| **Kontakt** | Telefon, E-Mail, Website |
|
||||
| **Geschaeftsfuehrer** | Name des Inhabers/GF |
|
||||
| **Steuernummer** | Oder USt-IdNr. (eine davon Pflicht) |
|
||||
|
||||
## Hilfe-System
|
||||
|
||||
Hier koennen Sie die URL zur Hilfe-Dokumentation konfigurieren:
|
||||
|
||||
- **Staging:** `http://192.168.100.93:5050`
|
||||
- **Produktion:** `https://help.ihre-domain.de`
|
||||
|
||||
> **Tipp:** Alle mit * markierten Felder sind Pflichtfelder und muessen ausgefuellt werden.
|
||||
51
content/import.md
Normal file
51
content/import.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
id: import
|
||||
title: Import/Export
|
||||
icon: arrow-left-right
|
||||
description: Daten importieren und exportieren
|
||||
section: Integrationen
|
||||
tags: [Import, Export, CSV, Migration]
|
||||
related: [masterdata, booking]
|
||||
order: 42
|
||||
---
|
||||
|
||||
# Import/Export
|
||||
|
||||
## Daten exportieren
|
||||
|
||||
Exportieren Sie Ihre Daten als CSV:
|
||||
|
||||
| Export | Inhalt |
|
||||
|--------|--------|
|
||||
| **Buchungen** | Alle Buchungen mit Teilnehmerdaten |
|
||||
| **Kurse** | Kursliste mit Terminen |
|
||||
| **Teilnehmer** | Kontaktdaten aller Teilnehmer |
|
||||
|
||||
## Daten importieren
|
||||
|
||||
Importieren Sie Daten aus anderen Systemen:
|
||||
|
||||
| Import | Format |
|
||||
|--------|--------|
|
||||
| **Kurse** | CSV mit Pflichtfeldern |
|
||||
| **Teilnehmer** | CSV mit E-Mail als Schluessel |
|
||||
| **Stammdaten** | JSON-Format |
|
||||
|
||||
## CSV-Format
|
||||
|
||||
Beispiel fuer Kurs-Import:
|
||||
|
||||
```csv
|
||||
titel;datum;uhrzeit_von;uhrzeit_bis;preis;max_teilnehmer
|
||||
"WordPress Grundkurs";2025-02-15;09:00;17:00;299.00;12
|
||||
"PHP fuer Einsteiger";2025-02-22;10:00;16:00;349.00;10
|
||||
```
|
||||
|
||||
## Hinweise
|
||||
|
||||
- Trennzeichen: Semikolon (;)
|
||||
- Encoding: UTF-8
|
||||
- Datums-Format: YYYY-MM-DD
|
||||
- Preise: Mit Punkt als Dezimaltrennzeichen
|
||||
|
||||
> **Wichtig:** Erstellen Sie vor dem Import immer ein Backup!
|
||||
163
content/kurs-vorlagen.md
Normal file
163
content/kurs-vorlagen.md
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
id: kurs-vorlagen
|
||||
title: Kurs-Vorlagen
|
||||
icon: copy
|
||||
description: Kurse als Vorlage speichern und wiederverwenden
|
||||
section: Tipps & Tricks
|
||||
tags: [Vorlagen, Templates, Kurse, Effizienz]
|
||||
related: [booking, dienstleistungen, best-practices]
|
||||
order: 63
|
||||
---
|
||||
|
||||
# Kurs-Vorlagen
|
||||
|
||||
Mit Kurs-Vorlagen koennen Sie wiederkehrende Veranstaltungen schnell erstellen.
|
||||
|
||||
---
|
||||
|
||||
## Was sind Kurs-Vorlagen?
|
||||
|
||||
Eine Kurs-Vorlage ist ein gespeicherter Kurs, der als Basis fuer neue Kurse dient. Alle Einstellungen werden uebernommen:
|
||||
|
||||
- Titel und Beschreibung
|
||||
- Produktart
|
||||
- Preisvarianten
|
||||
- Buchungsfelder
|
||||
- Kategorien und Tags
|
||||
- Alle Meta-Felder
|
||||
|
||||
---
|
||||
|
||||
## Vorlage erstellen
|
||||
|
||||
### Methode 1: Bestehenden Kurs als Vorlage markieren
|
||||
|
||||
1. Oeffnen Sie einen bestehenden Kurs
|
||||
2. In der rechten Sidebar: **Kurs-Optionen**
|
||||
3. Aktivieren Sie: **Als Vorlage speichern**
|
||||
4. Aktualisieren Sie den Kurs
|
||||
|
||||
### Methode 2: Neuen Kurs als Vorlage anlegen
|
||||
|
||||
1. Erstellen Sie einen neuen Kurs
|
||||
2. Fuellen Sie alle wiederkehrenden Felder aus
|
||||
3. **Wichtig:** Lassen Sie Datum/Zeit leer
|
||||
4. Aktivieren Sie: **Als Vorlage speichern**
|
||||
5. Veroeffentlichen Sie den Kurs
|
||||
|
||||
> **Tipp:** Vorlagen erscheinen nicht im Frontend und sind nur fuer Admins sichtbar.
|
||||
|
||||
---
|
||||
|
||||
## Kurs aus Vorlage erstellen
|
||||
|
||||
### Ueber die Kursliste
|
||||
|
||||
1. Gehen Sie zu **Kurse > Alle Kurse**
|
||||
2. Klicken Sie auf **Aus Vorlage erstellen** (oben)
|
||||
3. Waehlen Sie eine Vorlage aus dem Dropdown
|
||||
4. Klicken Sie **Erstellen**
|
||||
|
||||
### Was wird kopiert?
|
||||
|
||||
| Element | Wird kopiert |
|
||||
|---------|--------------|
|
||||
| Titel | Ja (mit "Kopie von" Prefix) |
|
||||
| Inhalt/Beschreibung | Ja |
|
||||
| Produktart | Ja |
|
||||
| Preisvarianten | Ja |
|
||||
| Max. Teilnehmer | Ja |
|
||||
| Buchungsfelder | Ja |
|
||||
| Kategorien | Ja |
|
||||
| Beitragsbild | Ja |
|
||||
| Datum/Zeit | Nein (muss neu eingegeben werden) |
|
||||
| Veranstaltungsort | Ja |
|
||||
| Reitlehrer | Ja |
|
||||
|
||||
### Nach dem Erstellen
|
||||
|
||||
1. Aendern Sie den Titel (entfernen Sie "Kopie von")
|
||||
2. Setzen Sie das neue Datum und die Uhrzeit
|
||||
3. Passen Sie ggf. den Preis an
|
||||
4. Veroeffentlichen Sie den Kurs
|
||||
|
||||
---
|
||||
|
||||
## Vorlagen verwalten
|
||||
|
||||
### Vorlagen finden
|
||||
|
||||
1. Gehen Sie zu **Kurse > Alle Kurse**
|
||||
2. Nutzen Sie den Filter: **Vorlagen anzeigen**
|
||||
3. Oder suchen Sie nach "[Vorlage]" im Titel
|
||||
|
||||
### Vorlage bearbeiten
|
||||
|
||||
Aenderungen an einer Vorlage wirken sich **nicht** auf bereits erstellte Kurse aus. Jeder Kurs ist unabhaengig.
|
||||
|
||||
### Vorlage loeschen
|
||||
|
||||
1. Oeffnen Sie die Vorlage
|
||||
2. Deaktivieren Sie: **Als Vorlage speichern**
|
||||
3. Loeschen Sie den Kurs (Papierkorb)
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Namenskonvention
|
||||
|
||||
Benennen Sie Vorlagen eindeutig:
|
||||
|
||||
```
|
||||
[Vorlage] Wochenend-Workshop
|
||||
[Vorlage] Online-Webinar 2h
|
||||
[Vorlage] Tageskurs Anfaenger
|
||||
```
|
||||
|
||||
### Eine Vorlage pro Produktart
|
||||
|
||||
Erstellen Sie mindestens eine Vorlage fuer jede Produktart:
|
||||
|
||||
| Vorlage | Produktart | Verwendung |
|
||||
|---------|------------|------------|
|
||||
| [Vorlage] Praesenz-Kurs | A | Mehrtaegige Kurse |
|
||||
| [Vorlage] Tages-Workshop | B | Einzeltage |
|
||||
| [Vorlage] Online-Webinar | C | Zoom-Sessions |
|
||||
| [Vorlage] Gruppencoaching | D | Mental-Training |
|
||||
| [Vorlage] Einzelsession | D1 | 1:1 Coaching |
|
||||
| [Vorlage] Video-Kurs | E | Aufzeichnungen |
|
||||
|
||||
### Preisvarianten vorbereiten
|
||||
|
||||
Legen Sie in der Vorlage die typischen Varianten an:
|
||||
|
||||
```
|
||||
Fruehbucher: [Preis anpassen]
|
||||
Normalpreis: [Preis anpassen]
|
||||
Wiederholer: [Preis anpassen]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Haeufige Fragen
|
||||
|
||||
### Kann ich mehrere Kurse gleichzeitig aus einer Vorlage erstellen?
|
||||
|
||||
Nein, aktuell muss jeder Kurs einzeln erstellt werden.
|
||||
|
||||
### Werden Buchungen mitkopiert?
|
||||
|
||||
Nein, Buchungen sind immer kursspezifisch und werden nie kopiert.
|
||||
|
||||
### Kann ich eine Vorlage aus einem bereits stattgefundenen Kurs machen?
|
||||
|
||||
Ja, oeffnen Sie den Kurs und aktivieren Sie "Als Vorlage speichern".
|
||||
|
||||
---
|
||||
|
||||
## Siehe auch
|
||||
|
||||
- [Buchungseinstellungen](/topic/booking)
|
||||
- [Produktarten](/topic/dienstleistungen)
|
||||
- [Best Practices](/topic/best-practices)
|
||||
164
content/kursarten/online-termine.md
Normal file
164
content/kursarten/online-termine.md
Normal file
@@ -0,0 +1,164 @@
|
||||
---
|
||||
title: Online-Termine
|
||||
description: Termine und Zeiten bei Online-Angeboten (G-L)
|
||||
---
|
||||
|
||||
# Online-Termine
|
||||
|
||||
Wie Datum und Uhrzeit bei den verschiedenen Online-Kursarten gehandhabt werden.
|
||||
|
||||
---
|
||||
|
||||
## Uebersicht
|
||||
|
||||
| Kursart | Terminart | Datum/Uhrzeit erforderlich? |
|
||||
|---------|-----------|----------------------------|
|
||||
| G) Webinar Live | **Fix** | Ja |
|
||||
| H) Workshop Online | **Fix** | Ja |
|
||||
| I) Coaching Online | Individuell | Nein |
|
||||
| J) Online Unterricht | Individuell | Nein |
|
||||
| K) Video-Analyse | Kein Termin | Nein |
|
||||
| L) Beratung | Individuell | Nein |
|
||||
|
||||
---
|
||||
|
||||
## G) Webinar Live - Fester Termin
|
||||
|
||||
### So funktioniert es
|
||||
- Webinare finden zu **festen Terminen** ueber Zoom statt
|
||||
- Teilnehmer loggen sich zur angegebenen **Uhrzeit** ein
|
||||
- Dauer: **60-90 Minuten**
|
||||
- Termine stehen **mehrere Wochen im Voraus** fest
|
||||
|
||||
### Im Kurs-Editor eingeben
|
||||
- Startdatum (Pflicht)
|
||||
- Startzeit (Pflicht)
|
||||
- Endzeit (optional)
|
||||
- Zoom-Link (Pflicht)
|
||||
|
||||
### Zusatzfelder fuer Aufzeichnung
|
||||
- Aufzeichnung verfuegbar: Ja/Nein
|
||||
- Tage bis Aufzeichnung fertig: z.B. 2 Tage
|
||||
- Zugang zur Aufzeichnung: z.B. 30 Tage
|
||||
|
||||
---
|
||||
|
||||
## H) Workshop Online - Fester Termin
|
||||
|
||||
### So funktioniert es
|
||||
- Termine stehen **mehrere Wochen im Voraus** fest
|
||||
- Dauer: **2-4 Stunden**, je nach Thema
|
||||
- Kann auf **zwei Termine** verteilt werden
|
||||
|
||||
### Im Kurs-Editor eingeben
|
||||
- Startdatum (Pflicht)
|
||||
- Enddatum (bei mehrtaegig)
|
||||
- Startzeit (Pflicht)
|
||||
- Endzeit (optional)
|
||||
- Zoom-Link (Pflicht)
|
||||
|
||||
---
|
||||
|
||||
## I) Coaching Online - Auf Anfrage
|
||||
|
||||
### So funktioniert es
|
||||
- Termin wird **individuell vereinbart**
|
||||
- Kunde schickt Anfrage mit bevorzugten Zeiten
|
||||
- Dauer: **45-60 Minuten** (Standard)
|
||||
|
||||
### Im Kurs-Editor eingeben
|
||||
- **Kein Datum/Uhrzeit** - Buchungsart "Anfrage" waehlen
|
||||
- Coaching-Dauer in Minuten (z.B. 60)
|
||||
|
||||
### Ablauf
|
||||
1. Kunde stellt Anfrage ueber Formular
|
||||
2. Du meldest dich mit Terminvorschlaegen
|
||||
3. Termin wird per E-Mail/Telefon vereinbart
|
||||
4. Zoom-Link wird manuell verschickt
|
||||
|
||||
---
|
||||
|
||||
## J) Online Unterricht - Auf Anfrage
|
||||
|
||||
### So funktioniert es
|
||||
- Findet nach **individueller Vereinbarung** statt
|
||||
- Live-Reitunterricht per Zoom
|
||||
- Kunde stellt Handy/Tablet am Reitplatz auf
|
||||
|
||||
### Im Kurs-Editor eingeben
|
||||
- **Kein Datum/Uhrzeit** - Buchungsart "Anfrage" waehlen
|
||||
- Unterrichtsdauer in Minuten (z.B. 45)
|
||||
- Technische Voraussetzungen (z.B. "Smartphone, Stativ")
|
||||
|
||||
---
|
||||
|
||||
## K) Video-Analyse - Kein fester Termin
|
||||
|
||||
### So funktioniert es
|
||||
- Kunde ist **zeitlich unabhaengig**
|
||||
- Kunde filmt, **wenn es ihm passt**
|
||||
- Du analysierst das Video und erstellst Feedback
|
||||
|
||||
### Im Kurs-Editor eingeben
|
||||
- **Kein Datum/Uhrzeit** - Buchungsart "Anfrage" waehlen
|
||||
- Max. Videolaenge (z.B. 15 Minuten)
|
||||
- Feedback-Format (Audio/Video/Text)
|
||||
- Bearbeitungszeit in Tagen (z.B. 3 Tage)
|
||||
|
||||
---
|
||||
|
||||
## L) Beratung & Begleitung - Flexibel
|
||||
|
||||
### Einmalige Beratung
|
||||
- Ein einzelnes Gespraech zu einem Thema
|
||||
- Termin wird individuell vereinbart
|
||||
- Dauer: z.B. 60 Minuten
|
||||
|
||||
### Laengere Begleitung
|
||||
- Regelmaessige Gespraeche ueber Wochen/Monate
|
||||
- Fuer groessere Themen
|
||||
- Mehrere Termine werden vereinbart
|
||||
|
||||
### Im Kurs-Editor eingeben
|
||||
- **Kein Datum/Uhrzeit** - Buchungsart "Anfrage" waehlen
|
||||
- Beratungsart: "Einmalig" oder "Begleitung"
|
||||
- Bei Einmalig: Gespraechsdauer
|
||||
- Bei Begleitung: Dauer in Wochen
|
||||
- Kommunikationskanaele: Zoom, Telefon, E-Mail
|
||||
|
||||
---
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
### Bei festen Terminen (G, H)
|
||||
```
|
||||
Startdatum: [Datum waehlen]
|
||||
Startzeit: [Uhrzeit waehlen]
|
||||
Zoom-Link: [Link einfuegen]
|
||||
```
|
||||
|
||||
### Bei Anfragen (I, J, K, L)
|
||||
```
|
||||
Buchungsart: "Anfrage" waehlen
|
||||
Datum/Uhrzeit: NICHT ausfuellen
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Zoom-Automatisierung
|
||||
|
||||
Mit dem **Zoom Webhook-Modul** werden Online-Kurse automatisch mit Zoom verbunden:
|
||||
|
||||
| Funktion | Beschreibung |
|
||||
|----------|--------------|
|
||||
| Meeting-Status | Kurs wird automatisch "live" wenn Meeting startet |
|
||||
| Anwesenheit | Wer hat teilgenommen? (E-Mail-Abgleich) |
|
||||
| Aufnahmen | Zoom-Aufnahmen automatisch dem Kurs zuweisen |
|
||||
|
||||
### Einrichtung
|
||||
|
||||
1. **Modul aktivieren:** Einstellungen → Module → "Zoom Webhooks"
|
||||
2. **Zoom App erstellen:** [marketplace.zoom.us](https://marketplace.zoom.us/develop/create)
|
||||
3. **Konfigurieren:** Einstellungen → Zoom
|
||||
|
||||
→ **Vollstaendige Anleitung:** [Zoom Webhooks](/topic/zoom)
|
||||
67
content/kursarten/uebersicht.md
Normal file
67
content/kursarten/uebersicht.md
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
title: Kursarten-Uebersicht
|
||||
description: Alle Kursarten A-L im Ueberblick
|
||||
---
|
||||
|
||||
# Kursarten-Uebersicht
|
||||
|
||||
Alle verfuegbaren Kursarten von A bis L im Ueberblick.
|
||||
|
||||
---
|
||||
|
||||
## Praesenz & Hybrid (A-B)
|
||||
|
||||
| ID | Name | Termin | Buchung | Beschreibung |
|
||||
|----|------|--------|---------|--------------|
|
||||
| **A** | Kurse | Fix | Direkt | Prasenzkurse vor Ort |
|
||||
| **B** | Workshops | Fix | Direkt | Online, Vor Ort oder Hybrid |
|
||||
|
||||
---
|
||||
|
||||
## Online Live (C-D, G-H)
|
||||
|
||||
| ID | Name | Termin | Buchung | Beschreibung |
|
||||
|----|------|--------|---------|--------------|
|
||||
| **C** | Webinare | Fix | Direkt | Online per Zoom mit Aufzeichnung |
|
||||
| **D** | Mental Coaching | Fix | Direkt | Gruppencoaching per Zoom |
|
||||
| **G** | Webinar Live | Fix | Direkt | Live-Webinar + Aufzeichnung danach |
|
||||
| **H** | Workshop Online | Fix | Direkt | Interaktiver Online-Workshop |
|
||||
|
||||
---
|
||||
|
||||
## Auf Anfrage (D1, I-L)
|
||||
|
||||
| ID | Name | Termin | Buchung | Beschreibung |
|
||||
|----|------|--------|---------|--------------|
|
||||
| **D1** | Einzel-Coaching | Individuell | Anfrage | 1:1 Coaching |
|
||||
| **I** | Coaching Online | Individuell | Anfrage | Persoenliches Online-Coaching |
|
||||
| **J** | Online Unterricht | Individuell | Anfrage | Live-Reitunterricht per Zoom |
|
||||
| **K** | Video-Analyse | Kein Termin | Anfrage | Feedback zu eingesendeten Videos |
|
||||
| **L** | Beratung | Individuell | Anfrage | Beratung & Begleitung |
|
||||
|
||||
---
|
||||
|
||||
## Video/Digital (E-F)
|
||||
|
||||
| ID | Name | Termin | Buchung | Beschreibung |
|
||||
|----|------|--------|---------|--------------|
|
||||
| **E** | Video-Kurs | Unbegrenzt | Direkt | Aufgezeichneter Video-Kurs |
|
||||
| **F** | Video-Paket | Unbegrenzt | Direkt | Bundle mehrerer Video-Kurse |
|
||||
|
||||
---
|
||||
|
||||
## Schnell-Referenz
|
||||
|
||||
| Situation | Kursart |
|
||||
|-----------|---------|
|
||||
| Mehrtaegiger Kurs vor Ort | A) Kurse |
|
||||
| Workshop mit Zoom-Option | B) Workshops |
|
||||
| Theorie-Vortrag online | C) Webinare oder G) Webinar Live |
|
||||
| Gruppen-Mentaltraining | D) Mental Coaching |
|
||||
| 1:1 Coaching-Session | D1) Einzel-Coaching oder I) Coaching Online |
|
||||
| Interaktiver Online-Workshop | H) Workshop Online |
|
||||
| Live-Reitunterricht via Zoom | J) Online Unterricht |
|
||||
| Kunde schickt Video, du gibst Feedback | K) Video-Analyse |
|
||||
| Laengerfristige Begleitung | L) Beratung |
|
||||
| Aufgezeichnetes Lernvideo verkaufen | E) Video-Kurs |
|
||||
| Mehrere Videos als Bundle | F) Video-Paket |
|
||||
30
content/legal.md
Normal file
30
content/legal.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
id: legal
|
||||
title: Rechtliche Seiten
|
||||
icon: shield-check
|
||||
description: AGB, Datenschutz und Widerruf
|
||||
section: Rechtliches
|
||||
tags: [AGB, Datenschutz, Widerruf, Recht]
|
||||
related: [fields, cancellation]
|
||||
order: 20
|
||||
---
|
||||
|
||||
# Rechtliche Seiten
|
||||
|
||||
Verknuepfen Sie Ihre rechtlichen Seiten fuer das Buchungsformular.
|
||||
|
||||
## Erforderliche Seiten
|
||||
|
||||
| Seite | Beschreibung |
|
||||
|-------|--------------|
|
||||
| **AGB-Seite** | Allgemeine Geschaeftsbedingungen |
|
||||
| **Datenschutz-Seite** | Datenschutzerklaerung (DSGVO) |
|
||||
| **Widerrufsbelehrung** | Fuer Verbraucher (Fernabsatz) |
|
||||
|
||||
## Verwendung
|
||||
|
||||
- Links erscheinen im Buchungsformular
|
||||
- Zustimmungs-Felder verweisen auf diese Seiten
|
||||
- E-Mail-Footer enthaelt Links
|
||||
|
||||
> **Wichtig:** Fuer rechtskonforme Buchungen muessen alle drei Seiten angelegt und verknuepft sein!
|
||||
34
content/masterdata.md
Normal file
34
content/masterdata.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
id: masterdata
|
||||
title: Stammdaten
|
||||
icon: database
|
||||
description: Reitlehrer, Pferde und Orte verwalten
|
||||
section: Grundeinstellungen
|
||||
tags: [Stammdaten, Reitlehrer, Pferde, Orte]
|
||||
related: [general, booking]
|
||||
order: 3
|
||||
---
|
||||
|
||||
# Stammdaten verwalten
|
||||
|
||||
Hier pflegen Sie wiederverwendbare Daten fuer Ihre Kurse.
|
||||
|
||||
## Reitlehrer
|
||||
|
||||
- Name und Qualifikation
|
||||
- Foto (optional)
|
||||
- Beschreibung
|
||||
|
||||
## Pferde
|
||||
|
||||
- Name und Rasse
|
||||
- Foto
|
||||
- Beschreibung/Charakter
|
||||
|
||||
## Orte
|
||||
|
||||
- Bezeichnung
|
||||
- Adresse
|
||||
- Beschreibung/Anfahrt
|
||||
|
||||
> **Tipp:** Stammdaten koennen beim Erstellen eines Kurses einfach ausgewaehlt werden.
|
||||
26
content/modules.md
Normal file
26
content/modules.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
id: modules
|
||||
title: Module
|
||||
icon: puzzle
|
||||
description: Funktionen aktivieren und deaktivieren
|
||||
section: Grundeinstellungen
|
||||
tags: [Module, Features, Aktivierung]
|
||||
related: [general, sevdesk, video, popup-neuigkeiten]
|
||||
order: 2
|
||||
---
|
||||
|
||||
# Module verwalten
|
||||
|
||||
Aktivieren Sie nur die Module, die Sie benoetigen.
|
||||
|
||||
## Verfuegbare Module
|
||||
|
||||
| Modul | Beschreibung | Tab erscheint |
|
||||
|-------|--------------|---------------|
|
||||
| **Stammdaten** | Reitlehrer, Pferde, Orte verwalten | Stammdaten |
|
||||
| **sevDesk** | Automatische Rechnungserstellung | sevDesk |
|
||||
| **Videos** | Video-Kurse und Aufzeichnungen | Video-Service |
|
||||
| **Stornierung** | Stornierungsregeln und -gebuehren | Stornierung |
|
||||
| **Pop-up Neuigkeiten** | Kurse im Pop-up bewerben | Neuigkeiten (Menue) |
|
||||
|
||||
> **Hinweis:** Deaktivierte Module entfernen den entsprechenden Tab aus den Einstellungen.
|
||||
31
content/payments.md
Normal file
31
content/payments.md
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
id: payments
|
||||
title: Zahlungen
|
||||
icon: credit-card
|
||||
description: Zahlungsmethoden und Gateways
|
||||
section: Buchung & Preise
|
||||
tags: [Zahlung, Gateway, Ueberweisung]
|
||||
related: [prices, bank, booking]
|
||||
order: 14
|
||||
---
|
||||
|
||||
# Zahlungen
|
||||
|
||||
## Zahlungsmethoden
|
||||
|
||||
Aktivieren Sie die gewuenschten Zahlungsarten:
|
||||
|
||||
| Methode | Beschreibung |
|
||||
|---------|--------------|
|
||||
| **Vorkasse/Ueberweisung** | Kunde ueberweist vor dem Kurs |
|
||||
| **Barzahlung vor Ort** | Zahlung am Kurstag |
|
||||
| **PayPal** | Online-Zahlung (erfordert Konfiguration) |
|
||||
| **Stripe** | Kreditkarte (erfordert Konfiguration) |
|
||||
|
||||
## Hinweis bei manueller Zahlung
|
||||
|
||||
Text der bei Vorkasse/Barzahlung angezeigt wird, z.B.:
|
||||
|
||||
```
|
||||
"Wird vor Ort verrechnet"
|
||||
```
|
||||
136
content/popup-neuigkeiten.md
Normal file
136
content/popup-neuigkeiten.md
Normal file
@@ -0,0 +1,136 @@
|
||||
---
|
||||
id: popup-neuigkeiten
|
||||
title: Pop-up Neuigkeiten
|
||||
icon: megaphone
|
||||
description: Kurse und Angebote im Pop-up bewerben
|
||||
section: Marketing
|
||||
tags: [Popup, Marketing, Werbung, Neuigkeiten]
|
||||
related: [modules, features, shortcodes]
|
||||
order: 1
|
||||
---
|
||||
|
||||
# Pop-up Neuigkeiten
|
||||
|
||||
Bewerben Sie Ihre Kurse mit einem ansprechenden Pop-up auf der Website.
|
||||
|
||||
## Modul aktivieren
|
||||
|
||||
1. Gehen Sie zu **Kurs Booking > Einstellungen > Module**
|
||||
2. Aktivieren Sie **Pop-up Neuigkeiten**
|
||||
3. Speichern Sie die Einstellungen
|
||||
|
||||
Nach der Aktivierung erscheint im Admin-Menue der neue Punkt **Neuigkeiten**.
|
||||
|
||||
## Neuigkeit erstellen
|
||||
|
||||
### Schritt 1: Neue Neuigkeit anlegen
|
||||
|
||||
1. Klicken Sie auf **Neuigkeiten > Neu hinzufuegen**
|
||||
2. Geben Sie einen internen Titel ein (wird nicht im Frontend angezeigt)
|
||||
3. Waehlen Sie das **Beitragsbild** - dieses erscheint im Pop-up
|
||||
|
||||
### Schritt 2: Pop-up Inhalt
|
||||
|
||||
| Feld | Beschreibung |
|
||||
|------|--------------|
|
||||
| **Ueberschrift** | Titel im Pop-up (z.B. "Neuer Kurs!") |
|
||||
| **Beschreibung** | Kurzer Werbetext (2-3 Saetze) |
|
||||
| **Button-Text** | Text des CTA-Buttons (Standard: "Mehr erfahren") |
|
||||
| **Button-Link** | Optionale URL (siehe unten) |
|
||||
|
||||
### Button-Link Verhalten
|
||||
|
||||
| Einstellung | Wohin fuehrt der Button? |
|
||||
|-------------|--------------------------|
|
||||
| **Leer lassen** | Automatisch zur Kurs-Seite des angezeigten Kurses |
|
||||
| **URL eintragen** | Zur eingetragenen URL (z.B. Uebersichtsseite) |
|
||||
|
||||
> **Tipp:** Lassen Sie das Feld leer, wenn der Button direkt zum beworbenen Kurs fuehren soll. Tragen Sie eine URL ein, wenn Sie auf eine Uebersichtsseite (z.B. alle Webinare) verlinken moechten.
|
||||
|
||||
### Schritt 3: Kurse auswaehlen
|
||||
|
||||
Es gibt **4 Auswahlmodi**:
|
||||
|
||||
| Modus | Beschreibung |
|
||||
|-------|--------------|
|
||||
| **Einzelne Kurse** | Manuell bestimmte Kurse auswaehlen |
|
||||
| **Nach Kategorien** | Alle Kurse einer Kategorie |
|
||||
| **Nach Produktarten** | Alle Kurse einer Produktart (z.B. Webinare) |
|
||||
| **Alle Kurse** | Zufaellig aus allen Kursen |
|
||||
|
||||
> **Tipp:** Bei mehreren Kursen wird bei jedem Seitenaufruf ein **zufaelliger Kurs** angezeigt.
|
||||
|
||||
### Was wird zum Kurs angezeigt?
|
||||
|
||||
Das Pop-up zeigt automatisch diese Informationen zum gewaehlten Kurs:
|
||||
|
||||
| Information | Anzeige |
|
||||
|-------------|---------|
|
||||
| **Kurs-Titel** | Name des Kurses |
|
||||
| **Startdatum** | z.B. "15.01.2025" (falls vorhanden) |
|
||||
| **Preis** | z.B. "ab 150 EUR" (falls vorhanden) |
|
||||
| **Bild** | Beitragsbild des Kurses (falls kein Neuigkeits-Bild) |
|
||||
|
||||
### Schritt 4: Zeitsteuerung
|
||||
|
||||
| Option | Beschreibung |
|
||||
|--------|--------------|
|
||||
| **Startdatum** | Ab wann das Pop-up erscheint |
|
||||
| **Enddatum** | Bis wann das Pop-up erscheint |
|
||||
|
||||
Lassen Sie beide Felder leer fuer unbegrenzte Anzeige.
|
||||
|
||||
## Einstellungen
|
||||
|
||||
Im Modul-Tab finden Sie weitere Optionen:
|
||||
|
||||
| Einstellung | Beschreibung | Standard |
|
||||
|-------------|--------------|----------|
|
||||
| **Verzoegerung** | Sekunden bis Pop-up erscheint | 3 Sekunden |
|
||||
| **Cookie-Dauer** | Tage bis Pop-up erneut erscheint | 1 Tag |
|
||||
|
||||
## Statistiken
|
||||
|
||||
Jede Neuigkeit zeigt Statistiken:
|
||||
|
||||
| Statistik | Beschreibung |
|
||||
|-----------|--------------|
|
||||
| **Aufrufe** | Wie oft das Pop-up angezeigt wurde |
|
||||
| **Klicks** | Wie oft auf den Button geklickt wurde |
|
||||
| **Geschlossen** | Wie oft das X geklickt wurde |
|
||||
| **Klickrate** | Prozent der Klicks pro Aufruf |
|
||||
|
||||
## Design
|
||||
|
||||
Das Pop-up passt sich automatisch an Ihr Theme an:
|
||||
|
||||
- **Desktop:** Zentriertes Pop-up mit Overlay
|
||||
- **Mobil:** Vollbild-Ansicht fuer beste Lesbarkeit
|
||||
- **Schliessen:** X-Button oder Klick auf Overlay
|
||||
|
||||
## Mehrere Neuigkeiten
|
||||
|
||||
Sie koennen mehrere Neuigkeiten gleichzeitig aktiv haben. Das System zeigt automatisch eine **zufaellige aktive Neuigkeit** pro Besucher.
|
||||
|
||||
## Beispiel-Workflow
|
||||
|
||||
1. **Neuer Kurs:** Erstellen Sie eine Neuigkeit fuer den neuen Kurs
|
||||
2. **Saisonales Angebot:** Zeitlich begrenzte Neuigkeit (z.B. Fruehbucher)
|
||||
3. **Kategorie-Werbung:** Alle Webinare bewerben
|
||||
|
||||
## Haeufige Fragen
|
||||
|
||||
### Pop-up erscheint nicht?
|
||||
|
||||
- Pruefen Sie ob das **Modul aktiviert** ist
|
||||
- Pruefen Sie das **Start-/Enddatum**
|
||||
- Pruefen Sie ob mindestens ein **Kurs ausgewaehlt** ist
|
||||
- Loeschen Sie Ihre **Browser-Cookies** (Cookie-Sperre)
|
||||
|
||||
### Pop-up erscheint zu oft?
|
||||
|
||||
Erhoehen Sie die **Cookie-Dauer** in den Einstellungen.
|
||||
|
||||
### Anderes Design gewuenscht?
|
||||
|
||||
Das Pop-up verwendet die CSS-Klasse `.kb-news-popup`. Fuer Anpassungen fuegen Sie Custom CSS in Ihrem Theme hinzu.
|
||||
47
content/portal.md
Normal file
47
content/portal.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
id: portal
|
||||
title: Teilnehmer-Portal
|
||||
icon: person-circle
|
||||
description: Self-Service fuer Teilnehmer
|
||||
section: Erweitert
|
||||
tags: [Portal, Self-Service, Account, Login]
|
||||
related: [booking, video]
|
||||
order: 53
|
||||
---
|
||||
|
||||
# Teilnehmer-Portal
|
||||
|
||||
Ein optionaler Bereich fuer angemeldete Teilnehmer.
|
||||
|
||||
## Funktionen
|
||||
|
||||
| Funktion | Beschreibung |
|
||||
|----------|--------------|
|
||||
| **Meine Buchungen** | Uebersicht aller Buchungen |
|
||||
| **Video-Zugang** | Gebuchte Videos ansehen |
|
||||
| **Daten aendern** | Kontaktdaten aktualisieren |
|
||||
| **Stornierung** | Buchungen selbst stornieren |
|
||||
|
||||
## Portal einrichten
|
||||
|
||||
1. **Seite erstellen** mit Shortcode `[kurs_portal]`
|
||||
2. **Zugang aktivieren** in den Einstellungen
|
||||
3. **E-Mail-Template** fuer Portal-Registrierung einrichten
|
||||
|
||||
## Portal-Seiten
|
||||
|
||||
| Shortcode | Funktion |
|
||||
|-----------|----------|
|
||||
| `[kurs_portal]` | Hauptportal mit Buchungsuebersicht |
|
||||
| `[kurs_video_access]` | Video-Zugangsbereich |
|
||||
| `[kurs_booking_status]` | Buchungsstatus anzeigen |
|
||||
|
||||
## Zugang
|
||||
|
||||
Teilnehmer erhalten nach der Buchung automatisch:
|
||||
|
||||
- Login-Link per E-Mail
|
||||
- Oder: Magic-Link ohne Passwort
|
||||
- Oder: WordPress-Benutzer-Account
|
||||
|
||||
> **Hinweis:** Das Portal ist optional. Alle wichtigen Infos werden auch per E-Mail versendet.
|
||||
266
content/preisvarianten.md
Normal file
266
content/preisvarianten.md
Normal file
@@ -0,0 +1,266 @@
|
||||
---
|
||||
id: preisvarianten
|
||||
title: Preisvarianten
|
||||
icon: currency-euro
|
||||
description: Flexible Preisgestaltung mit mehreren Varianten
|
||||
section: Buchung & Preise
|
||||
tags: [Preise, Varianten, Tickets, Rabatte]
|
||||
related: [prices, booking, dienstleistungen]
|
||||
order: 14
|
||||
---
|
||||
|
||||
# Preisvarianten
|
||||
|
||||
Bieten Sie verschiedene Preisoptionen fuer Ihre Kurse an.
|
||||
|
||||
---
|
||||
|
||||
## Was sind Preisvarianten?
|
||||
|
||||
Preisvarianten ermoeglichen verschiedene Ticket-Typen pro Kurs:
|
||||
|
||||
- Fruehbucher-Rabatt
|
||||
- Normalpreis
|
||||
- Wiederholer-Rabatt
|
||||
- VIP mit Extras
|
||||
- Ermaessigt (Studenten, Senioren)
|
||||
|
||||
---
|
||||
|
||||
## Variante erstellen
|
||||
|
||||
### Im Kurs-Editor
|
||||
|
||||
1. Kurs bearbeiten oeffnen
|
||||
2. Scrollen zu **Preisvarianten**
|
||||
3. Klicken Sie **Variante hinzufuegen**
|
||||
4. Felder ausfuellen:
|
||||
|
||||
| Feld | Beschreibung |
|
||||
|------|--------------|
|
||||
| **Bezeichnung** | z.B. "Fruehbucher" |
|
||||
| **Preis** | Betrag in EUR |
|
||||
| **Beschreibung** | Kurze Info (optional) |
|
||||
| **Verfuegbar bis** | Datum (optional) |
|
||||
| **Max. Anzahl** | Kontingent (optional) |
|
||||
|
||||
5. Speichern
|
||||
|
||||
---
|
||||
|
||||
## Varianten-Optionen
|
||||
|
||||
### Pro Tag berechnen
|
||||
|
||||
Aktivieren Sie diese Option, wenn der Preis pro Kurstag gilt:
|
||||
|
||||
```
|
||||
Kurs: 3 Tage
|
||||
Variante: Gastbox (60 EUR/Tag)
|
||||
Berechnung: 3 × 60 = 180 EUR
|
||||
```
|
||||
|
||||
**Anwendung:** Gastbox, Verpflegung, Leihausruestung
|
||||
|
||||
### Automatisch verrechnen
|
||||
|
||||
Die Variante wird automatisch auf die sevDesk-Rechnung uebernommen.
|
||||
|
||||
| Aktiviert | Verhalten |
|
||||
|-----------|-----------|
|
||||
| Ja | Erscheint auf Rechnung |
|
||||
| Nein | Nur Info, Zahlung vor Ort |
|
||||
|
||||
### Hinweistext
|
||||
|
||||
Wird im Buchungsformular angezeigt:
|
||||
|
||||
```
|
||||
"Wird bei Kursstart bar bezahlt"
|
||||
"Leihausruestung bitte reservieren"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Beispiel-Konfiguration
|
||||
|
||||
### Reit-Kurs (3 Tage)
|
||||
|
||||
| Variante | Preis | Pro Tag | Auto-Rechnung | Hinweis |
|
||||
|----------|-------|---------|---------------|---------|
|
||||
| Kurs-Teilnahme | 350 EUR | Nein | Ja | - |
|
||||
| Fruehbucher | 299 EUR | Nein | Ja | Bis 30 Tage vorher |
|
||||
| Wiederholer | 280 EUR | Nein | Ja | - |
|
||||
| Gastbox | 60 EUR | Ja | Nein | Zahlung vor Ort |
|
||||
| Leihpferd | 50 EUR | Ja | Nein | Nur mit Reservierung |
|
||||
| Verpflegung | 25 EUR | Ja | Nein | Fruehstueck inkl. |
|
||||
|
||||
### Buchungs-Beispiel
|
||||
|
||||
```
|
||||
Kunde waehlt:
|
||||
- Kurs-Teilnahme: 350 EUR (auf Rechnung)
|
||||
- Gastbox 3 Tage: 180 EUR (vor Ort)
|
||||
- Leihpferd: 150 EUR (vor Ort)
|
||||
────────────────────────────────────────
|
||||
Gesamt: 680 EUR
|
||||
Davon Rechnung: 350 EUR
|
||||
Vor Ort: 330 EUR
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Preisanzeige im Frontend
|
||||
|
||||
### Einzelpreis
|
||||
|
||||
Wenn nur eine Variante:
|
||||
```
|
||||
350 EUR
|
||||
```
|
||||
|
||||
### Preisspanne
|
||||
|
||||
Bei mehreren Varianten:
|
||||
```
|
||||
ab 299 EUR
|
||||
```
|
||||
|
||||
### Mit Zusatzoptionen
|
||||
|
||||
```
|
||||
Kurs-Teilnahme: 350 EUR
|
||||
+ Gastbox: 60 EUR/Tag
|
||||
+ Leihpferd: 50 EUR/Tag
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Kontingent-Verwaltung
|
||||
|
||||
### Begrenzte Varianten
|
||||
|
||||
Setzen Sie "Max. Anzahl" fuer limitierte Angebote:
|
||||
|
||||
| Variante | Max | Gebucht | Verfuegbar |
|
||||
|----------|-----|---------|------------|
|
||||
| Fruehbucher | 10 | 8 | 2 |
|
||||
| VIP | 5 | 5 | Ausverkauft |
|
||||
|
||||
### Anzeige bei Ausverkauft
|
||||
|
||||
- Variante wird ausgegraut
|
||||
- "Ausverkauft" Badge
|
||||
- Andere Varianten bleiben waehlbar
|
||||
|
||||
---
|
||||
|
||||
## Zeitgesteuerte Varianten
|
||||
|
||||
### Fruehbucher-Logik
|
||||
|
||||
**Verfuegbar bis:** 30 Tage vor Kursbeginn
|
||||
|
||||
```
|
||||
Kurs: 01.04.2025
|
||||
Fruehbucher bis: 02.03.2025
|
||||
|
||||
Nach 02.03.: Variante nicht mehr waehlbar
|
||||
```
|
||||
|
||||
### Spaetbucher-Aufpreis
|
||||
|
||||
Erstellen Sie eine "Last-Minute" Variante mit hoeherem Preis, die erst kurz vor Kursbeginn erscheint.
|
||||
|
||||
---
|
||||
|
||||
## Varianten im Buchungsformular
|
||||
|
||||
### Schritt 1: Auswahl
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Waehlen Sie Ihre Tickets: │
|
||||
├─────────────────────────────────────┤
|
||||
│ ○ Kurs-Teilnahme 350 EUR │
|
||||
│ ○ Fruehbucher (bis 02.03) 299 EUR │
|
||||
│ ○ Wiederholer 280 EUR │
|
||||
├─────────────────────────────────────┤
|
||||
│ Zusatzoptionen: │
|
||||
│ □ Gastbox 60 EUR/Tag │
|
||||
│ □ Leihpferd 50 EUR/Tag │
|
||||
│ □ Verpflegung 25 EUR/Tag │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Preis-Berechnung (Live)
|
||||
|
||||
Bei Aenderung der Auswahl wird der Gesamtpreis sofort aktualisiert.
|
||||
|
||||
---
|
||||
|
||||
## Rabatte kombinieren
|
||||
|
||||
### Erlaubt
|
||||
|
||||
- Fruehbucher + Zusatzoptionen
|
||||
- Wiederholer + Gastbox
|
||||
|
||||
### Nicht erlaubt
|
||||
|
||||
- Fruehbucher + Wiederholer (nur eine Basis-Variante)
|
||||
|
||||
---
|
||||
|
||||
## sevDesk-Integration
|
||||
|
||||
### Automatische Positionen
|
||||
|
||||
Bei aktiviertem "Auto-Rechnung":
|
||||
|
||||
```
|
||||
Rechnung #2025-001
|
||||
─────────────────────────────────────
|
||||
Kurs-Teilnahme "Anfaengerkurs" 350,00
|
||||
─────────────────────────────────────
|
||||
Summe netto: 291,67
|
||||
MwSt 20%: 58,33
|
||||
Gesamtbetrag: 350,00
|
||||
```
|
||||
|
||||
### Manuelle Positionen
|
||||
|
||||
Varianten ohne "Auto-Rechnung" erscheinen nicht auf der Rechnung und werden separat verrechnet.
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Klare Bezeichnungen
|
||||
|
||||
| Gut | Schlecht |
|
||||
|-----|----------|
|
||||
| Fruehbucher (-15%) | Variante 1 |
|
||||
| Normalpreis | Standard |
|
||||
| Wiederholer-Rabatt | Rabatt |
|
||||
|
||||
### Preis-Psychologie
|
||||
|
||||
- Fruehbucher als "Ersparnis" darstellen
|
||||
- Streichpreis anzeigen
|
||||
- Limitierung kommunizieren ("Nur noch 3 Plaetze")
|
||||
|
||||
### Uebersichtlichkeit
|
||||
|
||||
- Max. 3-4 Basis-Varianten
|
||||
- Zusatzoptionen separat gruppieren
|
||||
- Beschreibungen kurz halten
|
||||
|
||||
---
|
||||
|
||||
## Siehe auch
|
||||
|
||||
- [Preise & MwSt](/topic/prices)
|
||||
- [Buchungseinstellungen](/topic/booking)
|
||||
- [sevDesk Integration](/topic/sevdesk)
|
||||
- [Produktarten](/topic/dienstleistungen)
|
||||
36
content/prices.md
Normal file
36
content/prices.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
id: prices
|
||||
title: Preise & MwSt
|
||||
icon: currency-euro
|
||||
description: Waehrung, Steuern und Preisanzeige
|
||||
section: Buchung & Preise
|
||||
tags: [Preise, MwSt, Waehrung, Steuern]
|
||||
related: [booking, bank]
|
||||
order: 13
|
||||
---
|
||||
|
||||
# Preise & MwSt
|
||||
|
||||
## Preisanzeige
|
||||
|
||||
| Einstellung | Beschreibung |
|
||||
|-------------|--------------|
|
||||
| **Brutto/Netto** | Wie werden Preise angezeigt? (Privatkunden: Brutto) |
|
||||
| **Waehrungssymbol** | EUR, CHF, etc. |
|
||||
| **Position** | Symbol vor oder nach dem Betrag |
|
||||
| **Dezimalstellen** | Standard: 2 (z.B. 99,00) |
|
||||
|
||||
## Mehrwertsteuer
|
||||
|
||||
| Einstellung | Beschreibung |
|
||||
|-------------|--------------|
|
||||
| **MwSt-Satz** | Standard: 19% (DE) bzw. 20% (AT) |
|
||||
| **Kleinunternehmer** | Aktivieren fuer §19 UStG (keine MwSt-Ausweisung) |
|
||||
|
||||
## Anzahlung
|
||||
|
||||
| Einstellung | Beschreibung |
|
||||
|-------------|--------------|
|
||||
| **Standard-Anzahlung** | Prozentsatz der bei Buchung faellig ist (0 = voller Betrag) |
|
||||
|
||||
> **Kleinunternehmer:** Bei aktivierter Option wird keine MwSt berechnet und der Hinweis "§19 UStG" erscheint auf Rechnungen.
|
||||
46
content/produktarten-filter.md
Normal file
46
content/produktarten-filter.md
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
id: produktarten-filter
|
||||
title: Produktarten-Filter
|
||||
icon: funnel
|
||||
description: Felder je nach Produktart ein-/ausblenden
|
||||
section: Erweitert
|
||||
tags: [Filter, Produktarten, Felder, Logik]
|
||||
related: [dienstleistungen, fields, feldtypen]
|
||||
order: 51
|
||||
---
|
||||
|
||||
# Produktarten-Filter
|
||||
|
||||
Steuern Sie, welche Felder bei welcher Produktart erscheinen.
|
||||
|
||||
## Funktionsweise
|
||||
|
||||
Jedes Buchungsfeld kann fuer bestimmte Produktarten aktiviert oder deaktiviert werden:
|
||||
|
||||
| Feld | A | B | C | D | D1 | E |
|
||||
|------|---|---|---|---|----|----|
|
||||
| Veranstaltungsort | ✓ | ✓ | - | - | - | - |
|
||||
| Zoom-Link | - | ✓ | ✓ | ✓ | ✓ | - |
|
||||
| Video-Zugang | - | Opt. | ✓ | - | - | ✓ |
|
||||
| Termin-Wunsch | - | - | - | - | ✓ | - |
|
||||
|
||||
## Filter einrichten
|
||||
|
||||
1. Gehen Sie zu **Buchungsfelder**
|
||||
2. Bearbeiten Sie ein Feld
|
||||
3. Aktivieren Sie unter **Produktarten** die gewuenschten Typen
|
||||
4. Speichern Sie die Aenderungen
|
||||
|
||||
## Beispiele
|
||||
|
||||
### Praesenzkurs (Typ A)
|
||||
- Veranstaltungsort: Aktiv
|
||||
- Zoom-Link: Inaktiv
|
||||
- Video-Zugang: Inaktiv
|
||||
|
||||
### Online-Webinar (Typ C)
|
||||
- Veranstaltungsort: Inaktiv
|
||||
- Zoom-Link: Aktiv
|
||||
- Video-Zugang: Aktiv
|
||||
|
||||
> **Hinweis:** Felder ohne Produktarten-Zuweisung erscheinen bei ALLEN Produktarten.
|
||||
178
content/schnellstart.md
Normal file
178
content/schnellstart.md
Normal file
@@ -0,0 +1,178 @@
|
||||
---
|
||||
id: schnellstart
|
||||
title: Schnellstart
|
||||
icon: rocket-takeoff
|
||||
description: In 10 Minuten zum ersten Kurs mit Buchungsfunktion
|
||||
section: Grundeinstellungen
|
||||
tags: [Einstieg, Tutorial, Anfaenger, Setup]
|
||||
related: [general, booking, prices, emails]
|
||||
order: 1
|
||||
---
|
||||
|
||||
# Schnellstart
|
||||
|
||||
Diese Anleitung fuehrt Sie in 10 Minuten durch die Grundeinrichtung.
|
||||
|
||||
---
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- WordPress 6.0 oder hoeher
|
||||
- PHP 8.0 oder hoeher
|
||||
- Kurs-Booking Plugin aktiviert
|
||||
|
||||
---
|
||||
|
||||
## Schritt 1: Firmendaten eintragen
|
||||
|
||||
**Pfad:** Kurs-Booking > Einstellungen > Allgemein
|
||||
|
||||
| Feld | Eingabe |
|
||||
|------|---------|
|
||||
| Firmenname | Ihr Unternehmensname |
|
||||
| E-Mail | kontakt@ihre-domain.de |
|
||||
| Telefon | +43 123 456789 |
|
||||
| Adresse | Strasse, PLZ Ort |
|
||||
| Logo | Hochladen (PNG/JPG, mind. 200px) |
|
||||
|
||||
**Speichern** klicken.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 2: Preise & MwSt konfigurieren
|
||||
|
||||
**Pfad:** Kurs-Booking > Einstellungen > Preise & MwSt
|
||||
|
||||
| Feld | Empfehlung |
|
||||
|------|------------|
|
||||
| Waehrung | EUR |
|
||||
| MwSt-Satz | 20% (AT) oder 19% (DE) |
|
||||
| Kleinunternehmer | Ja, wenn unter Umsatzgrenze |
|
||||
| Preisanzeige | Brutto (inkl. MwSt) |
|
||||
|
||||
**Speichern** klicken.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 3: E-Mail-Absender einstellen
|
||||
|
||||
**Pfad:** Kurs-Booking > Einstellungen > E-Mails
|
||||
|
||||
| Feld | Eingabe |
|
||||
|------|---------|
|
||||
| Absender-Name | Ihr Firmenname |
|
||||
| Absender-E-Mail | noreply@ihre-domain.de |
|
||||
| Admin-E-Mail | ihre@email.de |
|
||||
|
||||
**Speichern** klicken.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 4: Ersten Kurs erstellen
|
||||
|
||||
**Pfad:** Kurse > Neu hinzufuegen
|
||||
|
||||
### Basis-Informationen
|
||||
|
||||
| Feld | Eingabe |
|
||||
|------|---------|
|
||||
| Titel | z.B. "Anfaengerkurs Reiten" |
|
||||
| Beschreibung | Kursbeschreibung im Editor |
|
||||
| Beitragsbild | Attraktives Foto hochladen |
|
||||
|
||||
### Termin & Ort
|
||||
|
||||
| Feld | Eingabe |
|
||||
|------|---------|
|
||||
| Startdatum | Waehlen Sie ein Datum |
|
||||
| Enddatum | Bei mehrtaegigen Kursen |
|
||||
| Startzeit | z.B. 09:00 |
|
||||
| Endzeit | z.B. 17:00 |
|
||||
| Veranstaltungsort | Adresse oder Online |
|
||||
|
||||
### Buchungsoptionen
|
||||
|
||||
| Feld | Eingabe |
|
||||
|------|---------|
|
||||
| Max. Teilnehmer | z.B. 10 |
|
||||
| Produktart | A) Praesenz-Kurs |
|
||||
| Preis | z.B. 350 EUR |
|
||||
|
||||
### Veroeffentlichen
|
||||
|
||||
1. Waehlen Sie die passende **Kategorie**
|
||||
2. Klicken Sie **Veroeffentlichen**
|
||||
|
||||
---
|
||||
|
||||
## Schritt 5: Buchungsseite testen
|
||||
|
||||
1. Oeffnen Sie den Kurs im Frontend
|
||||
2. Klicken Sie auf **Jetzt buchen**
|
||||
3. Durchlaufen Sie den 3-Schritt-Prozess:
|
||||
- Tickets waehlen
|
||||
- Daten eingeben
|
||||
- Bestaetigen
|
||||
|
||||
> **Tipp:** Nutzen Sie Ihre eigene E-Mail zum Testen.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 6: Buchung pruefen
|
||||
|
||||
**Pfad:** Buchungen > Alle Buchungen
|
||||
|
||||
Hier sehen Sie die Testbuchung mit Status "Ausstehend".
|
||||
|
||||
### Buchung bestaetigen
|
||||
|
||||
1. Pruefen Sie Ihre E-Mail fuer den Bestaetigungslink
|
||||
2. Klicken Sie den Link in der E-Mail
|
||||
3. Status wechselt zu "Bestaetigt"
|
||||
|
||||
---
|
||||
|
||||
## Geschafft!
|
||||
|
||||
Ihr Buchungssystem ist einsatzbereit.
|
||||
|
||||
### Naechste Schritte
|
||||
|
||||
| Aufgabe | Hilfe-Seite |
|
||||
|---------|-------------|
|
||||
| E-Mail-Vorlagen anpassen | [E-Mail Vorlagen](/topic/email_templates) |
|
||||
| Stornierungsregeln festlegen | [Stornierung](/topic/cancellation) |
|
||||
| Weitere Produktarten nutzen | [Produktarten](/topic/dienstleistungen) |
|
||||
| sevDesk verbinden | [sevDesk Integration](/topic/sevdesk) |
|
||||
| Spam-Schutz aktivieren | [Spam-Schutz](/topic/spam) |
|
||||
|
||||
---
|
||||
|
||||
## Checkliste
|
||||
|
||||
- [ ] Firmendaten eingetragen
|
||||
- [ ] MwSt/Preise konfiguriert
|
||||
- [ ] E-Mail-Absender gesetzt
|
||||
- [ ] Erster Kurs erstellt
|
||||
- [ ] Testbuchung durchgefuehrt
|
||||
- [ ] Bestaetigungs-E-Mail erhalten
|
||||
- [ ] Buchung im Admin geprueft
|
||||
|
||||
---
|
||||
|
||||
## Probleme?
|
||||
|
||||
Wenn etwas nicht funktioniert:
|
||||
|
||||
1. Pruefen Sie die [Fehlerbehebung](/topic/troubleshooting)
|
||||
2. Stellen Sie sicher, dass alle Pflichtfelder ausgefuellt sind
|
||||
3. Pruefen Sie den Spam-Ordner fuer E-Mails
|
||||
|
||||
---
|
||||
|
||||
## Siehe auch
|
||||
|
||||
- [Allgemeine Einstellungen](/topic/general)
|
||||
- [Buchungseinstellungen](/topic/booking)
|
||||
- [E-Mail System](/topic/emails)
|
||||
- [Best Practices](/topic/best-practices)
|
||||
96
content/sevdesk.md
Normal file
96
content/sevdesk.md
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
id: sevdesk
|
||||
title: sevDesk Integration
|
||||
icon: cloud-arrow-up
|
||||
description: Buchhaltungs-Anbindung an sevDesk
|
||||
section: Integrationen
|
||||
tags: [sevDesk, Buchhaltung, Rechnungen, API, E-Rechnung]
|
||||
related: [prices, booking, e-rechnung]
|
||||
order: 40
|
||||
---
|
||||
|
||||
# sevDesk Integration
|
||||
|
||||
## Verbindung einrichten
|
||||
|
||||
1. **API-Token erstellen** in sevDesk unter Einstellungen > API
|
||||
2. **Token einfuegen** in den Plugin-Einstellungen
|
||||
3. **Verbindung testen** mit dem Test-Button
|
||||
|
||||
## Automatische Synchronisation
|
||||
|
||||
| Funktion | Beschreibung |
|
||||
|----------|--------------|
|
||||
| **Kontakte** | Teilnehmer werden als sevDesk-Kontakte angelegt |
|
||||
| **Rechnungen** | Buchungen werden als Rechnungen uebertragen |
|
||||
| **Positionen** | Kursdetails als Rechnungspositionen |
|
||||
|
||||
## Einstellungen
|
||||
|
||||
| Option | Beschreibung |
|
||||
|--------|--------------|
|
||||
| **Auto-Sync** | Automatische Uebertragung bei neuer Buchung |
|
||||
| **Rechnungsstatus** | Entwurf oder Finalisiert |
|
||||
| **Zahlungsziel** | Tage bis Faelligkeit (Standard: 14) |
|
||||
| **Waehrung** | EUR, CHF, GBP oder USD |
|
||||
| **Kundenreferenz** | Template mit Platzhaltern (z.B. `{buchungs_nummer}`) |
|
||||
|
||||
## Rechnungsnummern konfigurieren
|
||||
|
||||
**Wichtig:** Die Rechnungsnummern werden direkt in sevDesk verwaltet - nicht im Plugin!
|
||||
|
||||
### Einrichtung in sevDesk
|
||||
|
||||
1. Melden Sie sich bei **sevDesk** an
|
||||
2. Gehen Sie zu **Einstellungen > Nummernkreise > Rechnungen**
|
||||
3. Konfigurieren Sie:
|
||||
- **Praefix:** z.B. `RE-`
|
||||
- **Format:** z.B. `{PRAEFIX}{JAHR}-{NUMMER}`
|
||||
- **Startnummer:** z.B. `1`
|
||||
|
||||
### Empfohlenes Format
|
||||
|
||||
Fuer automatischen Jahreswechsel ohne manuelles Zuruecksetzen:
|
||||
|
||||
```
|
||||
RE-2025-0001
|
||||
RE-2025-0002
|
||||
...
|
||||
RE-2026-0001 (automatisch neues Jahr)
|
||||
```
|
||||
|
||||
### Jahreswechsel
|
||||
|
||||
| Option | Beschreibung |
|
||||
|--------|--------------|
|
||||
| **Mit Jahreszahl im Format** | Kein manuelles Zuruecksetzen noetig (empfohlen) |
|
||||
| **Ohne Jahreszahl** | Am 01.01. manuell in sevDesk auf 1 zuruecksetzen |
|
||||
|
||||
> **Tipp:** Das Format mit Jahreszahl ist zuverlaessiger und wird von den meisten Steuerberatern empfohlen.
|
||||
|
||||
## Teilrechnungen / Anzahlungen
|
||||
|
||||
Bei Buchungen mit Anzahlung werden zwei Rechnungen erstellt:
|
||||
|
||||
1. **Anzahlungsrechnung** - Sofort bei Buchung
|
||||
2. **Restrechnung** - Bei Kursabschluss oder manuell
|
||||
|
||||
## E-Rechnung (ZUGFeRD)
|
||||
|
||||
Ab 2025 Pflicht fuer B2B in Deutschland. Aktivieren Sie E-Rechnungen im ZUGFeRD-Format:
|
||||
|
||||
1. Gehen Sie zu **Kurse > Einstellungen > sevDesk**
|
||||
2. Aktivieren Sie **"E-Rechnungen im ZUGFeRD-Format erstellen"**
|
||||
|
||||
Die PDF-Rechnung sieht normal aus, enthaelt aber eingebettetes XML fuer automatische Verarbeitung.
|
||||
|
||||
**Voraussetzungen in sevDesk:**
|
||||
- Vollstaendige Firmenadresse
|
||||
- IBAN und Bank hinterlegt
|
||||
- Steuernummer oder USt-IdNr.
|
||||
|
||||
> Ausfuehrliche Dokumentation: [E-Rechnung (ZUGFeRD/XRechnung)](#/topic/e-rechnung)
|
||||
|
||||
---
|
||||
|
||||
> **Wichtig:** Die sevDesk-Integration erfordert einen sevDesk-Account mit API-Zugang.
|
||||
50
content/sevdesk/rechnungsnummern.md
Normal file
50
content/sevdesk/rechnungsnummern.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Rechnungsnummern konfigurieren
|
||||
icon: hash
|
||||
description: Rechnungsnummern-Format und Jahreswechsel in sevDesk einrichten
|
||||
section: Integrationen
|
||||
tags: [sevDesk, Rechnungsnummern, Jahreswechsel, Nummernkreise]
|
||||
order: 10
|
||||
---
|
||||
|
||||
# Rechnungsnummern konfigurieren
|
||||
|
||||
Die Rechnungsnummern werden direkt in sevDesk verwaltet - nicht im Plugin!
|
||||
|
||||
## Einrichtung in sevDesk
|
||||
|
||||
1. Melden Sie sich bei **sevDesk** an
|
||||
2. Gehen Sie zu **Einstellungen > Nummernkreise > Rechnungen**
|
||||
3. Konfigurieren Sie:
|
||||
- **Praefix:** z.B. `RE-`
|
||||
- **Format:** z.B. `{PRAEFIX}{JAHR}-{NUMMER}`
|
||||
- **Startnummer:** z.B. `1`
|
||||
|
||||
## Empfohlenes Format
|
||||
|
||||
Fuer automatischen Jahreswechsel ohne manuelles Zuruecksetzen:
|
||||
|
||||
```
|
||||
RE-2025-0001
|
||||
RE-2025-0002
|
||||
...
|
||||
RE-2026-0001 (automatisch neues Jahr)
|
||||
```
|
||||
|
||||
## Jahreswechsel
|
||||
|
||||
| Option | Beschreibung |
|
||||
|--------|--------------|
|
||||
| **Mit Jahreszahl im Format** | Kein manuelles Zuruecksetzen noetig (empfohlen) |
|
||||
| **Ohne Jahreszahl** | Am 01.01. manuell in sevDesk auf 1 zuruecksetzen |
|
||||
|
||||
> **Tipp:** Das Format mit Jahreszahl ist zuverlaessiger und wird von den meisten Steuerberatern empfohlen.
|
||||
|
||||
## Warum nicht im Plugin?
|
||||
|
||||
Das Kurs-Booking Plugin sendet **keine** Rechnungsnummer an sevDesk. sevDesk vergibt die Nummer automatisch basierend auf den Nummernkreis-Einstellungen.
|
||||
|
||||
**Vorteile:**
|
||||
- Keine Duplikate bei Fehlern
|
||||
- Keine Luecken bei abgebrochenen Buchungen
|
||||
- Zentrale Verwaltung in sevDesk
|
||||
361
content/shortcodes.md
Normal file
361
content/shortcodes.md
Normal file
@@ -0,0 +1,361 @@
|
||||
---
|
||||
id: shortcodes
|
||||
title: Shortcodes
|
||||
icon: code-slash
|
||||
description: Alle verfuegbaren Shortcodes fuer Frontend und Templates
|
||||
section: Tipps & Support
|
||||
tags: [Shortcodes, Frontend, Templates, Kadence]
|
||||
related: [features, kursarten/uebersicht]
|
||||
order: 63
|
||||
---
|
||||
|
||||
# Shortcodes
|
||||
|
||||
Alle verfuegbaren Shortcodes fuer das Kurs-Booking Plugin.
|
||||
|
||||
---
|
||||
|
||||
## Uebersicht
|
||||
|
||||
| Shortcode | Beschreibung |
|
||||
|-----------|--------------|
|
||||
| `[kurs_cards]` | Kurs-Cards Grid mit Filter |
|
||||
| `[kurs_booking_form]` | Buchungsformular |
|
||||
| `[kurs_booking_button]` | Buchungs-Button |
|
||||
| `[kurs_booking_status]` | Buchungsstatus-Meldungen |
|
||||
| `[kurs_field]` | Einzelnes Kurs-Feld ausgeben |
|
||||
| `[kurs_video_access]` | Video-Zugangsseite |
|
||||
| `[kurs_video]` | Video-Player |
|
||||
| `[video_pakete]` | Video-Pakete Uebersicht |
|
||||
| `[video_paket]` | Einzelnes Video-Paket |
|
||||
|
||||
---
|
||||
|
||||
## [kurs_cards]
|
||||
|
||||
Zeigt Veranstaltungen als responsive Card-Grid mit optionalem Filter.
|
||||
|
||||
### Attribute
|
||||
|
||||
| Attribut | Standard | Beschreibung |
|
||||
|----------|----------|--------------|
|
||||
| `show_filter` | `true` | Filter anzeigen (Kategorie, Monat) |
|
||||
| `columns` | `3` | Anzahl Spalten (2, 3 oder 4) |
|
||||
| `limit` | `12` | Maximale Anzahl Veranstaltungen |
|
||||
| `category` | `""` | Nach Kategorie filtern (Slug) |
|
||||
| `month` | `""` | Nach Monat filtern (YYYY-MM) |
|
||||
| `orderby` | `date` | Sortierung (`date`, `title`) |
|
||||
| `order` | `ASC` | Reihenfolge (`ASC`, `DESC`) |
|
||||
|
||||
### Beispiele
|
||||
|
||||
```
|
||||
[kurs_cards]
|
||||
[kurs_cards show_filter="true" columns="3" limit="12"]
|
||||
[kurs_cards category="praesenz-kurs" limit="6"]
|
||||
[kurs_cards month="2025-01" columns="4"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [kurs_field]
|
||||
|
||||
**NEU** - Gibt ein einzelnes Meta-Feld eines Kurses aus. Ideal fuer Kadence Templates und Custom Layouts.
|
||||
|
||||
### Attribute
|
||||
|
||||
| Attribut | Standard | Beschreibung |
|
||||
|----------|----------|--------------|
|
||||
| `name` | *erforderlich* | Feldname (z.B. `coaching_dauer`) |
|
||||
| `id` | aktueller Post | Kurs-ID (optional) |
|
||||
| `prefix` | `""` | Text vor dem Wert |
|
||||
| `suffix` | `""` | Text nach dem Wert |
|
||||
| `format` | auto | Format: `date`, `time`, `datetime`, `price` oder Datumsformat |
|
||||
| `default` | `""` | Standardwert wenn Feld leer |
|
||||
| `wrapper` | `""` | HTML-Element: `span`, `div`, `p`, `strong`, `em` |
|
||||
| `class` | `""` | CSS-Klasse fuer Wrapper |
|
||||
|
||||
### Standard-Felder
|
||||
|
||||
Diese Felder sind fuer alle Kurse verfuegbar:
|
||||
|
||||
| Name | Beschreibung |
|
||||
|------|--------------|
|
||||
| `start_date` | Startdatum |
|
||||
| `end_date` | Enddatum |
|
||||
| `start_time` | Startzeit |
|
||||
| `end_time` | Endzeit |
|
||||
| `location` | Veranstaltungsort |
|
||||
| `max_participants` | Max. Teilnehmer |
|
||||
| `price` | Preis |
|
||||
| `deposit_percent` | Anzahlung in % |
|
||||
| `produktart` | Produktart-ID |
|
||||
| `zoom_link` | Zoom-Link |
|
||||
| `zoom_meeting_id` | Zoom Meeting-ID |
|
||||
| `zoom_passcode` | Zoom Passcode |
|
||||
| `instructor` | Reitlehrer/in |
|
||||
| `horse` | Pferd |
|
||||
|
||||
### Produktart-spezifische Felder
|
||||
|
||||
#### G) Webinar Live
|
||||
|
||||
| Name | Beschreibung |
|
||||
|------|--------------|
|
||||
| `aufzeichnung_verfuegbar` | Aufzeichnung vorhanden (1/0) |
|
||||
| `aufzeichnung_tage` | Tage bis Aufzeichnung verfuegbar |
|
||||
| `aufzeichnung_zugang_tage` | Zugang zur Aufzeichnung (Tage) |
|
||||
|
||||
#### H) Workshop Online / I) Coaching Online
|
||||
|
||||
| Name | Beschreibung |
|
||||
|------|--------------|
|
||||
| `coaching_dauer` | Coaching-Dauer in Minuten |
|
||||
|
||||
#### J) Online-Unterricht
|
||||
|
||||
| Name | Beschreibung |
|
||||
|------|--------------|
|
||||
| `unterricht_dauer` | Unterrichtsdauer in Minuten |
|
||||
| `unterricht_technik` | Technische Voraussetzungen |
|
||||
|
||||
#### K) Video-Analyse
|
||||
|
||||
| Name | Beschreibung |
|
||||
|------|--------------|
|
||||
| `video_max_length` | Max. Videolaenge in Minuten |
|
||||
| `feedback_format` | Feedback-Format |
|
||||
| `turnaround_days` | Bearbeitungszeit in Werktagen |
|
||||
|
||||
#### L) Beratung
|
||||
|
||||
| Name | Beschreibung |
|
||||
|------|--------------|
|
||||
| `beratung_art` | Beratungsart |
|
||||
| `beratung_dauer` | Gespraechsdauer in Minuten |
|
||||
| `beratung_wochen` | Begleitungsdauer in Wochen |
|
||||
| `beratung_kanal` | Bevorzugter Kanal |
|
||||
|
||||
### Eigene Produktarten und Felder
|
||||
|
||||
Der `[kurs_field]` Shortcode ist **vollstaendig dynamisch** - er unterstuetzt nicht nur die vordefinierten Produktarten (A-L), sondern auch alle selbst erstellten.
|
||||
|
||||
#### Workflow fuer eigene Produktarten
|
||||
|
||||
1. **Produktart anlegen**
|
||||
- Einstellungen → Dienstleistungen → "Neue Produktart"
|
||||
- z.B. `M) Reitkurs Spezial` mit ID `reitkurs_spezial`
|
||||
|
||||
2. **Felder zuweisen**
|
||||
- Einstellungen → Buchungsfelder → Neues Feld erstellen
|
||||
- Produktart-Checkboxen: Nur `M) Reitkurs Spezial` aktivieren
|
||||
- z.B. Feld `stallmiete` (number)
|
||||
|
||||
3. **Shortcode verwenden**
|
||||
```
|
||||
[kurs_field name="stallmiete" suffix=" EUR/Tag"]
|
||||
```
|
||||
|
||||
#### Beispiel: Eigene Produktart
|
||||
|
||||
**Produktart:** `M) Pferde-Pension` (ID: `pferde_pension`)
|
||||
|
||||
**Eigene Felder:**
|
||||
|
||||
| Feldname | Typ | Beschreibung |
|
||||
|----------|-----|--------------|
|
||||
| `box_groesse` | select | Boxengroesse (S/M/L) |
|
||||
| `weidegang` | checkbox | Weidegang inklusive |
|
||||
| `futter_art` | select | Futterart |
|
||||
| `preis_pro_tag` | number | Tagespreis |
|
||||
|
||||
**Shortcodes im Template:**
|
||||
|
||||
```html
|
||||
<div class="pension-details">
|
||||
<p><strong>Boxengroesse:</strong> [kurs_field name="box_groesse"]</p>
|
||||
<p><strong>Weidegang:</strong> [kurs_field name="weidegang" default="Nein"]</p>
|
||||
<p><strong>Futter:</strong> [kurs_field name="futter_art"]</p>
|
||||
<p><strong>Preis:</strong> [kurs_field name="preis_pro_tag" suffix=" EUR/Tag"]</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Feldbaum nutzen
|
||||
|
||||
Um alle verfuegbaren Felder fuer eine Produktart zu sehen:
|
||||
|
||||
1. Einstellungen → Buchungsfelder
|
||||
2. Filter auf gewuenschte Produktart setzen
|
||||
3. Button "Baum anzeigen" klicken
|
||||
4. Optional: Als JSON/Markdown/CSV exportieren
|
||||
|
||||
### Beispiele
|
||||
|
||||
**Einfache Ausgabe:**
|
||||
```
|
||||
[kurs_field name="coaching_dauer"]
|
||||
```
|
||||
Ausgabe: `60`
|
||||
|
||||
**Mit Prefix und Suffix:**
|
||||
```
|
||||
[kurs_field name="coaching_dauer" prefix="Dauer: " suffix=" Minuten"]
|
||||
```
|
||||
Ausgabe: `Dauer: 60 Minuten`
|
||||
|
||||
**Datum formatieren:**
|
||||
```
|
||||
[kurs_field name="start_date" format="d.m.Y"]
|
||||
[kurs_field name="start_date" format="l, d. F Y"]
|
||||
```
|
||||
Ausgabe: `15.01.2025` oder `Mittwoch, 15. Januar 2025`
|
||||
|
||||
**Preis formatieren:**
|
||||
```
|
||||
[kurs_field name="price" format="price"]
|
||||
```
|
||||
Ausgabe: `350,00 EUR`
|
||||
|
||||
**Mit Wrapper und CSS-Klasse:**
|
||||
```
|
||||
[kurs_field name="location" wrapper="span" class="kurs-location"]
|
||||
```
|
||||
Ausgabe: `<span class="kurs-location">Reiterhof Beispiel</span>`
|
||||
|
||||
**Standardwert wenn leer:**
|
||||
```
|
||||
[kurs_field name="zoom_link" default="Link wird noch bekannt gegeben"]
|
||||
```
|
||||
|
||||
**Fuer anderen Kurs:**
|
||||
```
|
||||
[kurs_field name="price" id="123" format="price"]
|
||||
```
|
||||
|
||||
### Kadence Template Beispiel
|
||||
|
||||
In einem Kadence Element/Template fuer Single-Kurs:
|
||||
|
||||
```html
|
||||
<div class="kurs-details">
|
||||
<p><strong>Datum:</strong> [kurs_field name="start_date" format="d.m.Y"]</p>
|
||||
<p><strong>Uhrzeit:</strong> [kurs_field name="start_time"] - [kurs_field name="end_time"] Uhr</p>
|
||||
<p><strong>Ort:</strong> [kurs_field name="location"]</p>
|
||||
<p><strong>Preis:</strong> [kurs_field name="price" format="price"]</p>
|
||||
</div>
|
||||
|
||||
<!-- Nur fuer Online-Coaching anzeigen -->
|
||||
<div class="coaching-info">
|
||||
[kurs_field name="coaching_dauer" prefix="Dauer: " suffix=" Minuten" wrapper="p"]
|
||||
</div>
|
||||
|
||||
<!-- Nur fuer Webinar Live anzeigen -->
|
||||
<div class="webinar-info">
|
||||
[kurs_field name="aufzeichnung_tage" prefix="Aufzeichnung nach " suffix=" Tagen verfuegbar" wrapper="p"]
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [kurs_booking_form]
|
||||
|
||||
Vollstaendiges 3-Schritt Buchungsformular.
|
||||
|
||||
### Attribute
|
||||
|
||||
| Attribut | Standard | Beschreibung |
|
||||
|----------|----------|--------------|
|
||||
| `id` | aktueller Post | Kurs-ID |
|
||||
|
||||
### Beispiel
|
||||
|
||||
```
|
||||
[kurs_booking_form]
|
||||
[kurs_booking_form id="123"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [kurs_booking_button]
|
||||
|
||||
Zeigt den Buchungs-Button fuer einen Kurs.
|
||||
|
||||
### Attribute
|
||||
|
||||
| Attribut | Standard | Beschreibung |
|
||||
|----------|----------|--------------|
|
||||
| `id` | aktueller Post | Kurs-ID |
|
||||
| `text` | "Jetzt buchen" | Button-Text |
|
||||
|
||||
### Beispiel
|
||||
|
||||
```
|
||||
[kurs_booking_button]
|
||||
[kurs_booking_button text="Platz reservieren"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [kurs_booking_status]
|
||||
|
||||
Zeigt Buchungsstatus-Meldungen nach Redirect (z.B. nach Bestaetigung).
|
||||
|
||||
```
|
||||
[kurs_booking_status]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [kurs_video_access]
|
||||
|
||||
Video-Zugangsseite fuer Kunden mit gekauftem Video-Kurs.
|
||||
|
||||
```
|
||||
[kurs_video_access]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [kurs_video]
|
||||
|
||||
Eingebetteter Video-Player.
|
||||
|
||||
### Attribute
|
||||
|
||||
| Attribut | Standard | Beschreibung |
|
||||
|----------|----------|--------------|
|
||||
| `id` | *erforderlich* | Video-ID |
|
||||
|
||||
```
|
||||
[kurs_video id="456"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [video_pakete]
|
||||
|
||||
Zeigt alle verfuegbaren Video-Pakete.
|
||||
|
||||
```
|
||||
[video_pakete]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [video_paket]
|
||||
|
||||
Zeigt ein einzelnes Video-Paket.
|
||||
|
||||
### Attribute
|
||||
|
||||
| Attribut | Standard | Beschreibung |
|
||||
|----------|----------|--------------|
|
||||
| `id` | *erforderlich* | Paket-ID |
|
||||
|
||||
```
|
||||
[video_paket id="789"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Tipp:** Shortcodes koennen in Kadence Elements, Gutenberg-Bloecken und klassischen Widgets verwendet werden.
|
||||
48
content/spam.md
Normal file
48
content/spam.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
id: spam
|
||||
title: Spam-Schutz
|
||||
icon: shield-exclamation
|
||||
description: Schutz vor Spam-Buchungen
|
||||
section: Erweitert
|
||||
tags: [Spam, Sicherheit, Honeypot, Captcha]
|
||||
related: [booking, fields]
|
||||
order: 52
|
||||
---
|
||||
|
||||
# Spam-Schutz
|
||||
|
||||
Schuetzen Sie Ihr Buchungsformular vor Spam und Bots.
|
||||
|
||||
## Verfuegbare Methoden
|
||||
|
||||
| Methode | Beschreibung |
|
||||
|---------|--------------|
|
||||
| **Honeypot** | Unsichtbares Feld das Bots ausfuellen |
|
||||
| **Zeit-Check** | Minimum-Zeit bis zur Absendung |
|
||||
| **reCAPTCHA** | Google reCAPTCHA v2/v3 |
|
||||
| **hCaptcha** | Datenschutzfreundliche Alternative |
|
||||
|
||||
## Honeypot (empfohlen)
|
||||
|
||||
- Automatisch aktiv
|
||||
- Unsichtbar fuer echte Benutzer
|
||||
- Keine zusaetzliche Benutzerinteraktion
|
||||
- DSGVO-konform
|
||||
|
||||
## Zeit-Check
|
||||
|
||||
Definieren Sie eine Mindestzeit fuer das Formular:
|
||||
|
||||
| Einstellung | Empfehlung |
|
||||
|-------------|------------|
|
||||
| **Minimum** | 5 Sekunden |
|
||||
| **Maximum** | 3600 Sekunden (1 Stunde) |
|
||||
|
||||
## reCAPTCHA einrichten
|
||||
|
||||
1. API-Keys bei Google erstellen
|
||||
2. Site Key und Secret Key eintragen
|
||||
3. Version waehlen (v2 oder v3)
|
||||
4. Schwellenwert festlegen (v3)
|
||||
|
||||
> **Tipp:** Honeypot + Zeit-Check bieten bereits guten Schutz ohne externe Dienste.
|
||||
273
content/stornierung-workflow.md
Normal file
273
content/stornierung-workflow.md
Normal file
@@ -0,0 +1,273 @@
|
||||
---
|
||||
id: stornierung-workflow
|
||||
title: Stornierung Workflow
|
||||
icon: x-circle
|
||||
description: Kompletter Ablauf einer Stornierung von A bis Z
|
||||
section: Rechtliches
|
||||
tags: [Stornierung, Workflow, Ablauf, Gebuehren]
|
||||
related: [cancellation, legal, emails, booking]
|
||||
order: 26
|
||||
---
|
||||
|
||||
# Stornierung Workflow
|
||||
|
||||
Diese Seite erklaert den kompletten Ablauf einer Stornierung.
|
||||
|
||||
---
|
||||
|
||||
## Ueberblick
|
||||
|
||||
```
|
||||
Kunde storiniert → System prueft → Gebuehr berechnet → E-Mail versendet
|
||||
↓ ↓ ↓ ↓
|
||||
Storno-Link Frist-Check Anteilige Kosten Bestaetigung
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 1: Stornierung einleiten
|
||||
|
||||
### Kunde (Self-Service)
|
||||
|
||||
1. Kunde erhaelt Buchungsbestaetigung per E-Mail
|
||||
2. In der E-Mail: **Stornieren** Link
|
||||
3. Klick oeffnet Stornierungsseite
|
||||
4. Kunde bestaetigt die Stornierung
|
||||
|
||||
### Admin (Manuell)
|
||||
|
||||
1. Admin > Buchungen > Buchung oeffnen
|
||||
2. Status-Dropdown: **Storniert** waehlen
|
||||
3. Speichern
|
||||
|
||||
---
|
||||
|
||||
## Schritt 2: Frist-Pruefung
|
||||
|
||||
Das System prueft automatisch die Stornofristen:
|
||||
|
||||
### Zeitraum-Berechnung
|
||||
|
||||
```
|
||||
Heute: 01.03.2025
|
||||
Kursbeginn: 15.03.2025
|
||||
Differenz: 14 Tage
|
||||
```
|
||||
|
||||
### Fristen-Logik
|
||||
|
||||
| Konfiguration | Pruefung |
|
||||
|---------------|----------|
|
||||
| `free_days: 21` | >= 21 Tage? → Kostenfrei |
|
||||
| `partial_days: 7` | >= 7 Tage? → Teilgebuehr |
|
||||
| Sonst | < 7 Tage? → Volle Gebuehr |
|
||||
|
||||
---
|
||||
|
||||
## Schritt 3: Gebuehr berechnen
|
||||
|
||||
### Staffelung (Standard)
|
||||
|
||||
| Zeitraum vor Kursbeginn | Stornogebuehr |
|
||||
|------------------------|---------------|
|
||||
| Mehr als 21 Tage | 0% (kostenfrei) |
|
||||
| 8-21 Tage | 50% des Preises |
|
||||
| 1-7 Tage | 100% des Preises |
|
||||
| Am Kurstag | Nicht moeglich |
|
||||
|
||||
### Beispiel-Berechnung
|
||||
|
||||
```
|
||||
Buchungspreis: 350 EUR
|
||||
Stornierung: 10 Tage vor Kursbeginn
|
||||
|
||||
→ Zeitraum: 8-21 Tage
|
||||
→ Gebuehr: 50%
|
||||
→ Stornokosten: 175 EUR
|
||||
→ Rueckerstattung: 175 EUR
|
||||
```
|
||||
|
||||
### Sonderfaelle
|
||||
|
||||
| Fall | Behandlung |
|
||||
|------|------------|
|
||||
| **Video-Kurs** | Keine Stornierung moeglich (Widerrufsverzicht) |
|
||||
| **Kurs bereits gestartet** | Keine Stornierung moeglich |
|
||||
| **Teilnehmer krank** | Manuell durch Admin (Kulanz) |
|
||||
|
||||
---
|
||||
|
||||
## Schritt 4: Status-Aenderung
|
||||
|
||||
Nach erfolgreicher Stornierung:
|
||||
|
||||
| Vorher | Nachher |
|
||||
|--------|---------|
|
||||
| `confirmed` | `cancelled` |
|
||||
| Teilnehmerplatz belegt | Platz wieder frei |
|
||||
|
||||
### Meta-Daten
|
||||
|
||||
Folgende Daten werden gespeichert:
|
||||
|
||||
```
|
||||
_buchung_cancelled_at: 2025-03-01 14:30:00
|
||||
_buchung_cancel_fee: 175.00
|
||||
_buchung_cancel_reason: customer_request
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 5: E-Mail-Versand
|
||||
|
||||
### An den Kunden
|
||||
|
||||
**Vorlage:** Stornierungsbestaetigung
|
||||
|
||||
Inhalt:
|
||||
- Bestaetigung der Stornierung
|
||||
- Berechnete Stornogebuehr
|
||||
- Rueckerstattungsbetrag
|
||||
- Kontaktdaten bei Fragen
|
||||
|
||||
### An den Admin
|
||||
|
||||
**Vorlage:** Admin-Benachrichtigung (Storno)
|
||||
|
||||
Inhalt:
|
||||
- Welche Buchung storniert wurde
|
||||
- Kundenname und Kurs
|
||||
- Stornogebuehr
|
||||
- Link zur Buchung
|
||||
|
||||
---
|
||||
|
||||
## Schritt 6: sevDesk (optional)
|
||||
|
||||
Wenn sevDesk-Integration aktiv:
|
||||
|
||||
### Stornorechnung
|
||||
|
||||
1. System prueft ob Rechnung existiert
|
||||
2. Stornorechnung wird erstellt
|
||||
3. Oder: Gutschrift fuer Rueckerstattung
|
||||
|
||||
### Manuelle Schritte
|
||||
|
||||
Bei komplexen Faellen:
|
||||
- Rechnung manuell in sevDesk stornieren
|
||||
- Gutschrift erstellen
|
||||
- Rueckzahlung veranlassen
|
||||
|
||||
---
|
||||
|
||||
## Stornierung nicht moeglich
|
||||
|
||||
In diesen Faellen kann nicht storniert werden:
|
||||
|
||||
| Fall | Grund | Alternative |
|
||||
|------|-------|-------------|
|
||||
| Kurs hat begonnen | Zu spaet | Kulanz durch Admin |
|
||||
| Video-Kurs | Widerrufsverzicht | Kein Anspruch |
|
||||
| Bereits storniert | Doppel-Storno | Keine Aktion |
|
||||
| Token abgelaufen | Sicherheit | Admin kontaktieren |
|
||||
|
||||
---
|
||||
|
||||
## Admin-Optionen
|
||||
|
||||
### Manuelle Stornierung
|
||||
|
||||
1. Buchung oeffnen
|
||||
2. Status > Storniert
|
||||
3. Optional: Stornogebuehr anpassen
|
||||
4. Speichern
|
||||
|
||||
### Kulanz-Stornierung
|
||||
|
||||
Bei besonderen Umstaenden:
|
||||
1. Status > Storniert
|
||||
2. Stornogebuehr manuell auf 0 setzen
|
||||
3. Kommentar hinzufuegen
|
||||
4. Speichern
|
||||
|
||||
### Teilnehmer umbuchen
|
||||
|
||||
Statt Stornierung:
|
||||
1. Neue Buchung fuer anderen Termin erstellen
|
||||
2. Alte Buchung auf "Umgebucht" setzen
|
||||
3. Keine Gebuehren
|
||||
|
||||
---
|
||||
|
||||
## Konfiguration
|
||||
|
||||
### Fristen anpassen
|
||||
|
||||
**Pfad:** Einstellungen > Stornierung
|
||||
|
||||
| Option | Beschreibung |
|
||||
|--------|--------------|
|
||||
| Kostenfreie Tage | Tage vor Kurs fuer 0% |
|
||||
| Teilgebuehr-Tage | Tage fuer anteilige Gebuehr |
|
||||
| Teilgebuehr-Prozent | Prozentsatz der Teilgebuehr |
|
||||
|
||||
### E-Mail-Vorlage anpassen
|
||||
|
||||
**Pfad:** Einstellungen > E-Mail Vorlagen > Stornierung
|
||||
|
||||
Platzhalter:
|
||||
- `{stornogebuehr}` - Berechnete Gebuehr
|
||||
- `{rueckerstattung}` - Zu erstattender Betrag
|
||||
- `{storno_datum}` - Datum der Stornierung
|
||||
|
||||
---
|
||||
|
||||
## Ablauf-Diagramm
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Storno-Anfrage │
|
||||
└────────┬────────┘
|
||||
↓
|
||||
┌─────────────────┐
|
||||
│ Buchung gueltig?│──Nein──→ Fehler anzeigen
|
||||
└────────┬────────┘
|
||||
↓ Ja
|
||||
┌─────────────────┐
|
||||
│ Kurs gestartet? │──Ja────→ Nicht moeglich
|
||||
└────────┬────────┘
|
||||
↓ Nein
|
||||
┌─────────────────┐
|
||||
│ Video-Kurs? │──Ja────→ Nicht moeglich
|
||||
└────────┬────────┘
|
||||
↓ Nein
|
||||
┌─────────────────┐
|
||||
│ Frist berechnen │
|
||||
└────────┬────────┘
|
||||
↓
|
||||
┌─────────────────┐
|
||||
│ Gebuehr ermitteln│
|
||||
└────────┬────────┘
|
||||
↓
|
||||
┌─────────────────┐
|
||||
│ Status aendern │
|
||||
└────────┬────────┘
|
||||
↓
|
||||
┌─────────────────┐
|
||||
│ E-Mails senden │
|
||||
└────────┬────────┘
|
||||
↓
|
||||
┌─────────────────┐
|
||||
│ sevDesk updaten │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Siehe auch
|
||||
|
||||
- [Stornierungseinstellungen](/topic/cancellation)
|
||||
- [Rechtliches](/topic/legal)
|
||||
- [E-Mail Vorlagen](/topic/email_templates)
|
||||
- [sevDesk Integration](/topic/sevdesk)
|
||||
134
content/theme/hero-banner.md
Normal file
134
content/theme/hero-banner.md
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
title: Hero-Banner
|
||||
description: Individuelle Banner-Bilder fuer jede Seite
|
||||
---
|
||||
|
||||
# Hero-Banner
|
||||
|
||||
Mit dem Hero-Customizer kannst du fuer jede Seite ein individuelles Banner-Bild mit Titel einstellen.
|
||||
|
||||
---
|
||||
|
||||
## Wo finde ich die Einstellungen?
|
||||
|
||||
1. Gehe zu **Seiten** im WordPress-Admin
|
||||
2. Bearbeite eine beliebige Seite
|
||||
3. In der rechten Seitenleiste: **"Hero Einstellungen"**
|
||||
|
||||
---
|
||||
|
||||
## Verfuegbare Felder
|
||||
|
||||
### 1. Hero Hintergrundbild
|
||||
|
||||
| Feld | Beschreibung |
|
||||
|------|--------------|
|
||||
| **Bild waehlen** | Oeffnet die Mediathek |
|
||||
| **Entfernen** | Loescht das Bild |
|
||||
|
||||
**Tipp:** Bilder im Querformat, mindestens 1920px breit.
|
||||
|
||||
**Fallback:** Ohne Hero-Bild wird das Beitragsbild verwendet.
|
||||
|
||||
---
|
||||
|
||||
### 2. Hero Titel
|
||||
|
||||
| Feld | Beschreibung |
|
||||
|------|--------------|
|
||||
| **Leer lassen** | Seitentitel wird angezeigt |
|
||||
| **Text eingeben** | Ueberschreibt den Seitentitel |
|
||||
|
||||
---
|
||||
|
||||
### 3. Untertitel
|
||||
|
||||
Optional - erscheint unterhalb des Titels mit tuerkisem Balken.
|
||||
|
||||
---
|
||||
|
||||
### 4. Overlay Staerke
|
||||
|
||||
Verdunkelt das Bild fuer bessere Lesbarkeit.
|
||||
|
||||
| Option | Wert |
|
||||
|--------|------|
|
||||
| Kein Overlay | 0% |
|
||||
| Leicht | 20% |
|
||||
| **Mittel (Standard)** | 40% |
|
||||
| Stark | 60% |
|
||||
| Sehr stark | 80% |
|
||||
|
||||
---
|
||||
|
||||
### 5. Bildausschnitt (vertikal)
|
||||
|
||||
Bestimmt, welcher Teil des Bildes sichtbar ist.
|
||||
|
||||
| Option | Zeigt |
|
||||
|--------|-------|
|
||||
| Oben (0%) | Oberen Bildrand |
|
||||
| Oben-Mitte (25%) | Zwischen oben und mitte |
|
||||
| **Mitte (50%)** | Bildmitte (Standard) |
|
||||
| Unten-Mitte (75%) | Zwischen mitte und unten |
|
||||
| Unten (100%) | Unteren Bildrand |
|
||||
| Benutzerdefiniert | Feineinstellung 0-100% |
|
||||
|
||||
**Beispiel:**
|
||||
- Foto mit Pferd am unteren Bildrand: 75-100%
|
||||
- Foto mit Himmel oben: 0-25%
|
||||
|
||||
---
|
||||
|
||||
## Banner-Format
|
||||
|
||||
Das Banner wird im **4:1 Format** angezeigt.
|
||||
|
||||
| Geraet | Verhaeltnis |
|
||||
|--------|-------------|
|
||||
| Desktop | 4:1 (flach) |
|
||||
| Tablet | 3:1 (etwas hoeher) |
|
||||
| Mobile | 2.5:1 (noch hoeher) |
|
||||
|
||||
---
|
||||
|
||||
## Titel-Styling
|
||||
|
||||
- **Tuerkiser Balken** als Hintergrund
|
||||
- **Teko Schriftart**, 48px
|
||||
- **Grossbuchstaben** automatisch
|
||||
- **Weisse Schrift**
|
||||
|
||||
---
|
||||
|
||||
## Beispiel-Konfigurationen
|
||||
|
||||
### Typische Kurs-Seite
|
||||
```
|
||||
Hero Bild: [Pferdebild]
|
||||
Hero Titel: [leer]
|
||||
Overlay: Mittel (40%)
|
||||
Bildausschnitt: Mitte (50%)
|
||||
```
|
||||
|
||||
### Startseite mit Text
|
||||
```
|
||||
Hero Bild: [Panorama-Bild]
|
||||
Hero Titel: Willkommen bei der Webwerkstatt
|
||||
Untertitel: Reitunterricht & Kurse
|
||||
Overlay: Leicht (20%)
|
||||
Bildausschnitt: Oben-Mitte (25%)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Haeufige Fragen
|
||||
|
||||
### Warum sehe ich kein Banner?
|
||||
Pruefe ob ein Hero-Bild oder Beitragsbild gesetzt ist.
|
||||
|
||||
### Das Bild wird abgeschnitten
|
||||
Aendere den **Bildausschnitt** (0% = oben, 100% = unten).
|
||||
|
||||
### Der Text ist schlecht lesbar
|
||||
Erhoehe die **Overlay Staerke**.
|
||||
139
content/theme/slider.md
Normal file
139
content/theme/slider.md
Normal file
@@ -0,0 +1,139 @@
|
||||
---
|
||||
title: Hero Slider
|
||||
description: Slider fuer die Startseite konfigurieren
|
||||
---
|
||||
|
||||
# Hero Slider
|
||||
|
||||
Der Hero Slider zeigt mehrere Slides mit Bildern, Titeln und Buttons auf der Startseite.
|
||||
|
||||
---
|
||||
|
||||
## Wo finde ich die Einstellungen?
|
||||
|
||||
**Kurs-Booking → Einstellungen → Hero Slider**
|
||||
|
||||
---
|
||||
|
||||
## Slide hinzufuegen
|
||||
|
||||
1. Klicke auf **"+ Neuen Slide hinzufuegen"**
|
||||
2. Fuelle die Felder aus
|
||||
3. Klicke auf **"Aenderungen speichern"**
|
||||
|
||||
---
|
||||
|
||||
## Felder pro Slide
|
||||
|
||||
### Bild
|
||||
|
||||
| Feld | Beschreibung |
|
||||
|------|--------------|
|
||||
| **Bild waehlen** | Oeffnet die Mediathek |
|
||||
| **Empfohlene Groesse** | 1950 × 1300 Pixel |
|
||||
|
||||
**Tipp:** Alle Slider-Bilder sollten die gleiche Groesse haben.
|
||||
|
||||
---
|
||||
|
||||
### Titel
|
||||
|
||||
Der Haupttext auf dem Slide.
|
||||
|
||||
| Option | Beschreibung |
|
||||
|--------|--------------|
|
||||
| **Zeilenumbruch** | Verwende `<br>` im Text |
|
||||
| **Beispiel** | `Willkommen bei der<br>Webwerkstatt` |
|
||||
|
||||
---
|
||||
|
||||
### Button
|
||||
|
||||
| Feld | Beschreibung | Beispiel |
|
||||
|------|--------------|----------|
|
||||
| **Button-Text** | Beschriftung des Buttons | "Mehr erfahren" |
|
||||
| **Button-Link** | Ziel-URL | `/kurse/` oder `https://...` |
|
||||
|
||||
**Tipp:** Lasse beide Felder leer, wenn kein Button erscheinen soll.
|
||||
|
||||
---
|
||||
|
||||
### Alt-Text (SEO)
|
||||
|
||||
Beschreibung des Bildes fuer Suchmaschinen und Screenreader.
|
||||
|
||||
| Gut | Schlecht |
|
||||
|-----|----------|
|
||||
| "Reiterin auf Islandpferd im Galopp" | "Bild1" |
|
||||
| "Kursteilnehmer beim Workshop" | "slider" |
|
||||
|
||||
---
|
||||
|
||||
## Slides verwalten
|
||||
|
||||
### Reihenfolge aendern
|
||||
|
||||
Die Slides erscheinen in der Reihenfolge, wie sie in der Liste stehen. Aktuell muss die Reihenfolge durch Loeschen und Neu-Anlegen geaendert werden.
|
||||
|
||||
### Slide loeschen
|
||||
|
||||
Klicke auf den **roten X-Button** rechts oben am Slide.
|
||||
|
||||
---
|
||||
|
||||
## Beispiel-Konfiguration
|
||||
|
||||
### Slide 1: Willkommen
|
||||
```
|
||||
Bild: [Panorama vom Gestüt]
|
||||
Titel: Willkommen bei der<br>Webwerkstatt
|
||||
Button-Text: Kurse entdecken
|
||||
Button-Link: /kurse/
|
||||
Alt-Text: Islandpferdegestüt mit Reitplatz im Sonnenuntergang
|
||||
```
|
||||
|
||||
### Slide 2: Aktueller Kurs
|
||||
```
|
||||
Bild: [Kursfoto]
|
||||
Titel: Neuer Kurs:<br>Sitzschulung intensiv
|
||||
Button-Text: Jetzt anmelden
|
||||
Button-Link: /kurs/sitzschulung-maerz-2025/
|
||||
Alt-Text: Reitlehrerin erklärt Sitzposition
|
||||
```
|
||||
|
||||
### Slide 3: Online-Angebot
|
||||
```
|
||||
Bild: [Laptop mit Zoom]
|
||||
Titel: Webinare &<br>Online-Coaching
|
||||
Button-Text: Online-Angebote
|
||||
Button-Link: /online/
|
||||
Alt-Text: Online-Unterricht per Videokonferenz
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technische Details
|
||||
|
||||
| Einstellung | Wert |
|
||||
|-------------|------|
|
||||
| Option-Name | `kurs_booking_slider_slides` |
|
||||
| Bildformat | Querformat empfohlen |
|
||||
| Optimale Groesse | 1950 × 1300 px |
|
||||
| Max. Slides | Unbegrenzt (3-5 empfohlen) |
|
||||
|
||||
---
|
||||
|
||||
## Haeufige Fragen
|
||||
|
||||
### Wie viele Slides sind sinnvoll?
|
||||
3-5 Slides sind optimal. Bei mehr verlieren Besucher das Interesse.
|
||||
|
||||
### Warum sehe ich keinen Slider?
|
||||
- Pruefe ob mindestens ein Slide mit Bild angelegt ist
|
||||
- Pruefe ob die Startseite das Slider-Element enthaelt
|
||||
|
||||
### Die Bilder werden abgeschnitten
|
||||
Verwende Bilder im Format 1950 × 1300 Pixel. Alle Bilder sollten die gleiche Groesse haben.
|
||||
|
||||
### Kann ich Videos im Slider verwenden?
|
||||
Nein, aktuell werden nur Bilder unterstuetzt.
|
||||
296
content/troubleshooting.md
Normal file
296
content/troubleshooting.md
Normal file
@@ -0,0 +1,296 @@
|
||||
---
|
||||
id: troubleshooting
|
||||
title: Fehlerbehebung
|
||||
icon: wrench-adjustable
|
||||
description: Loesungen fuer haeufige Probleme
|
||||
section: Tipps & Tricks
|
||||
tags: [Probleme, Fehler, FAQ, Hilfe, Support]
|
||||
related: [schnellstart, best-practices, emails]
|
||||
order: 65
|
||||
---
|
||||
|
||||
# Fehlerbehebung
|
||||
|
||||
Loesungen fuer die haeufigsten Probleme mit dem Kurs-Booking Plugin.
|
||||
|
||||
---
|
||||
|
||||
## E-Mail Probleme
|
||||
|
||||
### E-Mails kommen nicht an
|
||||
|
||||
**Symptom:** Buchungsbestaetigungen werden nicht zugestellt.
|
||||
|
||||
**Loesungen:**
|
||||
|
||||
1. **Spam-Ordner pruefen**
|
||||
- E-Mails landen oft im Spam
|
||||
|
||||
2. **Absender-E-Mail pruefen**
|
||||
- Muss zur Domain passen
|
||||
- `noreply@ihre-domain.de` statt Gmail
|
||||
|
||||
3. **SMTP Plugin installieren**
|
||||
- WordPress Standard-Mail ist unzuverlaessig
|
||||
- Empfohlen: WP Mail SMTP, FluentSMTP
|
||||
|
||||
4. **E-Mail-Log pruefen**
|
||||
- Plugins wie "WP Mail Log" zeigen, ob E-Mails versendet wurden
|
||||
|
||||
### E-Mails ohne Formatierung
|
||||
|
||||
**Symptom:** HTML wird als Text angezeigt.
|
||||
|
||||
**Loesung:**
|
||||
- Pruefen Sie die E-Mail-Vorlagen unter Einstellungen > E-Mail Vorlagen
|
||||
- Nutzen Sie Inline-Styles (siehe [E-Mail Vorlagen](/topic/email_templates))
|
||||
|
||||
---
|
||||
|
||||
## Buchungsformular Probleme
|
||||
|
||||
### Formular wird nicht angezeigt
|
||||
|
||||
**Symptom:** "Jetzt buchen" Button fehlt oder Modal oeffnet sich nicht.
|
||||
|
||||
**Loesungen:**
|
||||
|
||||
1. **JavaScript-Fehler pruefen**
|
||||
- Browser-Konsole oeffnen (F12)
|
||||
- Nach roten Fehlermeldungen suchen
|
||||
|
||||
2. **Theme-Konflikte**
|
||||
- Temporaer auf Standard-Theme wechseln
|
||||
- Testen, ob Formular erscheint
|
||||
|
||||
3. **Plugin-Konflikte**
|
||||
- Alle anderen Plugins deaktivieren
|
||||
- Einzeln wieder aktivieren
|
||||
|
||||
4. **Cache leeren**
|
||||
- Browser-Cache
|
||||
- WordPress Cache (wenn Caching-Plugin aktiv)
|
||||
- CDN Cache (Cloudflare etc.)
|
||||
|
||||
### Pflichtfeld-Fehler trotz Eingabe
|
||||
|
||||
**Symptom:** "Pflichtfeld fehlt" obwohl ausgefuellt.
|
||||
|
||||
**Loesungen:**
|
||||
|
||||
1. **Autofill-Problem**
|
||||
- Browser-Autofill kann Probleme verursachen
|
||||
- Manuell in Feld klicken und Eingabe bestaetigen
|
||||
|
||||
2. **JavaScript-Validierung**
|
||||
- Feld muss "blur" Event ausloesen
|
||||
- Einmal aus dem Feld klicken
|
||||
|
||||
3. **Feld-Name-Konflikt**
|
||||
- Pruefen Sie, ob Feldnamen eindeutig sind
|
||||
|
||||
### Buchung bleibt "Ausstehend"
|
||||
|
||||
**Symptom:** Status wechselt nicht zu "Bestaetigt".
|
||||
|
||||
**Loesungen:**
|
||||
|
||||
1. **Bestaetigungslink geklickt?**
|
||||
- E-Mail pruefen und Link anklicken
|
||||
|
||||
2. **Token abgelaufen**
|
||||
- Standard: 48 Stunden gueltig
|
||||
- Neue Buchung erforderlich
|
||||
|
||||
3. **Manuell bestaetigen**
|
||||
- Admin > Buchungen > Buchung oeffnen
|
||||
- Status auf "Bestaetigt" setzen
|
||||
|
||||
---
|
||||
|
||||
## Preis-Probleme
|
||||
|
||||
### Preis wird als 0 angezeigt
|
||||
|
||||
**Symptom:** Kurs zeigt keinen Preis.
|
||||
|
||||
**Loesungen:**
|
||||
|
||||
1. **Preis eingetragen?**
|
||||
- Kurs bearbeiten > Preis-Feld pruefen
|
||||
|
||||
2. **Preisvarianten pruefen**
|
||||
- Mindestens eine Variante muss aktiv sein
|
||||
- Preis darf nicht leer sein
|
||||
|
||||
3. **Waehrung gesetzt?**
|
||||
- Einstellungen > Preise > Waehrung
|
||||
|
||||
### MwSt wird nicht berechnet
|
||||
|
||||
**Symptom:** Preise ohne Steuer.
|
||||
|
||||
**Loesungen:**
|
||||
|
||||
1. **Kleinunternehmer-Regelung aktiv?**
|
||||
- Einstellungen > Preise > Kleinunternehmer
|
||||
- Wenn "Ja": Keine MwSt-Berechnung
|
||||
|
||||
2. **MwSt-Satz pruefen**
|
||||
- Muss groesser als 0 sein
|
||||
|
||||
---
|
||||
|
||||
## sevDesk Probleme
|
||||
|
||||
### Rechnung wird nicht erstellt
|
||||
|
||||
**Symptom:** Keine automatische Rechnung nach Buchung.
|
||||
|
||||
**Loesungen:**
|
||||
|
||||
1. **API-Token pruefen**
|
||||
- Einstellungen > sevDesk > Token
|
||||
- Token in sevDesk neu generieren
|
||||
|
||||
2. **Auto-Rechnung aktiviert?**
|
||||
- Einstellungen > sevDesk > Automatisch erstellen
|
||||
|
||||
3. **sevDesk-Log pruefen**
|
||||
- Buchung oeffnen > sevDesk-Status
|
||||
|
||||
4. **Kontakt-Problem**
|
||||
- Kunde muss in sevDesk angelegt sein
|
||||
- E-Mail-Adresse muss uebereinstimmen
|
||||
|
||||
### Falscher Kontakt zugeordnet
|
||||
|
||||
**Symptom:** Rechnung geht an falschen Kunden.
|
||||
|
||||
**Loesung:**
|
||||
- sevDesk sucht nach E-Mail-Adresse
|
||||
- Bei Duplikaten wird erster Treffer verwendet
|
||||
- Kontakte in sevDesk bereinigen
|
||||
|
||||
---
|
||||
|
||||
## Performance-Probleme
|
||||
|
||||
### Seite laedt langsam
|
||||
|
||||
**Symptom:** Kurs-Seiten brauchen lange zum Laden.
|
||||
|
||||
**Loesungen:**
|
||||
|
||||
1. **Caching aktivieren**
|
||||
- WP Rocket, W3 Total Cache, etc.
|
||||
|
||||
2. **Bilder optimieren**
|
||||
- Kurs-Bilder komprimieren
|
||||
- WebP-Format nutzen
|
||||
|
||||
3. **Datenbank optimieren**
|
||||
- Alte Buchungen archivieren
|
||||
- Transients bereinigen
|
||||
|
||||
### Admin-Bereich langsam
|
||||
|
||||
**Symptom:** Buchungsliste laedt langsam.
|
||||
|
||||
**Loesungen:**
|
||||
|
||||
1. **Weniger Eintraege anzeigen**
|
||||
- Ansicht anpassen > 20 statt 50 pro Seite
|
||||
|
||||
2. **Filter nutzen**
|
||||
- Nach Status oder Datum filtern
|
||||
|
||||
---
|
||||
|
||||
## Anzeige-Probleme
|
||||
|
||||
### Kurs-Cards nicht sichtbar
|
||||
|
||||
**Symptom:** Shortcode `[kurs_cards]` zeigt nichts.
|
||||
|
||||
**Loesungen:**
|
||||
|
||||
1. **Kurse vorhanden und veroeffentlicht?**
|
||||
- Mindestens ein Kurs muss publiziert sein
|
||||
|
||||
2. **Datum in der Zukunft?**
|
||||
- Vergangene Kurse werden nicht angezeigt
|
||||
|
||||
3. **Shortcode-Parameter pruefen**
|
||||
- `[kurs_cards limit="12"]`
|
||||
|
||||
### Styling passt nicht zum Theme
|
||||
|
||||
**Symptom:** Farben/Fonts stimmen nicht.
|
||||
|
||||
**Loesungen:**
|
||||
|
||||
1. **CSS-Variablen anpassen**
|
||||
- Theme Customizer > Zusaetzliches CSS
|
||||
|
||||
```css
|
||||
:root {
|
||||
--kb-primary: #ihre-farbe;
|
||||
}
|
||||
```
|
||||
|
||||
2. **Theme-Kompatibilitaet**
|
||||
- Kadence, Astra, GeneratePress getestet
|
||||
- Bei anderen Themes ggf. Anpassungen noetig
|
||||
|
||||
---
|
||||
|
||||
## Allgemeine Tipps
|
||||
|
||||
### Debug-Modus aktivieren
|
||||
|
||||
Fuer detaillierte Fehlermeldungen in `wp-config.php`:
|
||||
|
||||
```php
|
||||
define('WP_DEBUG', true);
|
||||
define('WP_DEBUG_LOG', true);
|
||||
```
|
||||
|
||||
Fehler-Log: `/wp-content/debug.log`
|
||||
|
||||
### Browser-Cache leeren
|
||||
|
||||
| Browser | Tastenkombination |
|
||||
|---------|-------------------|
|
||||
| Chrome | Ctrl+Shift+Delete |
|
||||
| Firefox | Ctrl+Shift+Delete |
|
||||
| Safari | Cmd+Option+E |
|
||||
|
||||
### Plugin aktualisieren
|
||||
|
||||
1. Backup erstellen
|
||||
2. Plugin deaktivieren
|
||||
3. Neue Version hochladen
|
||||
4. Aktivieren
|
||||
|
||||
---
|
||||
|
||||
## Noch Probleme?
|
||||
|
||||
Wenn keine Loesung hilft:
|
||||
|
||||
1. **Fehler dokumentieren**
|
||||
- Screenshot des Problems
|
||||
- Browser-Konsole (F12) Screenshot
|
||||
- PHP-Fehlerlog
|
||||
|
||||
2. **Support kontaktieren**
|
||||
- Mit allen Informationen melden
|
||||
|
||||
---
|
||||
|
||||
## Siehe auch
|
||||
|
||||
- [Schnellstart](/topic/schnellstart)
|
||||
- [Best Practices](/topic/best-practices)
|
||||
- [E-Mail System](/topic/emails)
|
||||
48
content/video.md
Normal file
48
content/video.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
id: video
|
||||
title: Video-Modul
|
||||
icon: play-circle
|
||||
description: Online-Kurse und Video-Zugang
|
||||
section: Integrationen
|
||||
tags: [Video, Online-Kurs, Portal, Streaming]
|
||||
related: [booking, email_templates]
|
||||
order: 41
|
||||
---
|
||||
|
||||
# Video-Modul
|
||||
|
||||
Das Video-Modul ermoeglicht Online-Kurse und Video-on-Demand.
|
||||
|
||||
## Produktarten mit Video
|
||||
|
||||
| Typ | Beschreibung |
|
||||
|-----|--------------|
|
||||
| **C - Hybrid** | Praesenz + Video-Aufzeichnung |
|
||||
| **D - Rein Online** | Nur Video-Zugang |
|
||||
| **D1 - Live-Stream** | Live-Teilnahme online |
|
||||
|
||||
## Video-Einstellungen
|
||||
|
||||
| Option | Beschreibung |
|
||||
|--------|--------------|
|
||||
| **Video-Provider** | Vimeo, YouTube (unlisted), oder eigener Server |
|
||||
| **Zugangs-Dauer** | Wie lange ist der Video-Zugang gueltig? |
|
||||
| **Passwort-Schutz** | Videos mit Passwort schuetzen |
|
||||
|
||||
## Video-Zugang verwalten
|
||||
|
||||
Nach der Buchung:
|
||||
|
||||
1. Video-Link wird automatisch erstellt
|
||||
2. Teilnehmer erhaelt Zugangs-E-Mail
|
||||
3. Zugang ist zeitlich begrenzt
|
||||
|
||||
## Portal-Ansicht
|
||||
|
||||
Teilnehmer sehen ihre Videos im persoenlichen Portal:
|
||||
|
||||
- Liste aller gebuchten Video-Kurse
|
||||
- Verbleibende Zugangsdauer
|
||||
- Direkter Play-Button
|
||||
|
||||
> **Tipp:** Laden Sie Videos auf Vimeo/YouTube hoch und schuetzen Sie diese mit Domain-Beschraenkung.
|
||||
258
content/zoom.md
Normal file
258
content/zoom.md
Normal file
@@ -0,0 +1,258 @@
|
||||
---
|
||||
id: zoom
|
||||
title: Zoom Webhooks
|
||||
icon: camera-video
|
||||
description: Automatische Zoom-Integration fuer Online-Kurse
|
||||
section: Integrationen
|
||||
tags: [Zoom, Webhook, Online-Kurs, Meeting, Aufnahme, Anwesenheit]
|
||||
related: [video, kursarten/online-termine, emails]
|
||||
order: 42
|
||||
---
|
||||
|
||||
# Zoom Webhook-Automatisierung
|
||||
|
||||
Verbindet Ihre Zoom-Meetings automatisch mit dem Buchungssystem. Teilnehmer-Tracking, Meeting-Status und Aufnahmen werden automatisch verarbeitet.
|
||||
|
||||
## Funktionen
|
||||
|
||||
| Funktion | Beschreibung |
|
||||
|----------|--------------|
|
||||
| **Meeting-Status** | Kurs wird automatisch als "live" oder "beendet" markiert |
|
||||
| **Anwesenheits-Tracking** | Wer hat teilgenommen? Wie lange? |
|
||||
| **Aufnahmen-Import** | Zoom-Aufnahmen automatisch dem Kurs zuweisen |
|
||||
| **E-Mail-Benachrichtigung** | Teilnehmer bei Meeting-Start informieren |
|
||||
|
||||
## Unterstuetzte Produktarten
|
||||
|
||||
Diese Kursarten nutzen Zoom:
|
||||
|
||||
| Typ | Name | Verwendung |
|
||||
|-----|------|------------|
|
||||
| **G** | Webinar Live | Live-Meeting mit Aufzeichnung |
|
||||
| **H** | Workshop Online | Interaktiver Online-Workshop |
|
||||
| **I** | Coaching Online | 1:1 Online-Coaching |
|
||||
| **L** | Beratung | Telefon oder Zoom |
|
||||
|
||||
---
|
||||
|
||||
## Einrichtung
|
||||
|
||||
### Schritt 1: Modul aktivieren
|
||||
|
||||
1. Gehen Sie zu **Kurs-Booking → Einstellungen → Module**
|
||||
2. Aktivieren Sie **"Zoom Webhooks"**
|
||||
3. Speichern
|
||||
|
||||
### Schritt 2: Zoom App erstellen
|
||||
|
||||
1. Oeffnen Sie [Zoom App Marketplace](https://marketplace.zoom.us/develop/create)
|
||||
2. Klicken Sie auf **"Build App"**
|
||||
3. Waehlen Sie **"Webhook Only"**
|
||||
4. Geben Sie einen Namen ein (z.B. "Kurs-Booking Webhook")
|
||||
5. Klicken Sie **"Create"**
|
||||
|
||||
### Schritt 3: Secret Token kopieren
|
||||
|
||||
1. In der Zoom App unter **"Feature"**
|
||||
2. Kopieren Sie das **"Secret Token"**
|
||||
3. Gehen Sie zu **Kurs-Booking → Einstellungen → Zoom**
|
||||
4. Fuegen Sie das Token bei **"Webhook Secret Token"** ein
|
||||
5. Aktivieren Sie **"Webhooks aktivieren"**
|
||||
6. Speichern
|
||||
|
||||
### Schritt 4: Webhook-URL eintragen
|
||||
|
||||
1. Kopieren Sie die angezeigte **Webhook-URL** aus den Plugin-Einstellungen
|
||||
2. In der Zoom App unter **"Feature" → "Event Subscriptions"**
|
||||
3. Klicken Sie **"+ Add Event Subscription"**
|
||||
4. Name: "Kurs-Booking"
|
||||
5. Event notification endpoint URL: **Die kopierte URL einfuegen**
|
||||
6. Klicken Sie **"Validate"** - Zoom prueft die Verbindung
|
||||
|
||||
### Schritt 5: Events aktivieren
|
||||
|
||||
Aktivieren Sie folgende Events:
|
||||
|
||||
| Event | Kategorie |
|
||||
|-------|-----------|
|
||||
| `meeting.started` | Meeting |
|
||||
| `meeting.ended` | Meeting |
|
||||
| `meeting.participant_joined` | Meeting |
|
||||
| `meeting.participant_left` | Meeting |
|
||||
| `recording.completed` | Recording |
|
||||
|
||||
1. Klicken Sie **"Add Events"**
|
||||
2. Waehlen Sie die Events aus
|
||||
3. Speichern und App aktivieren
|
||||
|
||||
---
|
||||
|
||||
## Kurs mit Zoom verbinden
|
||||
|
||||
### Meeting-ID eintragen
|
||||
|
||||
1. Erstellen Sie ein Zoom-Meeting
|
||||
2. Kopieren Sie die **Meeting-ID** (z.B. `123 456 7890`)
|
||||
3. Bearbeiten Sie den Kurs in WordPress
|
||||
4. Tragen Sie die Meeting-ID bei **"Zoom Meeting-ID"** ein
|
||||
5. Optional: **Zoom-Passcode** eintragen
|
||||
6. Speichern
|
||||
|
||||
> **Wichtig:** Die Meeting-ID muss exakt uebereinstimmen, damit Webhooks dem richtigen Kurs zugeordnet werden.
|
||||
|
||||
### Zoom-Link fuer Teilnehmer
|
||||
|
||||
Der Zoom-Link wird automatisch in der Buchungsbestaetigungs-E-Mail versendet. Platzhalter:
|
||||
|
||||
| Platzhalter | Ausgabe |
|
||||
|-------------|---------|
|
||||
| `{zoom_link}` | Meeting-Beitritts-URL |
|
||||
| `{zoom_password}` | Meeting-Passwort |
|
||||
| `{zoom_meeting_id}` | Meeting-ID |
|
||||
|
||||
---
|
||||
|
||||
## Automatische Aktionen
|
||||
|
||||
### Bei Meeting-Start
|
||||
|
||||
Wenn das Meeting beginnt:
|
||||
|
||||
1. Kurs-Status wird auf **"live"** gesetzt
|
||||
2. Startzeit wird gespeichert
|
||||
3. Optional: Teilnehmer werden benachrichtigt
|
||||
|
||||
### Bei Meeting-Ende
|
||||
|
||||
Wenn das Meeting endet:
|
||||
|
||||
1. Kurs-Status wird auf **"beendet"** gesetzt
|
||||
2. Meeting-Dauer wird berechnet
|
||||
3. Anwesenheitsliste ist komplett
|
||||
|
||||
### Bei Teilnehmer-Beitritt
|
||||
|
||||
Wenn ein Teilnehmer beitritt:
|
||||
|
||||
1. System sucht Buchung anhand der E-Mail-Adresse
|
||||
2. Teilnahme wird bei der Buchung gespeichert
|
||||
3. Zeitstempel: Wann beigetreten
|
||||
|
||||
### Bei Aufnahme fertig
|
||||
|
||||
Wenn Zoom die Aufnahme verarbeitet hat:
|
||||
|
||||
1. Aufnahme-URL wird beim Kurs gespeichert
|
||||
2. Optional: Video wird automatisch importiert
|
||||
3. Teilnehmer koennen informiert werden
|
||||
|
||||
---
|
||||
|
||||
## Anwesenheits-Tracking
|
||||
|
||||
### Wo sehe ich die Anwesenheit?
|
||||
|
||||
1. Oeffnen Sie eine Buchung im Admin-Bereich
|
||||
2. Scrollen Sie zu **"Zoom-Teilnahme"**
|
||||
3. Sie sehen:
|
||||
- Ob teilgenommen wurde
|
||||
- Beitritts- und Austrittszeiten
|
||||
- Gesamte Teilnahmedauer
|
||||
|
||||
### Abgleich mit Buchungen
|
||||
|
||||
Das System vergleicht die E-Mail-Adresse aus Zoom mit der Buchungs-E-Mail:
|
||||
|
||||
- **Match:** Teilnahme wird automatisch verknuepft
|
||||
- **Kein Match:** Wird als "unbekannter Teilnehmer" protokolliert
|
||||
|
||||
> **Tipp:** Bitten Sie Teilnehmer, sich mit der E-Mail-Adresse anzumelden, die sie bei der Buchung verwendet haben.
|
||||
|
||||
---
|
||||
|
||||
## Aufnahmen verwalten
|
||||
|
||||
### Automatischer Import
|
||||
|
||||
1. Aktivieren Sie **"Aufnahmen automatisch importieren"** in den Zoom-Einstellungen
|
||||
2. Wenn eine Aufnahme fertig ist:
|
||||
- Download-URL wird gespeichert
|
||||
- Kann als Video-Kurs weiterverwendet werden
|
||||
|
||||
### Manueller Zugriff
|
||||
|
||||
Die Aufnahme-URLs werden beim Kurs gespeichert:
|
||||
|
||||
- **Aufnahme-URL:** Direkter Link zur Zoom-Aufnahme
|
||||
- **Passwort:** Falls Zoom ein Passwort vergibt
|
||||
|
||||
---
|
||||
|
||||
## Webhook-Logs
|
||||
|
||||
### Logs einsehen
|
||||
|
||||
1. Gehen Sie zu **Einstellungen → Zoom**
|
||||
2. Scrollen Sie zu **"Letzte Webhook-Events"**
|
||||
3. Sie sehen die letzten 10 Events mit:
|
||||
- Zeitstempel
|
||||
- Event-Typ
|
||||
- Status (Erfolg/Fehler)
|
||||
|
||||
### Fehlerbehebung
|
||||
|
||||
| Problem | Loesung |
|
||||
|---------|---------|
|
||||
| Keine Events | URL-Validierung in Zoom wiederholen |
|
||||
| Signatur-Fehler | Secret Token pruefen |
|
||||
| Kurs nicht gefunden | Meeting-ID im Kurs pruefen |
|
||||
| Teilnehmer nicht erkannt | E-Mail-Adresse abgleichen |
|
||||
|
||||
---
|
||||
|
||||
## Sicherheit
|
||||
|
||||
### Signatur-Verifizierung
|
||||
|
||||
Jeder Webhook wird mit einer kryptographischen Signatur geprueft:
|
||||
|
||||
1. Zoom signiert jede Anfrage mit dem Secret Token
|
||||
2. Das Plugin verifiziert die Signatur
|
||||
3. Ungueltige Anfragen werden abgelehnt
|
||||
|
||||
### Nur aktivierte Webhooks
|
||||
|
||||
Wenn "Webhooks aktivieren" nicht aktiviert ist:
|
||||
|
||||
- Endpoint existiert, aber lehnt alle Anfragen ab
|
||||
- Schuetzt vor ungewollter Verarbeitung
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Meeting vorab erstellen** - Meeting-ID vor dem Kurs eintragen
|
||||
2. **Recurring Meetings** - Fuer Kursreihen dasselbe Meeting verwenden
|
||||
3. **E-Mail-Hinweis** - Teilnehmer bitten, sich mit Buchungs-E-Mail anzumelden
|
||||
4. **Aufnahmen pruefen** - Vor Freigabe auf Qualitaet pruefen
|
||||
5. **Logs beobachten** - Regelmaessig Webhook-Logs kontrollieren
|
||||
|
||||
---
|
||||
|
||||
## Haeufige Fragen
|
||||
|
||||
### Funktioniert das mit Zoom Basic (kostenlos)?
|
||||
|
||||
Ja, Webhooks funktionieren mit allen Zoom-Plaenen. Fuer Aufnahmen benoetigen Sie jedoch einen kostenpflichtigen Plan.
|
||||
|
||||
### Kann ich mehrere Kurse mit demselben Meeting verbinden?
|
||||
|
||||
Nein, jedes Meeting sollte nur einem Kurs zugeordnet sein. Verwenden Sie fuer Kursreihen ein Recurring Meeting.
|
||||
|
||||
### Was passiert bei Verbindungsproblemen?
|
||||
|
||||
Zoom wiederholt fehlgeschlagene Webhooks automatisch. Falls das Plugin temporaer nicht erreichbar ist, werden Events nachgeholt.
|
||||
|
||||
### Werden Breakout-Rooms getrackt?
|
||||
|
||||
Nein, nur das Haupt-Meeting. Breakout-Room-Teilnahme wird nicht separat erfasst.
|
||||
39
docker-compose.coolify.yml
Normal file
39
docker-compose.coolify.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
# Help-Service für Coolify
|
||||
# =========================
|
||||
#
|
||||
# Flask + Gunicorn mit Bind Mount für Dokumentation
|
||||
#
|
||||
# WICHTIG in Coolify UI:
|
||||
# 1. Domain setzen: https://hilfe.islandpferde-melanieworbs.de
|
||||
# 2. Port: 5000
|
||||
#
|
||||
# Content aktualisieren:
|
||||
# SSH → cd /pfad/zum/service → git pull → Coolify: Restart
|
||||
#
|
||||
# Domain: https://hilfe.islandpferde-melanieworbs.de
|
||||
# Port: 5000
|
||||
|
||||
services:
|
||||
help:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: help-service
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- 5000
|
||||
environment:
|
||||
- FLASK_ENV=production
|
||||
- DOCS_PATH=/app/content
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./content
|
||||
target: /app/content
|
||||
is_directory: true
|
||||
read_only: true
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
42
docker-compose.yml
Normal file
42
docker-compose.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
# Help-Service Stack für Coolify/Hetzner
|
||||
# =======================================
|
||||
#
|
||||
# Kurs-Booking Dokumentation (Flask App)
|
||||
#
|
||||
# Required Environment Variables:
|
||||
# - HELP_DOMAIN (z.B. hilfe.islandpferde-melanieworbs.de)
|
||||
|
||||
services:
|
||||
help:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: ${PROJECT_NAME:-help-service}
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- FLASK_ENV=production
|
||||
networks:
|
||||
- coolify
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
# HTTPS Router
|
||||
- "traefik.http.routers.${PROJECT_NAME:-help-service}.rule=Host(`${HELP_DOMAIN}`)"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-help-service}.entrypoints=https"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-help-service}.tls=true"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-help-service}.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.${PROJECT_NAME:-help-service}.loadbalancer.server.port=5000"
|
||||
# HTTP zu HTTPS Redirect
|
||||
- "traefik.http.routers.${PROJECT_NAME:-help-service}-http.rule=Host(`${HELP_DOMAIN}`)"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-help-service}-http.entrypoints=http"
|
||||
- "traefik.http.middlewares.${PROJECT_NAME:-help-service}-https.redirectscheme.scheme=https"
|
||||
- "traefik.http.routers.${PROJECT_NAME:-help-service}-http.middlewares=${PROJECT_NAME:-help-service}-https"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5000/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
networks:
|
||||
coolify:
|
||||
external: true
|
||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
flask==3.0.0
|
||||
flask-cors==4.0.0
|
||||
pyyaml==6.0.1
|
||||
gunicorn==21.2.0
|
||||
markdown==3.5.1
|
||||
python-frontmatter==1.1.0
|
||||
33
templates/404.html
Normal file
33
templates/404.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Nicht gefunden{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card text-center py-5">
|
||||
<div class="card-body">
|
||||
<i class="bi bi-question-circle text-warning display-1 mb-4"></i>
|
||||
<h2>Seite nicht gefunden</h2>
|
||||
<p class="text-muted mb-4">
|
||||
{% if topic_id %}
|
||||
Das Hilfethema <code>{{ topic_id }}</code> existiert nicht.
|
||||
{% else %}
|
||||
Das gesuchte Hilfethema existiert leider nicht.
|
||||
{% endif %}
|
||||
</p>
|
||||
<div class="d-flex gap-2 justify-content-center">
|
||||
<a href="/" class="btn btn-primary">
|
||||
<i class="bi bi-house me-2"></i>Zur Startseite
|
||||
</a>
|
||||
<a href="/search" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-search me-2"></i>Suchen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
370
templates/base.html
Normal file
370
templates/base.html
Normal file
@@ -0,0 +1,370 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Hilfe{% endblock %} - Kurs-Booking</title>
|
||||
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Bootstrap Icons -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--kb-primary: #6366f1;
|
||||
--kb-primary-hover: #4f46e5;
|
||||
--kb-success: #22c55e;
|
||||
--kb-warning: #f59e0b;
|
||||
--kb-danger: #ef4444;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 280px;
|
||||
height: 100vh;
|
||||
background: rgba(30, 30, 46, 0.95);
|
||||
border-right: 1px solid rgba(255,255,255,0.1);
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.sidebar-brand {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
background: linear-gradient(135deg, var(--kb-primary), #8b5cf6);
|
||||
}
|
||||
|
||||
.sidebar-brand h4 {
|
||||
margin: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.nav-section {
|
||||
padding: 0.5rem 1.5rem;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: rgba(255,255,255,0.5);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
padding: 0.6rem 1.5rem;
|
||||
color: rgba(255,255,255,0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
transition: all 0.2s;
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
background: rgba(255,255,255,0.05);
|
||||
color: #fff;
|
||||
border-left-color: var(--kb-primary);
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
background: rgba(99, 102, 241, 0.2);
|
||||
color: #fff;
|
||||
border-left-color: var(--kb-primary);
|
||||
}
|
||||
|
||||
.nav-link i {
|
||||
font-size: 1.1rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 280px;
|
||||
padding: 2rem;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.search-box .form-control {
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.search-box .form-control:focus {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border-color: var(--kb-primary);
|
||||
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.25);
|
||||
}
|
||||
|
||||
.card {
|
||||
background: rgba(30, 30, 46, 0.8);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: rgba(255,255,255,0.05);
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.topic-card {
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.topic-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.badge-section {
|
||||
background: var(--kb-primary);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.content-body h3 {
|
||||
color: var(--kb-primary);
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.content-body h4 {
|
||||
color: #a5b4fc;
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.content-body table {
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.content-body table th,
|
||||
.content-body table td {
|
||||
padding: 0.75rem;
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.content-body table th {
|
||||
background: rgba(99, 102, 241, 0.2);
|
||||
}
|
||||
|
||||
.content-body code {
|
||||
background: rgba(99, 102, 241, 0.2);
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
color: #a5b4fc;
|
||||
}
|
||||
|
||||
.content-body pre {
|
||||
background: rgba(0,0,0,0.3);
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.content-body ul, .content-body ol {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.content-body li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tip-box {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
border-left: 4px solid var(--kb-success);
|
||||
padding: 1rem;
|
||||
border-radius: 0 8px 8px 0;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.warning-box {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
border-left: 4px solid var(--kb-warning);
|
||||
padding: 1rem;
|
||||
border-radius: 0 8px 8px 0;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.sidebar.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.mobile-toggle {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-toggle {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(255,255,255,0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(255,255,255,0.3);
|
||||
}
|
||||
|
||||
/* Sticky TOC Sidebar */
|
||||
.toc-sidebar {
|
||||
position: sticky;
|
||||
top: 2rem;
|
||||
max-height: calc(100vh - 4rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.toc-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.toc-link {
|
||||
display: block;
|
||||
padding: 0.4rem 0.75rem;
|
||||
color: rgba(255,255,255,0.7);
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
border-left: 2px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.toc-link:hover {
|
||||
color: #fff;
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
|
||||
.toc-link.active {
|
||||
color: var(--kb-primary);
|
||||
border-left-color: var(--kb-primary);
|
||||
background: rgba(99, 102, 241, 0.1);
|
||||
}
|
||||
|
||||
.toc-link-sub {
|
||||
padding-left: 1.5rem;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.toc-card {
|
||||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||||
}
|
||||
|
||||
/* Smooth scroll */
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Scroll offset for sticky header */
|
||||
[id] {
|
||||
scroll-margin-top: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.toc-sidebar {
|
||||
position: static;
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Mobile Toggle -->
|
||||
<button class="btn btn-primary mobile-toggle" onclick="document.querySelector('.sidebar').classList.toggle('show')">
|
||||
<i class="bi bi-list"></i>
|
||||
</button>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar">
|
||||
<a href="/" class="sidebar-brand text-decoration-none text-white d-block">
|
||||
<h4><i class="bi bi-book me-2"></i>Kurs-Booking</h4>
|
||||
<small class="text-white-50">Hilfe & Dokumentation</small>
|
||||
</a>
|
||||
|
||||
<div class="search-box">
|
||||
<form action="/search" method="get">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-transparent border-end-0">
|
||||
<i class="bi bi-search text-muted"></i>
|
||||
</span>
|
||||
<input type="text" name="q" class="form-control border-start-0"
|
||||
placeholder="Suchen..." value="{{ request.args.get('q', '') }}">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-nav">
|
||||
<a href="/" class="nav-link {% if request.path == '/' %}active{% endif %}">
|
||||
<i class="bi bi-house"></i>
|
||||
<span>Startseite</span>
|
||||
</a>
|
||||
|
||||
{% for section in content.sections %}
|
||||
<div class="nav-section">{{ section.title }}</div>
|
||||
{% for topic in section.topics %}
|
||||
<a href="/topic/{{ topic.id }}" class="nav-link {% if request.path == '/topic/' + topic.id %}active{% endif %}">
|
||||
<i class="bi bi-{{ topic.icon|default('file-text') }}"></i>
|
||||
<span>{{ topic.title }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Auto-scroll sidebar to active menu item -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const activeLink = document.querySelector('.sidebar .nav-link.active');
|
||||
if (activeLink) {
|
||||
activeLink.scrollIntoView({ block: 'center', behavior: 'instant' });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
143
templates/index.html
Normal file
143
templates/index.html
Normal file
@@ -0,0 +1,143 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Startseite{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<h1 class="display-5 mb-3">
|
||||
<i class="bi bi-life-preserver text-primary me-3"></i>
|
||||
Willkommen zur Hilfe
|
||||
</h1>
|
||||
<p class="lead text-muted mb-4">
|
||||
Alles was Sie ueber das Kurs-Booking Plugin wissen muessen
|
||||
</p>
|
||||
<form action="/search" method="get" class="d-flex justify-content-center">
|
||||
<div class="input-group" style="max-width: 500px;">
|
||||
<input type="text" name="q" class="form-control form-control-lg"
|
||||
placeholder="Was moechten Sie wissen?">
|
||||
<button class="btn btn-primary btn-lg" type="submit">
|
||||
<i class="bi bi-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Links -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h4 class="mb-3"><i class="bi bi-lightning me-2"></i>Schnellstart</h4>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="/topic/buchungsfelder-uebersicht" class="text-decoration-none">
|
||||
<div class="card topic-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-ui-checks-grid text-primary display-4 mb-3"></i>
|
||||
<h5>Buchungsfelder</h5>
|
||||
<p class="text-muted small mb-0">Formularfelder verwalten</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="/topic/produktarten" class="text-decoration-none">
|
||||
<div class="card topic-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-tags text-success display-4 mb-3"></i>
|
||||
<h5>Produktarten</h5>
|
||||
<p class="text-muted small mb-0">Kurs, Workshop, Webinar</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="/topic/email-vorlagen" class="text-decoration-none">
|
||||
<div class="card topic-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-envelope text-warning display-4 mb-3"></i>
|
||||
<h5>E-Mail Vorlagen</h5>
|
||||
<p class="text-muted small mb-0">Templates anpassen</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="/topic/sevdesk" class="text-decoration-none">
|
||||
<div class="card topic-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-receipt text-info display-4 mb-3"></i>
|
||||
<h5>sevDesk</h5>
|
||||
<p class="text-muted small mb-0">Rechnungen erstellen</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- All Sections -->
|
||||
{% for section in content.sections %}
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h4 class="mb-3">
|
||||
<i class="bi bi-{{ section.icon|default('folder') }} me-2"></i>
|
||||
{{ section.title }}
|
||||
</h4>
|
||||
</div>
|
||||
{% for topic in section.topics %}
|
||||
<div class="col-md-4 mb-3">
|
||||
<a href="/topic/{{ topic.id }}" class="text-decoration-none">
|
||||
<div class="card topic-card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi bi-{{ topic.icon|default('file-text') }} text-primary me-3 fs-4"></i>
|
||||
<div>
|
||||
<h6 class="mb-1">{{ topic.title }}</h6>
|
||||
<p class="text-muted small mb-0">{{ topic.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Interner Bereich -->
|
||||
<div class="row mt-5">
|
||||
<div class="col-12">
|
||||
<a href="/intern/" class="text-decoration-none">
|
||||
<div class="card border-danger bg-danger bg-opacity-10">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<i class="bi bi-shield-lock text-danger display-6 me-3"></i>
|
||||
<div>
|
||||
<h5 class="mb-1 text-danger">Interner Bereich</h5>
|
||||
<p class="text-muted mb-0">API-Dokumentation, DevOps & Troubleshooting fuer Entwickler (passwortgeschuetzt)</p>
|
||||
</div>
|
||||
<i class="bi bi-arrow-right text-danger ms-auto fs-4"></i>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="row mt-5">
|
||||
<div class="col-12 text-center">
|
||||
<hr class="border-secondary">
|
||||
<p class="text-muted small">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
Kurs-Booking Plugin v1.3.1 •
|
||||
<a href="https://web-werkstatt.at" target="_blank" class="text-muted">web-werkstatt.at</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
24
templates/intern/404.html
Normal file
24
templates/intern/404.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% extends "intern/base.html" %}
|
||||
|
||||
{% block title %}Nicht gefunden{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card text-center">
|
||||
<div class="card-body py-5">
|
||||
<i class="bi bi-exclamation-triangle text-danger display-1 mb-4"></i>
|
||||
<h2>Seite nicht gefunden</h2>
|
||||
<p class="text-muted">
|
||||
Das Thema <code>{{ topic_id }}</code> existiert nicht.
|
||||
</p>
|
||||
<a href="/intern/" class="btn btn-danger">
|
||||
<i class="bi bi-arrow-left me-2"></i>Zurueck zur Uebersicht
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
254
templates/intern/base.html
Normal file
254
templates/intern/base.html
Normal file
@@ -0,0 +1,254 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Intern{% endblock %} - Kurs-Booking (Intern)</title>
|
||||
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Bootstrap Icons -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--kb-primary: #dc2626;
|
||||
--kb-primary-hover: #b91c1c;
|
||||
--kb-success: #22c55e;
|
||||
--kb-warning: #f59e0b;
|
||||
--kb-danger: #ef4444;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #2d1f1f 100%);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 280px;
|
||||
height: 100vh;
|
||||
background: rgba(30, 20, 20, 0.95);
|
||||
border-right: 1px solid rgba(220, 38, 38, 0.3);
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.sidebar-brand {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid rgba(220, 38, 38, 0.3);
|
||||
background: linear-gradient(135deg, #dc2626, #991b1b);
|
||||
}
|
||||
|
||||
.sidebar-brand h4 {
|
||||
margin: 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.nav-section {
|
||||
padding: 0.5rem 1.5rem;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: rgba(255,255,255,0.5);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
padding: 0.6rem 1.5rem;
|
||||
color: rgba(255,255,255,0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
transition: all 0.2s;
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
background: rgba(220, 38, 38, 0.1);
|
||||
color: #fff;
|
||||
border-left-color: var(--kb-primary);
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
background: rgba(220, 38, 38, 0.2);
|
||||
color: #fff;
|
||||
border-left-color: var(--kb-primary);
|
||||
}
|
||||
|
||||
.nav-link i {
|
||||
font-size: 1.1rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 280px;
|
||||
padding: 2rem;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: rgba(30, 20, 20, 0.8);
|
||||
border: 1px solid rgba(220, 38, 38, 0.2);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: rgba(220, 38, 38, 0.1);
|
||||
border-bottom: 1px solid rgba(220, 38, 38, 0.2);
|
||||
}
|
||||
|
||||
.topic-card {
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.topic-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(220, 38, 38, 0.2);
|
||||
}
|
||||
|
||||
.content-body h2 {
|
||||
color: var(--kb-primary);
|
||||
border-bottom: 1px solid rgba(220, 38, 38, 0.3);
|
||||
padding-bottom: 0.5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.content-body h3 {
|
||||
color: #fca5a5;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.content-body table {
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.content-body table th,
|
||||
.content-body table td {
|
||||
padding: 0.75rem;
|
||||
border: 1px solid rgba(220, 38, 38, 0.2);
|
||||
}
|
||||
|
||||
.content-body table th {
|
||||
background: rgba(220, 38, 38, 0.2);
|
||||
}
|
||||
|
||||
.content-body code {
|
||||
background: rgba(220, 38, 38, 0.2);
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
color: #fca5a5;
|
||||
}
|
||||
|
||||
.content-body pre {
|
||||
background: rgba(0,0,0,0.4);
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
border: 1px solid rgba(220, 38, 38, 0.2);
|
||||
}
|
||||
|
||||
.intern-badge {
|
||||
background: var(--kb-primary);
|
||||
color: white;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.toc-sidebar {
|
||||
position: sticky;
|
||||
top: 2rem;
|
||||
}
|
||||
|
||||
.toc-link {
|
||||
display: block;
|
||||
padding: 0.4rem 0.75rem;
|
||||
color: rgba(255,255,255,0.7);
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
border-left: 2px solid transparent;
|
||||
}
|
||||
|
||||
.toc-link:hover {
|
||||
color: #fff;
|
||||
background: rgba(220, 38, 38, 0.1);
|
||||
}
|
||||
|
||||
.toc-link.active {
|
||||
color: var(--kb-primary);
|
||||
border-left-color: var(--kb-primary);
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.sidebar.show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
<nav class="sidebar">
|
||||
<a href="/intern/" class="sidebar-brand text-decoration-none text-white d-block">
|
||||
<h4><i class="bi bi-shield-lock me-2"></i>Intern</h4>
|
||||
<small class="text-white-50">Entwickler-Dokumentation</small>
|
||||
</a>
|
||||
|
||||
<div class="sidebar-nav">
|
||||
<a href="/intern/" class="nav-link {% if request.path == '/intern/' %}active{% endif %}">
|
||||
<i class="bi bi-house"></i>
|
||||
<span>Uebersicht</span>
|
||||
</a>
|
||||
|
||||
<a href="/" class="nav-link text-warning">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
<span>Zurueck zur Hilfe</span>
|
||||
</a>
|
||||
|
||||
{% for section in content.sections %}
|
||||
<div class="nav-section">{{ section.title }}</div>
|
||||
{% for topic in section.topics %}
|
||||
<a href="/intern/topic/{{ topic.id }}" class="nav-link {% if request.path == '/intern/topic/' + topic.id %}active{% endif %}">
|
||||
<i class="bi bi-{{ topic.icon|default('file-lock') }}"></i>
|
||||
<span>{{ topic.title }}</span>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Auto-scroll sidebar to active menu item -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const activeLink = document.querySelector('.sidebar .nav-link.active');
|
||||
if (activeLink) {
|
||||
activeLink.scrollIntoView({ block: 'center', behavior: 'instant' });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
116
templates/intern/index.html
Normal file
116
templates/intern/index.html
Normal file
@@ -0,0 +1,116 @@
|
||||
{% extends "intern/base.html" %}
|
||||
|
||||
{% block title %}Uebersicht{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<span class="intern-badge mb-3 d-inline-block">INTERNER BEREICH</span>
|
||||
<h1 class="display-5 mb-3">
|
||||
<i class="bi bi-shield-lock text-danger me-3"></i>
|
||||
Entwickler-Dokumentation
|
||||
</h1>
|
||||
<p class="lead text-muted mb-0">
|
||||
Technische Referenz, API, DevOps und Troubleshooting
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Access -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h4 class="mb-3"><i class="bi bi-lightning me-2"></i>Schnellzugriff</h4>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="/intern/topic/api/hooks" class="text-decoration-none">
|
||||
<div class="card topic-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-code-slash text-danger display-4 mb-3"></i>
|
||||
<h5>Hooks & Filter</h5>
|
||||
<p class="text-muted small mb-0">Actions & Filters</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="/intern/topic/api/meta-felder" class="text-decoration-none">
|
||||
<div class="card topic-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-database text-warning display-4 mb-3"></i>
|
||||
<h5>Meta-Felder</h5>
|
||||
<p class="text-muted small mb-0">Alle Post Meta Keys</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="/intern/topic/devops/docker" class="text-decoration-none">
|
||||
<div class="card topic-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-box text-info display-4 mb-3"></i>
|
||||
<h5>Docker</h5>
|
||||
<p class="text-muted small mb-0">Container & Ports</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<a href="/intern/topic/troubleshooting/debug" class="text-decoration-none">
|
||||
<div class="card topic-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<i class="bi bi-bug text-success display-4 mb-3"></i>
|
||||
<h5>Debugging</h5>
|
||||
<p class="text-muted small mb-0">Logs & Fehlersuche</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- All Sections -->
|
||||
{% for section in content.sections %}
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h4 class="mb-3">
|
||||
<i class="bi bi-{{ section.icon|default('folder') }} me-2"></i>
|
||||
{{ section.title }}
|
||||
</h4>
|
||||
</div>
|
||||
{% for topic in section.topics %}
|
||||
<div class="col-md-4 mb-3">
|
||||
<a href="/intern/topic/{{ topic.id }}" class="text-decoration-none">
|
||||
<div class="card topic-card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi bi-{{ topic.icon|default('file-lock') }} text-danger me-3 fs-4"></i>
|
||||
<div>
|
||||
<h6 class="mb-1">{{ topic.title }}</h6>
|
||||
<p class="text-muted small mb-0">{{ topic.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="row mt-5">
|
||||
<div class="col-12 text-center">
|
||||
<hr class="border-danger opacity-25">
|
||||
<p class="text-muted small">
|
||||
<i class="bi bi-shield-lock me-1"></i>
|
||||
Interner Bereich - Nur fuer autorisierte Entwickler
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
94
templates/intern/topic.html
Normal file
94
templates/intern/topic.html
Normal file
@@ -0,0 +1,94 @@
|
||||
{% extends "intern/base.html" %}
|
||||
|
||||
{% block title %}{{ topic.title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<!-- Main Content -->
|
||||
<div class="col-lg-9">
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-3">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/intern/" class="text-danger">Intern</a></li>
|
||||
{% if parent_topic %}
|
||||
<li class="breadcrumb-item"><a href="/intern/topic/{{ parent_topic.id }}" class="text-danger">{{ parent_topic.title }}</a></li>
|
||||
{% endif %}
|
||||
<li class="breadcrumb-item active">{{ topic.title }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Topic Card -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h1 class="h3 mb-0">
|
||||
<i class="bi bi-{{ topic.icon|default('file-lock') }} text-danger me-2"></i>
|
||||
{{ topic.title }}
|
||||
</h1>
|
||||
<span class="intern-badge">INTERN</span>
|
||||
</div>
|
||||
<div class="card-body content-body">
|
||||
{% if topic.description %}
|
||||
<p class="lead text-muted">{{ topic.description }}</p>
|
||||
<hr class="border-danger opacity-25">
|
||||
{% endif %}
|
||||
|
||||
{{ topic.content|safe }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Child Topics -->
|
||||
{% if child_topics %}
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-folder2-open me-2"></i>Unterseiten</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
{% for child in child_topics %}
|
||||
<div class="col-md-6 mb-3">
|
||||
<a href="/intern/topic/{{ child.id }}" class="text-decoration-none">
|
||||
<div class="card topic-card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi bi-{{ child.icon|default('file-lock') }} text-danger me-3 fs-4"></i>
|
||||
<div>
|
||||
<h6 class="mb-1">{{ child.title }}</h6>
|
||||
<p class="text-muted small mb-0">{{ child.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- TOC Sidebar -->
|
||||
<div class="col-lg-3 d-none d-lg-block">
|
||||
{% if topic.toc %}
|
||||
<div class="toc-sidebar">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0"><i class="bi bi-list-ul me-2"></i>Inhalt</h6>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<nav class="toc-nav">
|
||||
{% for item in topic.toc %}
|
||||
<a href="#{{ item.id }}" class="toc-link {% if item.level == 3 %}ps-4{% endif %}">
|
||||
{{ item.title }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
84
templates/search.html
Normal file
84
templates/search.html
Normal file
@@ -0,0 +1,84 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Suche: {{ query }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Search Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h2 class="mb-3">
|
||||
<i class="bi bi-search me-2"></i>
|
||||
Suchergebnisse
|
||||
</h2>
|
||||
<form action="/search" method="get">
|
||||
<div class="input-group" style="max-width: 600px;">
|
||||
<input type="text" name="q" class="form-control form-control-lg"
|
||||
placeholder="Suchbegriff eingeben..." value="{{ query }}">
|
||||
<button class="btn btn-primary btn-lg" type="submit">
|
||||
<i class="bi bi-search"></i> Suchen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{% if query %}
|
||||
{% if results %}
|
||||
<p class="text-muted mb-4">
|
||||
{{ results|length }} Ergebnis{% if results|length != 1 %}se{% endif %} fuer "{{ query }}"
|
||||
</p>
|
||||
|
||||
{% for result in results %}
|
||||
<div class="card mb-3 topic-card">
|
||||
<div class="card-body">
|
||||
<a href="/topic/{{ result.id }}" class="text-decoration-none">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="bi bi-file-text text-primary me-3 fs-4"></i>
|
||||
<div>
|
||||
<h5 class="mb-1">{{ result.title }}</h5>
|
||||
<p class="text-muted mb-2">{{ result.description }}</p>
|
||||
<span class="badge bg-secondary">{{ result.section }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% else %}
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<i class="bi bi-search text-muted display-1 mb-3"></i>
|
||||
<h4>Keine Ergebnisse gefunden</h4>
|
||||
<p class="text-muted">
|
||||
Fuer "{{ query }}" wurden keine passenden Hilfethemen gefunden.
|
||||
</p>
|
||||
<a href="/" class="btn btn-primary">
|
||||
<i class="bi bi-house me-2"></i>Zur Startseite
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<i class="bi bi-search text-muted display-1 mb-3"></i>
|
||||
<h4>Geben Sie einen Suchbegriff ein</h4>
|
||||
<p class="text-muted">
|
||||
Durchsuchen Sie die gesamte Hilfe nach Stichworten.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
241
templates/topic.html
Normal file
241
templates/topic.html
Normal file
@@ -0,0 +1,241 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ topic.title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/">Startseite</a></li>
|
||||
{% if parent_topic %}
|
||||
<li class="breadcrumb-item"><a href="/topic/{{ parent_topic.id }}">{{ parent_topic.title }}</a></li>
|
||||
{% endif %}
|
||||
<li class="breadcrumb-item active">{{ topic.title }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Topic Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<i class="bi bi-{{ topic.icon|default('file-text') }} text-primary display-4 me-3"></i>
|
||||
<div>
|
||||
<h1 class="mb-1">{{ topic.title }}</h1>
|
||||
<p class="text-muted mb-0">{{ topic.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if topic.tags %}
|
||||
<div class="mt-3">
|
||||
{% for tag in topic.tags %}
|
||||
<span class="badge bg-secondary me-1">{{ tag }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Topic Content -->
|
||||
<div class="row">
|
||||
<div class="col-lg-9">
|
||||
<div class="card">
|
||||
<div class="card-body content-body">
|
||||
{{ topic.content|safe }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Child Topics / Subpages -->
|
||||
{% if child_topics %}
|
||||
<div class="card mt-4">
|
||||
<div class="card-header bg-primary bg-opacity-10">
|
||||
<h5 class="mb-0"><i class="bi bi-folder2-open me-2"></i>Unterseiten</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
{% for child in child_topics %}
|
||||
<div class="col-md-6 mb-3">
|
||||
<a href="/topic/{{ child.id }}" class="text-decoration-none">
|
||||
<div class="card h-100 hover-shadow">
|
||||
<div class="card-body py-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-{{ child.icon|default('file-text') }} text-primary me-3 fs-4"></i>
|
||||
<div>
|
||||
<h6 class="mb-1">{{ child.title }}</h6>
|
||||
{% if child.description %}
|
||||
<small class="text-muted">{{ child.description }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Related Topics -->
|
||||
{% if topic.related %}
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="bi bi-link-45deg me-2"></i>Verwandte Themen</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
{% for related_id in topic.related %}
|
||||
{% for section in content.sections %}
|
||||
{% for t in section.topics %}
|
||||
{% if t.id == related_id %}
|
||||
<div class="col-md-6 mb-2">
|
||||
<a href="/topic/{{ t.id }}" class="text-decoration-none">
|
||||
<div class="d-flex align-items-center p-2 rounded hover-bg">
|
||||
<i class="bi bi-{{ t.icon|default('file-text') }} text-primary me-2"></i>
|
||||
<span>{{ t.title }}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Sidebar (Sticky) -->
|
||||
<div class="col-lg-3">
|
||||
<div class="toc-sidebar">
|
||||
<!-- Table of Contents -->
|
||||
{% if topic.toc %}
|
||||
<div class="card mb-4 toc-card">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0"><i class="bi bi-list-ul me-2"></i>Inhalt</h6>
|
||||
</div>
|
||||
<div class="card-body py-2">
|
||||
<nav class="toc-nav" id="toc-nav">
|
||||
{% for item in topic.toc %}
|
||||
<a href="#{{ item.id }}" class="toc-link {% if item.level == 3 %}toc-link-sub{% endif %}" data-target="{{ item.id }}">
|
||||
{{ item.title }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Quick Tips -->
|
||||
{% if topic.tips %}
|
||||
<div class="card">
|
||||
<div class="card-header bg-success bg-opacity-25">
|
||||
<h6 class="mb-0 text-success"><i class="bi bi-lightbulb me-2"></i>Tipps</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="list-unstyled mb-0">
|
||||
{% for tip in topic.tips %}
|
||||
<li class="mb-2">
|
||||
<i class="bi bi-check-circle text-success me-1"></i>
|
||||
{{ tip }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="/" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-2"></i>Zurueck zur Uebersicht
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Scroll-Spy for TOC
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const tocNav = document.getElementById('toc-nav');
|
||||
if (!tocNav) return;
|
||||
|
||||
const tocLinks = tocNav.querySelectorAll('.toc-link');
|
||||
if (tocLinks.length === 0) return;
|
||||
|
||||
// Get all headings that are in the TOC
|
||||
const headingIds = Array.from(tocLinks).map(link => link.getAttribute('data-target'));
|
||||
const headings = headingIds.map(id => document.getElementById(id)).filter(el => el);
|
||||
|
||||
if (headings.length === 0) return;
|
||||
|
||||
function updateActiveLink() {
|
||||
const scrollPos = window.scrollY + 100; // Offset for better UX
|
||||
|
||||
let activeIndex = 0;
|
||||
|
||||
// Find the heading that is currently in view
|
||||
for (let i = 0; i < headings.length; i++) {
|
||||
if (headings[i].offsetTop <= scrollPos) {
|
||||
activeIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Update active state
|
||||
tocLinks.forEach((link, index) => {
|
||||
if (index === activeIndex) {
|
||||
link.classList.add('active');
|
||||
} else {
|
||||
link.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Throttle scroll events
|
||||
let ticking = false;
|
||||
window.addEventListener('scroll', function() {
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(function() {
|
||||
updateActiveLink();
|
||||
ticking = false;
|
||||
});
|
||||
ticking = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Initial update
|
||||
updateActiveLink();
|
||||
|
||||
// Smooth scroll with offset when clicking TOC links
|
||||
tocLinks.forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const targetId = this.getAttribute('data-target');
|
||||
const target = document.getElementById(targetId);
|
||||
if (target) {
|
||||
const offset = 20;
|
||||
const targetPos = target.offsetTop - offset;
|
||||
window.scrollTo({
|
||||
top: targetPos,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user