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