========================================
PROJEKTSTRUKTUR
========================================
(Ermittelt via Git unter Berücksichtigung von .gitignore)
copyChat.sh
llm_context.txt
src/app/api/hotels/[id]/prices/route.ts
src/app/api/hotels/[id]/scan/route.ts
.gitignore
Dockerfile
README.md
docker-compose.yml
next.config.js
next.config.mjs
package-lock.json
package.json
src/app/api/hotels/[id]/history/route.ts
src/app/api/hotels/[id]/review-summary/route.ts
src/app/api/hotels/[id]/route.ts
src/app/api/hotels/route.ts
src/app/api/jobs/trigger-daily-check/route.ts
src/app/api/trips/route.ts
src/app/api/webhooks/scraper/route.ts
src/app/layout.tsx
src/app/page.tsx
src/lib/db.ts
tsconfig.json

========================================
DATEIINHALTE
========================================

--- DATEI: .gitignore ---
```
# ==========================================
# 1. Lokale Umgebungs- & Geheimdateien
# ==========================================
.env
.env*.local
.env.development.local
.env.test.local
.env.production.local

# ==========================================
# 2. Abhängigkeiten & Paketmanager
# ==========================================
node_modules/
.pnpm-store/

# ==========================================
# 3. Next.js Build- & Output-Dateien
# ==========================================
.next/
out/
build/
dist/

# ==========================================
# 4. Logs & temporäre Dateien
# ==========================================
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
.pnpm-debug.log*
*.log

# ==========================================
# 5. IDEs & Betriebssystem
# ==========================================
.DS_Store
*.pem
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.idea/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

```

--- DATEI: Dockerfile ---
```
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .
RUN npm run build

EXPOSE 3001
ENV PORT=3001

CMD ["npm", "start"]

```

--- DATEI: README.md ---
```

```

--- DATEI: copyChat.sh ---
```
#!/usr/bin/env bash

OUTPUT_FILE="${1:-llm_context.txt}"
ROOT_DIR="${2:-.}"

# Absolute Pfade auflösen
OUTPUT_PATH="$(cd "$(dirname "$OUTPUT_FILE")" 2>/dev/null && pwd)/$(basename "$OUTPUT_FILE")"

cd "$ROOT_DIR" || exit 1

# Ausgabedatei initialisieren / leeren
> "$OUTPUT_PATH"

echo "========================================" >> "$OUTPUT_PATH"
echo "PROJEKTSTRUKTUR" >> "$OUTPUT_PATH"
echo "========================================" >> "$OUTPUT_PATH"

# Prüfen, ob Git verfügbar und das Verzeichnis ein Git-Repo ist
IS_GIT_REPO=false
if command -v git &> /dev/null && git rev-parse --is-inside-work-tree &> /dev/null; then
    IS_GIT_REPO=true
fi

if [ "$IS_GIT_REPO" = true ]; then
    echo "(Ermittelt via Git unter Berücksichtigung von .gitignore)" >> "$OUTPUT_PATH"
    # Zeigt alle getrackten + ungetrackten Dateien an, ignoriert .gitignore-Treffer
    git ls-files -co --exclude-standard >> "$OUTPUT_PATH"
else
    if command -v tree &> /dev/null; then
        tree -I 'node_modules|.git|venv|.venv|__pycache__|dist|build|.next|target' >> "$OUTPUT_PATH"
    else
        find . -maxdepth 3 -not -path '*/.*' -not -path '*/node_modules*' -not -path '*/venv*' >> "$OUTPUT_PATH"
    fi
fi

echo -e "\n========================================" >> "$OUTPUT_PATH"
echo "DATEIINHALTE" >> "$OUTPUT_PATH"
echo "========================================" >> "$OUTPUT_PATH"

# Dateiliste ermitteln
get_file_list() {
    if [ "$IS_GIT_REPO" = true ]; then
        # -c = cached/tracked, -o = others/untracked, --exclude-standard = .gitignore beachten
        git ls-files -co --exclude-standard
    else
        find . -type f \
            -not -path '*/.git/*' \
            -not -path '*/node_modules/*' \
            -not -path '*/venv/*' \
            -not -path '*/.venv/*' \
            -not -path '*/__pycache__/*' \
            -not -path '*/dist/*' \
            -not -path '*/build/*' \
            -not -path '*/.next/*'
    fi
}

# Dateien verarbeiten
get_file_list | sort | while IFS= read -r file; do
    # Überspringe leere Pfade, die Zieldatei selbst oder typische Binär-Endungen
    [ -z "$file" ] && continue
    [ "$file" = "$OUTPUT_FILE" ] && continue
    [ "$(realpath "$file" 2>/dev/null)" = "$OUTPUT_PATH" ] && continue

    case "$file" in
        *.png|*.jpg|*.jpeg|*.gif|*.ico|*.pdf|*.zip|*.tar.gz|*.lock|package-lock.json|yarn.lock)
            continue
            ;;
    esac

    # Prüfen, ob die Datei existiert und lesbarer Text ist
    if [ -f "$file" ] && (grep -Iq . "$file" 2>/dev/null || [ ! -s "$file" ]); then
        echo -e "\n--- DATEI: $file ---" >> "$OUTPUT_PATH"
        echo '```' >> "$OUTPUT_PATH"
        cat "$file" >> "$OUTPUT_PATH"
        echo -e "\n"'```' >> "$OUTPUT_PATH"
    fi
done

echo "Fertig! Projektkontext liegt in: $OUTPUT_PATH"
```

--- DATEI: docker-compose.yml ---
```
services:
  travel-app:
    build: .
    container_name: travel-app
    restart: always
    ports:
      - "3002:3001"
    env_file:
      - .env

```

--- DATEI: next.config.js ---
```
/** @type {import('next').NextConfig} */
const nextConfig = {
  serverExternalPackages: ['bullmq', 'ioredis'],
};

module.exports = nextConfig;

```

--- DATEI: next.config.mjs ---
```
/** @type {import('next').NextConfig} */
const nextConfig = {
  serverExternalPackages: ['bullmq', 'ioredis'],
};

export default nextConfig;

```

--- DATEI: package.json ---
```
{
  "name": "travel-planner-app",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "next dev -p 3001",
    "build": "next build",
    "start": "next start -p 3001"
  },
  "dependencies": {
    "axios": "^1.7.0",
    "bullmq": "^6.0.9",
    "ioredis": "^6.0.0",
    "next": "^14.2.0",
    "pg": "^8.11.5",
    "react": "^18.3.0",
    "react-dom": "^18.3.0"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "@types/pg": "^8.11.0",
    "@types/react": "^18.3.0",
    "autoprefixer": "^10.4.0",
    "postcss": "^8.4.0",
    "tailwindcss": "^3.4.0",
    "typescript": "^5.0.0"
  }
}

```

--- DATEI: src/app/api/hotels/[id]/history/route.ts ---
```
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';

export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
  try {
    const { id } = params;
    const res = await query(
      `SELECT * FROM hotel_price_history 
       WHERE hotel_id = $1 
       ORDER BY tracked_at DESC`,
      [id]
    );

    // Alle SQL-NUMERIC Strings in echte JS-Numbers umwandeln
    const parsedHistory = res.rows.map((entry) => ({
      ...entry,
      price_numeric: entry.price_numeric ? parseFloat(entry.price_numeric) : 0,
      genius_price_numeric: entry.genius_price_numeric ? parseFloat(entry.genius_price_numeric) : null,
      price_per_night: entry.price_per_night ? parseFloat(entry.price_per_night) : null,
      price_per_person: entry.price_per_person ? parseFloat(entry.price_per_person) : null,
      nights: entry.nights ? parseInt(entry.nights, 10) : 1,
      persons: entry.persons ? parseInt(entry.persons, 10) : 2,
    }));

    return NextResponse.json({ success: true, history: parsedHistory });
  } catch (e: any) {
    console.error('Error fetching history:', e);
    return NextResponse.json({ success: false, error: e.message }, { status: 500 });
  }
}

```

--- DATEI: src/app/api/hotels/[id]/prices/route.ts ---
```
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';

export const dynamic = 'force-dynamic';

export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
  try {
    const { id } = params;

    const res = await query(
      `SELECT 
        id, 
        price_numeric::float AS price, 
        price_per_night::float AS price_per_night,
        price_per_person::float AS price_per_person,
        nights, 
        persons, 
        tracked_at AS created_at 
       FROM hotel_price_history 
       WHERE hotel_id::text = $1::text 
       ORDER BY tracked_at ASC`,
      [id]
    );

    return NextResponse.json({ success: true, prices: res.rows });
  } catch (error: any) {
    console.error('Fehler beim Laden der Preishistorie:', error.message);
    return NextResponse.json({ success: false, error: error.message, prices: [] }, { status: 500 });
  }
}

```

--- DATEI: src/app/api/hotels/[id]/review-summary/route.ts ---
```
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';

export async function POST(req: NextRequest, { params }: { params: { id: string } }) {
  try {
    const { id } = params;
    const { summary_positive, summary_passable, summary_negative, summary_bathroom, summary_count } = await req.json();

    if (!id) {
      return NextResponse.json({ error: 'Hotel-ID fehlt' }, { status: 400 });
    }

    await query(
      `UPDATE hotels 
       SET review_summary_positive = $1,
           review_summary_passable = $2,
           review_summary_negative = $3,
           review_summary_bathroom = $4,
           review_summary_count = $5,
           reviews_updated_at = NOW()
       WHERE id = $6`,
      [summary_positive, summary_passable, summary_negative, summary_bathroom || null, summary_count || null, id]
    );

    return NextResponse.json({ success: true, message: 'Review-Zusammenfassungen gespeichert.' });
  } catch (error: any) {
    console.error('Fehler beim Speichern der Review-Zusammenfassung:', error);
    return NextResponse.json({ success: false, error: error.message }, { status: 500 });
  }
}

```

--- DATEI: src/app/api/hotels/[id]/route.ts ---
```
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';

// PATCH: Tracking aktivieren / deaktivieren (is_active)
export async function PATCH(req: NextRequest, { params }: { params: { id: string } }) {
  try {
    const { id } = params;
    const { is_active } = await req.json();

    await query('UPDATE hotels SET is_active = $1 WHERE id = $2', [is_active, id]);
    return NextResponse.json({ success: true });
  } catch (e: any) {
    return NextResponse.json({ success: false, error: e.message }, { status: 500 });
  }
}

// DELETE: Hotel entfernen
export async function DELETE(req: NextRequest, { params }: { params: { id: string } }) {
  try {
    const { id } = params;
    await query('DELETE FROM hotels WHERE id = $1', [id]);
    return NextResponse.json({ success: true });
  } catch (e: any) {
    return NextResponse.json({ success: false, error: e.message }, { status: 500 });
  }
}

```

--- DATEI: src/app/api/hotels/[id]/scan/route.ts ---
```
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

export async function POST(
  req: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    const { id } = params;

    const bullmq = typeof window === 'undefined' ? eval('require')('bullmq') : null;
    if (!bullmq || !bullmq.Queue) {
      throw new Error('BullMQ Queue Klasse konnte nicht geladen werden.');
    }

    const { Queue } = bullmq;

    const connection = {
      host: process.env.REDIS_HOST || '172.17.0.1',
      port: parseInt(process.env.REDIS_PORT || '6379', 10),
      connectTimeout: 5000,
      maxRetriesPerRequest: 3,
    };

    const scrapeQueue = new Queue('scrape-jobs', { connection });

    // Nur dieses eine Hotel laden
    const res = await query('SELECT id, booking_url, name FROM hotels WHERE id = $1', [id]);
    if (res.rows.length === 0) {
      await scrapeQueue.close();
      return NextResponse.json({ error: 'Hotel nicht gefunden' }, { status: 404 });
    }

    const hotel = res.rows[0];

    const n8nBaseUrl = process.env.APP_WEBHOOK_URL || 'https://n8n.prinz-pilaw.de/webhook-test/scrape-reviews';
    const n8nWebhookUrl = n8nBaseUrl.includes('?')
      ? `${n8nBaseUrl}&hotelId=${hotel.id}`
      : `${n8nBaseUrl}?hotelId=${hotel.id}`;

    const appWebhookUrl = `http://172.17.0.1:3002/api/webhooks/scraper?hotelId=${hotel.id}`;

    // Nur DIESEN einzelnen Job in die Queue schreiben
    await scrapeQueue.add('scrape-hotel', {
      hotelId: hotel.id,
      url: hotel.booking_url,
      appWebhookUrl,
      webhookUrl: n8nWebhookUrl,
    });

    await scrapeQueue.close();

    return NextResponse.json({
      success: true,
      message: `Scrape-Job für "${hotel.name}" erfolgreich gestartet.`,
    });
  } catch (error: any) {
    console.error('Fehler beim Starten des Einzel-Scrapes:', error);
    return NextResponse.json({ success: false, error: error.message }, { status: 500 });
  }
}

```

--- DATEI: src/app/api/hotels/route.ts ---
```
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';

export const dynamic = 'force-dynamic';

export async function GET() {
  try {
    const res = await query(`
      SELECT 
        h.*, 
        t.name AS trip_name,
        ph.price_numeric::float AS latest_price,
        ph.price_per_night::float AS latest_price_per_night,
        ph.price_per_person::float AS latest_price_per_person,
        ph.nights AS latest_nights,
        ph.persons AS latest_persons
      FROM hotels h
      LEFT JOIN trips t ON h.trip_id = t.id
      LEFT JOIN LATERAL (
        SELECT price_numeric, price_per_night, price_per_person, nights, persons
        FROM hotel_price_history 
        WHERE hotel_id::text = h.id::text 
        ORDER BY tracked_at DESC NULLS LAST, id DESC
        LIMIT 1
      ) ph ON true
      ORDER BY h.checkin_date ASC NULLS LAST, h.id ASC
    `);

    return NextResponse.json({ success: true, hotels: res.rows });
  } catch (error: any) {
    console.error('Fehler in GET /api/hotels:', error.message);
    try {
      const fallback = await query(`SELECT * FROM hotels ORDER BY id ASC`);
      return NextResponse.json({ success: true, hotels: fallback.rows });
    } catch (fallbackErr: any) {
      return NextResponse.json({ success: false, error: error.message, hotels: [] }, { status: 500 });
    }
  }
}

export async function POST(req: NextRequest) {
  try {
    const { name, booking_url, trip_id } = await req.json();

    if (!booking_url) {
      return NextResponse.json({ error: 'Booking.com URL erforderlich' }, { status: 400 });
    }

    let checkin_date: string | null = null;
    let checkout_date: string | null = null;

    try {
      const parsedUrl = new URL(booking_url);
      checkin_date = parsedUrl.searchParams.get('checkin');
      checkout_date = parsedUrl.searchParams.get('checkout');
    } catch (e) {}

    const res = await query(
      `INSERT INTO hotels (name, booking_url, checkin_date, checkout_date, trip_id, is_active)
       VALUES ($1, $2, $3, $4, $5, true)
       RETURNING *`,
      [name || 'Neues Hotel', booking_url, checkin_date, checkout_date, trip_id || null]
    );

    return NextResponse.json({ success: true, hotel: res.rows[0] });
  } catch (error: any) {
    console.error('Fehler in POST /api/hotels:', error.message);
    return NextResponse.json({ success: false, error: error.message }, { status: 500 });
  }
}

```

--- DATEI: src/app/api/jobs/trigger-daily-check/route.ts ---
```
import { NextResponse } from 'next/server';
import { query } from '@/lib/db';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

export async function POST() {
  try {
    const bullmq = typeof window === 'undefined' ? eval('require')('bullmq') : null;
    if (!bullmq || !bullmq.Queue) {
      throw new Error('BullMQ Queue Klasse konnte nicht geladen werden.');
    }

    const { Queue } = bullmq;

    const connection = {
      host: process.env.REDIS_HOST || '172.17.0.1',
      port: parseInt(process.env.REDIS_PORT || '6379', 10),
      connectTimeout: 5000,
      maxRetriesPerRequest: 3,
    };

    const scrapeQueue = new Queue('scrape-jobs', { connection });

    const res = await query('SELECT id, booking_url FROM hotels WHERE is_active = true');
    const activeHotels = res.rows;

    if (activeHotels.length === 0) {
      await scrapeQueue.close();
      return NextResponse.json({
        success: true,
        message: 'Keine aktiven Hotels zum Scrapen gefunden.',
      });
    }

    // Für ALLE aktiven Hotels nur den Preis-Scan anwerfen (OHNE n8n / OHNE LLM)
    for (const hotel of activeHotels) {
      const appWebhookUrl = `http://172.17.0.1:3002/api/webhooks/scraper?hotelId=${hotel.id}`;

      await scrapeQueue.add('scrape-hotel', {
        hotelId: hotel.id,
        url: hotel.booking_url,
        appWebhookUrl, // Speichert den Preis in Postgres
        webhookUrl: null, // KEIN n8n -> KOSTENLOS!
      });
    }

    await scrapeQueue.close();

    return NextResponse.json({
      success: true,
      message: `Preis-Scan für ${activeHotels.length} Hotels gestartet.`,
    });
  } catch (error: any) {
    console.error('Fehler beim Starten des Preis-Scans:', error);
    return NextResponse.json({ success: false, error: error.message }, { status: 500 });
  }
}

```

--- DATEI: src/app/api/trips/route.ts ---
```
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';

export const dynamic = 'force-dynamic';

export async function GET() {
  try {
    const res = await query(`SELECT * FROM trips ORDER BY id ASC`);
    return NextResponse.json({ success: true, trips: res.rows });
  } catch (error: any) {
    return NextResponse.json({ success: false, error: error.message, trips: [] }, { status: 500 });
  }
}

export async function POST(req: NextRequest) {
  try {
    const { name } = await req.json();
    if (!name) {
      return NextResponse.json({ error: 'Name der Reise erforderlich' }, { status: 400 });
    }

    const res = await query(
      `INSERT INTO trips (name) VALUES ($1) RETURNING *`,
      [name]
    );

    return NextResponse.json({ success: true, trip: res.rows[0] });
  } catch (error: any) {
    return NextResponse.json({ success: false, error: error.message }, { status: 500 });
  }
}

```

--- DATEI: src/app/api/webhooks/scraper/route.ts ---
```
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';

function parseGermanPrice(priceStr: any): number | null {
  if (!priceStr) return null;
  if (typeof priceStr === 'number') return Math.round(priceStr);

  let cleaned = String(priceStr).replace(/[^0-9.,]/g, '').trim();
  if (!cleaned) return null;

  if (cleaned.includes('.') && !cleaned.includes(',')) {
    cleaned = cleaned.replace(/\./g, '');
  } else if (cleaned.includes('.') && cleaned.includes(',')) {
    cleaned = cleaned.replace(/\./g, '').replace(',', '.');
  } else if (cleaned.includes(',')) {
    cleaned = cleaned.replace(',', '.');
  }

  const parsed = parseFloat(cleaned);
  return isNaN(parsed) ? null : Math.round(parsed);
}

export async function POST(req: NextRequest) {
  try {
    const body = await req.json().catch(() => ({}));
    const { searchParams } = new URL(req.url);
    const hotelId = searchParams.get('hotelId') || body.hotelId;

    if (!hotelId) {
      return NextResponse.json({ success: false, error: 'hotelId fehlt' }, { status: 400 });
    }

    const numericPrice = parseGermanPrice(body.price);

    // 1. In hotel_price_history eintragen mit echten Spalten
    if (numericPrice !== null && numericPrice > 0) {
      try {
        await query(
          `INSERT INTO hotel_price_history (hotel_id, price_numeric, price_raw, currency, nights, persons, tracked_at)
           VALUES ($1::uuid, $2, $3, 'EUR', $4, $5, NOW())`,
          [hotelId, numericPrice, String(body.price || ''), body.nights || 1, body.persons || 2]
        );
      } catch (err: any) {
        console.error('DB Insert Error (hotel_price_history):', err.message);
      }
    }

    // 2. Stammdaten aktualisieren
    try {
      await query(
        `UPDATE hotels
         SET name = COALESCE($1, name),
             rating = COALESCE($2, rating),
             review_count = COALESCE($3, review_count),
             address = COALESCE($4, address),
             image_url = COALESCE($5, image_url),
             room_size_m2 = COALESCE($6, room_size_m2),
             updated_at = NOW()
         WHERE id = $7::uuid`,
        [
          body.hotelName || null,
          body.rating || null,
          body.reviewCount || null,
          body.address || null,
          body.image || null,
          body.roomSizeM2 || null,
          hotelId
        ]
      );
    } catch (err: any) {
      console.error('DB Update Error (hotels):', err.message);
    }

    return NextResponse.json({ success: true, hotelId, parsedPrice: numericPrice });
  } catch (error: any) {
    console.error('Allgemeiner Fehler im Webhook:', error.message);
    return NextResponse.json({ success: false, error: error.message }, { status: 200 });
  }
}

```

--- DATEI: src/app/layout.tsx ---
```
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Reiseplaner App',
  description: 'Hotelpreise und Reisen tracken',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="de">
      <body style={{ margin: 0, padding: 0, backgroundColor: '#fafafa' }}>
        {children}
      </body>
    </html>
  );
}

```

--- DATEI: src/app/page.tsx ---
```
'use client';

import { useState, useEffect } from 'react';

interface Trip {
  id: string;
  name: string;
}

interface PriceEntry {
  id: string;
  price: number;
  price_per_night?: number | null;
  price_per_person?: number | null;
  nights: number;
  persons: number;
  created_at: string;
}

interface Hotel {
  id: string;
  name: string;
  booking_url: string;
  is_active: boolean;
  trip_id?: string | null;
  trip_name?: string | null;
  latest_price?: number | null;
  latest_price_per_night?: number | null;
  latest_price_per_person?: number | null;
  latest_nights?: number | null;
  latest_persons?: number | null;
  rating?: number | null;
  review_count?: number | null;
  image_url?: string | null;
  address?: string | null;
  room_size_m2?: number | null;
  review_summary_positive?: string | null;
  review_summary_passable?: string | null;
  review_summary_negative?: string | null;
  review_summary_bathroom?: string | null;
  review_summary_count?: number | null;
  reviews_updated_at?: string | null;
  checkin_date?: string | null;
  checkout_date?: string | null;
}

export default function TravelDashboard() {
  const [trips, setTrips] = useState<Trip[]>([]);
  const [activeTripId, setActiveTripId] = useState<string>('ALL');
  const [hotels, setHotels] = useState<Hotel[]>([]);
  const [loading, setLoading] = useState(true);

  const [scanningAllPrices, setScanningAllPrices] = useState(false);
  const [scanningHotelId, setScanningHotelId] = useState<string | null>(null);

  // Preishistorie Modal
  const [selectedPriceHotel, setSelectedPriceHotel] = useState<Hotel | null>(null);
  const [priceHistory, setPriceHistory] = useState<PriceEntry[]>([]);
  const [loadingPrices, setLoadingPrices] = useState(false);

  // Modals
  const [showAddTripModal, setShowAddTripModal] = useState(false);
  const [newTripName, setNewTripName] = useState('');

  const [showAddHotelModal, setShowAddHotelModal] = useState(false);
  const [newBookingUrl, setNewBookingUrl] = useState('');
  const [newHotelName, setNewHotelName] = useState('');
  const [isSubmitting, setIsSubmitting] = useState(false);

  useEffect(() => {
    loadData();
  }, []);

  const formatDateGerman = (dateStr?: string | null) => {
    if (!dateStr) return '';
    const date = new Date(dateStr);
    return isNaN(date.getTime()) ? dateStr : date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
  };

  const calculateNights = (checkin?: string | null, checkout?: string | null) => {
    if (!checkin || !checkout) return null;
    const start = new Date(checkin).getTime();
    const end = new Date(checkout).getTime();
    if (isNaN(start) || isNaN(end)) return null;
    const diffDays = Math.round((end - start) / (1000 * 60 * 60 * 24));
    return diffDays > 0 ? diffDays : null;
  };

  const loadData = async () => {
    setLoading(true);
    try {
      const resTrips = await fetch('/api/trips');
      const dataTrips = await resTrips.json();
      setTrips(dataTrips.trips || []);

      const resHotels = await fetch('/api/hotels');
      const dataHotels = await resHotels.json();
      setHotels(dataHotels.hotels || []);
    } catch (e) {
      console.error('Fehler beim Laden:', e);
    } finally {
      setLoading(false);
    }
  };

  const openPriceHistoryModal = async (hotel: Hotel) => {
    setSelectedPriceHotel(hotel);
    setLoadingPrices(true);
    setPriceHistory([]);
    try {
      const res = await fetch(`/api/hotels/${hotel.id}/prices`);
      const data = await res.json();
      if (data.success) {
        setPriceHistory(data.prices || []);
      }
    } catch (e) {
      console.error('Fehler beim Laden der Preise:', e);
    } finally {
      setLoadingPrices(false);
    }
  };

  const handleAddTrip = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!newTripName.trim()) return;

    try {
      const res = await fetch('/api/trips', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: newTripName.trim() })
      });
      const data = await res.json();
      if (data.success) {
        setTrips([...trips, data.trip]);
        setActiveTripId(String(data.trip.id));
        setNewTripName('');
        setShowAddTripModal(false);
      }
    } catch (e: any) {
      alert('Fehler: ' + e.message);
    }
  };

  const handleAddHotel = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!newBookingUrl) return alert('Bitte URL eingeben');

    setIsSubmitting(true);
    try {
      const targetTripId = activeTripId === 'ALL' ? (trips[0]?.id || null) : activeTripId;
      const res = await fetch('/api/hotels', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          name: newHotelName || 'Neues Hotel',
          booking_url: newBookingUrl,
          trip_id: targetTripId
        })
      });

      const data = await res.json();
      if (res.ok || data.success) {
        setNewBookingUrl('');
        setNewHotelName('');
        setShowAddHotelModal(false);
        loadData();
      }
    } catch (err: any) {
      alert('Fehler: ' + err.message);
    } finally {
      setIsSubmitting(false);
    }
  };

  const toggleHotelActive = async (hotel: Hotel) => {
    try {
      const updatedActive = !hotel.is_active;
      await fetch(`/api/hotels/${hotel.id}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ is_active: updatedActive })
      });
      setHotels(hotels.map(h => h.id === hotel.id ? { ...h, is_active: updatedActive } : h));
    } catch (e) {}
  };

  const deleteHotel = async (id: string, name: string) => {
    if (!confirm(`Hotel "${name}" löschen?`)) return;
    await fetch(`/api/hotels/${id}`, { method: 'DELETE' });
    setHotels(hotels.filter(h => h.id !== id));
  };

  const triggerBatchPriceScan = async () => {
    setScanningAllPrices(true);
    try {
      const res = await fetch('/api/jobs/trigger-daily-check', { method: 'POST' });
      const data = await res.json();
      alert(data.success ? '📊 Preis-Scan gestartet!' : 'Fehler: ' + data.error);
      setTimeout(loadData, 3000);
    } finally {
      setScanningAllPrices(false);
    }
  };

  const scanSingleHotel = async (hotelId: string) => {
    setScanningHotelId(hotelId);
    try {
      const res = await fetch(`/api/hotels/${hotelId}/scan`, { method: 'POST' });
      const data = await res.json();
      alert(data.message || data.error);
    } finally {
      setScanningHotelId(null);
    }
  };

  const currentHotels = hotels
    .filter(h => {
      if (activeTripId === 'ALL') return true;
      if (!h.trip_id) return true;
      return String(h.trip_id).toLowerCase() === String(activeTripId).toLowerCase();
    })
    .sort((a, b) => {
      if (!a.checkin_date && !b.checkin_date) return 0;
      if (!a.checkin_date) return 1;
      if (!b.checkin_date) return -1;
      return new Date(a.checkin_date).getTime() - new Date(b.checkin_date).getTime();
    });

  const pricesList = priceHistory.map(p => Number(p.price));
  const minPrice = pricesList.length > 0 ? Math.min(...pricesList) : 0;
  const maxPrice = pricesList.length > 0 ? Math.max(...pricesList) : 0;
  const currentPrice = pricesList.length > 0 ? pricesList[pricesList.length - 1] : 0;

  return (
    <div style={{ maxWidth: '950px', margin: '0 auto', padding: '2rem 1rem', fontFamily: 'system-ui, sans-serif', color: '#1f2937' }}>
      
      <header style={{ marginBottom: '2rem', textAlign: 'center' }}>
        <h1 style={{ fontSize: '2.2rem', marginBottom: '0.5rem', fontWeight: '800' }}>📍 Reise-Ablauf & Timeline</h1>
        <p style={{ color: '#6b7280', margin: 0 }}>Routenplanung, Preishistorie & KI-Bewertungen</p>

        <div style={{ display: 'flex', gap: '1rem', justifyContent: 'center', marginTop: '1.5rem', flexWrap: 'wrap' }}>
          <button
            onClick={() => setShowAddHotelModal(true)}
            style={{ background: '#10b981', color: '#fff', border: 'none', padding: '0.75rem 1.4rem', borderRadius: '8px', fontWeight: '600', cursor: 'pointer' }}
          >
            ➕ Neues Hotel hinzufügen
          </button>

          <button
            onClick={triggerBatchPriceScan}
            disabled={scanningAllPrices}
            style={{ background: '#2563eb', color: '#fff', border: 'none', padding: '0.75rem 1.4rem', borderRadius: '8px', fontWeight: '600', cursor: 'pointer', opacity: scanningAllPrices ? 0.7 : 1 }}
          >
            {scanningAllPrices ? '🔄 Scanne...' : '📊 Preise aller Hotels aktualisieren'}
          </button>
        </div>
      </header>

      {/* TABS */}
      <div style={{ display: 'flex', gap: '0.5rem', borderBottom: '2px solid #e5e7eb', marginBottom: '2rem', overflowX: 'auto', paddingBottom: '0.5rem' }}>
        <button
          onClick={() => setActiveTripId('ALL')}
          style={{
            padding: '0.6rem 1.2rem',
            borderRadius: '8px',
            border: 'none',
            background: activeTripId === 'ALL' ? '#2563eb' : '#f3f4f6',
            color: activeTripId === 'ALL' ? '#ffffff' : '#4b5563',
            fontWeight: 'bold',
            cursor: 'pointer',
            whiteSpace: 'nowrap'
          }}
        >
          🌐 Alle Hotels ({hotels.length})
        </button>

        {trips.map(trip => (
          <button
            key={trip.id}
            onClick={() => setActiveTripId(String(trip.id))}
            style={{
              padding: '0.6rem 1.2rem',
              borderRadius: '8px',
              border: 'none',
              background: String(activeTripId).toLowerCase() === String(trip.id).toLowerCase() ? '#2563eb' : '#f3f4f6',
              color: String(activeTripId).toLowerCase() === String(trip.id).toLowerCase() ? '#ffffff' : '#4b5563',
              fontWeight: 'bold',
              cursor: 'pointer',
              whiteSpace: 'nowrap'
            }}
          >
            🌍 {trip.name}
          </button>
        ))}

        <button
          onClick={() => setShowAddTripModal(true)}
          style={{ padding: '0.6rem 1rem', borderRadius: '8px', border: '1px dashed #2563eb', background: '#eff6ff', color: '#2563eb', fontWeight: 'bold', cursor: 'pointer' }}
        >
          ➕ Neue Reise
        </button>
      </div>

      {/* PREISHISTORIE MODAL */}
      {selectedPriceHotel && (
        <div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.6)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000, padding: '1rem' }}>
          <div style={{ background: '#fff', padding: '1.8rem', borderRadius: '12px', maxWidth: '650px', width: '100%', boxShadow: '0 20px 25px -5px rgba(0,0,0,0.1)' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
              <div>
                <h3 style={{ margin: 0, fontSize: '1.3rem' }}>📈 Preishistorie: {selectedPriceHotel.name}</h3>
                <p style={{ margin: 0, color: '#6b7280', fontSize: '0.85rem' }}>Entwicklung aller erfassten Preis-Scans</p>
              </div>
              <button onClick={() => setSelectedPriceHotel(null)} style={{ background: '#f3f4f6', border: 'none', borderRadius: '50%', width: '32px', height: '32px', cursor: 'pointer', fontWeight: 'bold' }}>✕</button>
            </div>

            {loadingPrices ? (
              <p style={{ textAlign: 'center', padding: '2rem', color: '#6b7280' }}>Lade Preishistorie...</p>
            ) : priceHistory.length === 0 ? (
              <p style={{ textAlign: 'center', padding: '2rem', color: '#6b7280' }}>Noch keine Preishistorie vorhanden. Klicke oben auf "Preise aller Hotels aktualisieren".</p>
            ) : (
              <div>
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '1rem', marginBottom: '1.5rem', textAlign: 'center' }}>
                  <div style={{ background: '#f0fdf4', padding: '0.8rem', borderRadius: '8px', border: '1px solid #bbf7d0' }}>
                    <small style={{ color: '#166534', fontWeight: 'bold' }}>Tiefstpreis</small>
                    <div style={{ fontSize: '1.3rem', fontWeight: 'bold', color: '#15803d' }}>{minPrice} €</div>
                  </div>
                  <div style={{ background: '#eff6ff', padding: '0.8rem', borderRadius: '8px', border: '1px solid #bfdbfe' }}>
                    <small style={{ color: '#1e40af', fontWeight: 'bold' }}>Aktuellster Preis</small>
                    <div style={{ fontSize: '1.3rem', fontWeight: 'bold', color: '#1d4ed8' }}>{currentPrice} €</div>
                  </div>
                  <div style={{ background: '#fef2f2', padding: '0.8rem', borderRadius: '8px', border: '1px solid #fecaca' }}>
                    <small style={{ color: '#991b1b', fontWeight: 'bold' }}>Höchstpreis</small>
                    <div style={{ fontSize: '1.3rem', fontWeight: 'bold', color: '#b91c1c' }}>{maxPrice} €</div>
                  </div>
                </div>

                {priceHistory.length > 1 && (
                  <div style={{ background: '#fafafa', padding: '1rem', borderRadius: '8px', border: '1px solid #f3f4f6', marginBottom: '1.5rem' }}>
                    <svg viewBox="0 0 500 150" style={{ width: '100%', height: '160px', overflow: 'visible' }}>
                      <line x1="0" y1="20" x2="500" y2="20" stroke="#e5e7eb" strokeDasharray="4" />
                      <line x1="0" y1="130" x2="500" y2="130" stroke="#e5e7eb" strokeDasharray="4" />

                      {(() => {
                        const range = maxPrice - minPrice || 1;
                        const points = priceHistory.map((p, idx) => {
                          const x = (idx / (priceHistory.length - 1)) * 500;
                          const y = 130 - ((p.price - minPrice) / range) * 110;
                          return `${x},${y}`;
                        }).join(' ');

                        return (
                          <>
                            <polyline fill="none" stroke="#2563eb" strokeWidth="3" points={points} />
                            {priceHistory.map((p, idx) => {
                              const x = (idx / (priceHistory.length - 1)) * 500;
                              const y = 130 - ((p.price - minPrice) / range) * 110;
                              return (
                                <g key={p.id}>
                                  <circle cx={x} cy={y} r="5" fill="#2563eb" stroke="#ffffff" strokeWidth="2" />
                                  <text x={x} y={y - 10} fontSize="10" textAnchor="middle" fill="#374151" fontWeight="bold">{p.price}€</text>
                                </g>
                              );
                            })}
                          </>
                        );
                      })()}
                    </svg>
                  </div>
                )}

                <div style={{ maxHeight: '180px', overflowY: 'auto' }}>
                  <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.85rem' }}>
                    <thead>
                      <tr style={{ background: '#f3f4f6', textAlign: 'left' }}>
                        <th style={{ padding: '0.5rem' }}>Datum & Uhrzeit</th>
                        <th style={{ padding: '0.5rem' }}>Preis</th>
                        <th style={{ padding: '0.5rem' }}>Details</th>
                      </tr>
                    </thead>
                    <tbody>
                      {priceHistory.map(p => (
                        <tr key={p.id} style={{ borderBottom: '1px solid #f3f4f6' }}>
                          <td style={{ padding: '0.5rem' }}>{new Date(p.created_at).toLocaleString('de-DE')}</td>
                          <td style={{ padding: '0.5rem', fontWeight: 'bold', color: '#2563eb' }}>{p.price} €</td>
                          <td style={{ padding: '0.5rem', color: '#6b7280' }}>{p.nights || 1} Nächte, {p.persons || 2} Personen</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              </div>
            )}
          </div>
        </div>
      )}

      {/* MODAL: NEUE REISE */}
      {showAddTripModal && (
        <div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000, padding: '1rem' }}>
          <div style={{ background: '#fff', padding: '2rem', borderRadius: '12px', maxWidth: '400px', width: '100%' }}>
            <h3 style={{ marginTop: 0 }}>🌍 Neue Reise anlegen</h3>
            <form onSubmit={handleAddTrip}>
              <input 
                type="text" 
                required 
                placeholder="z. B. Indien 2026 oder Japan 2027" 
                value={newTripName} 
                onChange={e => setNewTripName(e.target.value)}
                style={{ width: '100%', padding: '0.7rem', borderRadius: '6px', border: '1px solid #d1d5db', marginBottom: '1rem' }}
              />
              <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}>
                <button type="button" onClick={() => setShowAddTripModal(false)} style={{ padding: '0.6rem 1rem', background: '#e5e7eb', border: 'none', borderRadius: '6px', cursor: 'pointer' }}>Abbrechen</button>
                <button type="submit" style={{ padding: '0.6rem 1rem', background: '#2563eb', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer', fontWeight: 'bold' }}>Anlegen</button>
              </div>
            </form>
          </div>
        </div>
      )}

      {/* MODAL: HOTEL HINZUFÜGEN */}
      {showAddHotelModal && (
        <div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000, padding: '1rem' }}>
          <div style={{ background: '#fff', padding: '2rem', borderRadius: '12px', maxWidth: '500px', width: '100%' }}>
            <h2 style={{ marginTop: 0, marginBottom: '0.5rem' }}>➕ Neues Hotel hinzufügen</h2>
            <p style={{ fontSize: '0.85rem', color: '#6b7280', marginBottom: '1.2rem' }}>Check-in/Check-out Datum wird automatisch aus dem Link gelesen.</p>
            
            <form onSubmit={handleAddHotel} style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
              <div>
                <label style={{ display: 'block', fontSize: '0.85rem', fontWeight: 'bold', marginBottom: '0.3rem' }}>Hotel Name (optional)</label>
                <input type="text" placeholder="z. B. Abad Fort" value={newHotelName} onChange={e => setNewHotelName(e.target.value)} style={{ width: '100%', padding: '0.6rem', borderRadius: '6px', border: '1px solid #d1d5db' }} />
              </div>

              <div>
                <label style={{ display: 'block', fontSize: '0.85rem', fontWeight: 'bold', marginBottom: '0.3rem' }}>Booking.com URL *</label>
                <input type="url" required placeholder="https://www.booking.com/hotel/..." value={newBookingUrl} onChange={e => setNewBookingUrl(e.target.value)} style={{ width: '100%', padding: '0.6rem', borderRadius: '6px', border: '1px solid #d1d5db' }} />
              </div>

              <div style={{ display: 'flex', gap: '0.8rem', justifyContent: 'flex-end', marginTop: '1rem' }}>
                <button type="button" onClick={() => setShowAddHotelModal(false)} style={{ padding: '0.6rem 1.2rem', background: '#e5e7eb', border: 'none', borderRadius: '6px', cursor: 'pointer' }}>Abbrechen</button>
                <button type="submit" disabled={isSubmitting} style={{ padding: '0.6rem 1.2rem', background: '#10b981', color: '#fff', border: 'none', borderRadius: '6px', cursor: 'pointer', fontWeight: 'bold' }}>
                  {isSubmitting ? 'Speichere...' : 'Speichern'}
                </button>
              </div>
            </form>
          </div>
        </div>
      )}

      {/* TIMELINE */}
      {loading ? (
        <p style={{ textAlign: 'center', color: '#6b7280' }}>Lade Daten...</p>
      ) : currentHotels.length === 0 ? (
        <div style={{ textAlign: 'center', padding: '3rem', background: '#f9fafb', borderRadius: '12px', border: '1px dashed #d1d5db' }}>
          <p style={{ color: '#6b7280', marginBottom: '1rem' }}>Keine Hotels für diese Auswahl gefunden.</p>
          <button onClick={() => setShowAddHotelModal(true)} style={{ background: '#10b981', color: '#fff', border: 'none', padding: '0.6rem 1.2rem', borderRadius: '6px', cursor: 'pointer' }}>
            Erstes Hotel hinzufügen
          </button>
        </div>
      ) : (
        <div style={{ position: 'relative', paddingLeft: '35px', borderLeft: '4px solid #3b82f6', marginLeft: '15px' }}>
          {currentHotels.map((hotel, index) => {
            const nights = calculateNights(hotel.checkin_date, hotel.checkout_date) || hotel.latest_nights || 1;
            const persons = hotel.latest_persons || 2;
            const totalPrice = hotel.latest_price !== null && hotel.latest_price !== undefined ? Number(hotel.latest_price) : null;

            // Bevorzuge vorberechnete Werte aus der DB, sonst dyn. Fallback
            const pricePerNight = hotel.latest_price_per_night 
              ? Math.round(Number(hotel.latest_price_per_night)) 
              : (totalPrice ? Math.round(totalPrice / nights) : null);

            const pricePerPersonPerNight = hotel.latest_price_per_person 
              ? Math.round(Number(hotel.latest_price_per_person)) 
              : (totalPrice ? Math.round(totalPrice / (nights * persons)) : null);

            return (
              <div key={hotel.id} style={{ position: 'relative', marginBottom: '2.5rem', opacity: hotel.is_active ? 1 : 0.5 }}>
                <div style={{ position: 'absolute', left: '-47px', top: '2px', width: '20px', height: '20px', borderRadius: '50%', background: hotel.is_active ? '#3b82f6' : '#9ca3af', border: '4px solid #ffffff', boxShadow: '0 0 0 2px #3b82f6' }} />

                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '0.5rem', flexWrap: 'wrap', gap: '0.5rem' }}>
                  <div style={{ fontSize: '0.9rem', fontWeight: 'bold', color: '#2563eb', display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
                    <span>📍 Stop #{index + 1}</span>
                    {hotel.trip_name && <span style={{ background: '#f3f4f6', color: '#4b5563', padding: '0.2rem 0.5rem', borderRadius: '6px', fontSize: '0.75rem' }}>{hotel.trip_name}</span>}
                    {hotel.checkin_date ? (
                      <span style={{ background: '#dbeafe', color: '#1e40af', padding: '0.2rem 0.6rem', borderRadius: '12px', fontSize: '0.8rem' }}>
                        🗓️ {formatDateGerman(hotel.checkin_date)} – {formatDateGerman(hotel.checkout_date) || '?'} ({nights} {nights === 1 ? 'Nacht' : 'Nächte'})
                      </span>
                    ) : (
                      <span style={{ color: '#9ca3af', fontSize: '0.8rem', fontStyle: 'italic' }}>(Warte auf Scan für Datum)</span>
                    )}
                  </div>

                  <div style={{ display: 'flex', gap: '0.8rem', alignItems: 'center' }}>
                    <label style={{ fontSize: '0.8rem', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '0.3rem', color: '#4b5563' }}>
                      <input type="checkbox" checked={hotel.is_active} onChange={() => toggleHotelActive(hotel)} />
                      {hotel.is_active ? 'Aktiv' : 'Inaktiv'}
                    </label>
                    <button onClick={() => deleteHotel(hotel.id, hotel.name)} style={{ background: 'none', border: 'none', color: '#ef4444', cursor: 'pointer', fontSize: '0.85rem' }}>
                      🗑️ Löschen
                    </button>
                  </div>
                </div>

                <div style={{ background: '#ffffff', border: '1px solid #e5e7eb', borderRadius: '12px', padding: '1.2rem', boxShadow: '0 4px 6px -1px rgba(0,0,0,0.05)', display: 'flex', gap: '1.2rem', flexWrap: 'wrap' }}>
                  {hotel.image_url && <img src={hotel.image_url} alt={hotel.name} style={{ width: '130px', height: '100px', objectFit: 'cover', borderRadius: '8px' }} />}

                  <div style={{ flex: 1, minWidth: '220px' }}>
                    <h3 style={{ margin: '0 0 0.4rem 0', fontSize: '1.2rem', color: '#1f2937' }}>{hotel.name}</h3>
                    <p style={{ margin: '0 0 0.5rem 0', fontSize: '0.85rem', color: '#6b7280' }}>📍 {hotel.address || 'Keine Adresse hinterlegt'}</p>
                    
                    {/* ANZEIGE DER DREI PREIS-WERTE */}
                    {totalPrice !== null ? (
                      <div style={{ display: 'flex', gap: '0.6rem', flexWrap: 'wrap', marginBottom: '0.8rem', background: '#ecfdf5', padding: '0.6rem 0.8rem', borderRadius: '8px', border: '1px solid #a7f3d0', alignItems: 'center' }}>
                        <span style={{ fontWeight: '800', color: '#047857', fontSize: '0.95rem' }}>
                          💰 Gesamt: {totalPrice.toLocaleString('de-DE')} €
                        </span>
                        <span style={{ color: '#059669', fontSize: '0.85rem', fontWeight: '600' }}>
                          | 🌙 {pricePerNight?.toLocaleString('de-DE')} € / Nacht
                        </span>
                        <span style={{ color: '#059669', fontSize: '0.85rem' }}>
                          | 👤 {pricePerPersonPerNight?.toLocaleString('de-DE')} € / P. & Nacht
                        </span>
                      </div>
                    ) : (
                      <div style={{ marginBottom: '0.8rem', fontSize: '0.85rem', color: '#6b7280', fontStyle: 'italic' }}>
                        💰 Noch kein Preis erfasst
                      </div>
                    )}

                    <div style={{ display: 'flex', gap: '1rem', fontSize: '0.85rem', marginBottom: '1rem', color: '#374151', flexWrap: 'wrap', alignItems: 'center' }}>
                      <span>⭐ <strong>{hotel.rating || 'N/A'}</strong>/10</span>
                      <span>💬 {hotel.review_count || 0} Bewertungen</span>
                      {hotel.room_size_m2 && <span>📐 {hotel.room_size_m2} m²</span>}
                    </div>

                    <div style={{ display: 'flex', gap: '0.8rem', flexWrap: 'wrap' }}>
                      <button
                        onClick={() => openPriceHistoryModal(hotel)}
                        style={{ background: '#0284c7', color: '#ffffff', border: 'none', padding: '0.5rem 1rem', borderRadius: '6px', cursor: 'pointer', fontWeight: '600', fontSize: '0.85rem' }}
                      >
                        📈 Preishistorie anzeigen
                      </button>

                      <button
                        onClick={() => scanSingleHotel(hotel.id)}
                        disabled={scanningHotelId === hotel.id || !hotel.is_active}
                        style={{ background: '#8b5cf6', color: '#ffffff', border: 'none', padding: '0.5rem 1rem', borderRadius: '6px', cursor: 'pointer', fontWeight: '600', fontSize: '0.85rem', opacity: (!hotel.is_active || scanningHotelId === hotel.id) ? 0.6 : 1 }}
                      >
                        {scanningHotelId === hotel.id ? '🔄 Scanne KI-Reviews...' : '🤖 KI-Reviews scannen'}
                      </button>

                      <a href={hotel.booking_url} target="_blank" rel="noopener noreferrer" style={{ color: '#2563eb', textDecoration: 'none', fontSize: '0.85rem', alignSelf: 'center', fontWeight: '500' }}>
                        🔗 Booking.com ↗
                      </a>
                    </div>
                  </div>
                </div>

                {(hotel.review_summary_positive || hotel.review_summary_bathroom) && (
                  <div style={{ marginTop: '0.8rem', paddingLeft: '0.2rem' }}>
                    <details style={{ background: '#f9fafb', padding: '0.8rem 1rem', borderRadius: '8px', border: '1px solid #e5e7eb' }}>
                      <summary style={{ cursor: 'pointer', fontWeight: 'bold', color: '#374151', fontSize: '0.9rem' }}>
                        📋 KI-Zusammenfassung 
                        <span style={{ fontWeight: 'normal', color: '#6b7280', marginLeft: '0.5rem' }}>
                          (Basierend auf <strong>{hotel.review_summary_count || 'mehreren'}</strong> analysierten Bewertungen)
                        </span>
                      </summary>

                      <div style={{ marginTop: '1rem', fontSize: '0.88rem', display: 'grid', gap: '0.8rem' }}>
                        {hotel.review_summary_positive && (
                          <div style={{ background: '#e8f5e9', padding: '0.8rem', borderRadius: '6px', borderLeft: '4px solid #2e7d32' }}>
                            <strong style={{ color: '#1b5e20' }}>💚 Bestnoten & Highlights:</strong>
                            <div style={{ whiteSpace: 'pre-line', color: '#2e7d32', marginTop: '0.3rem' }}>{hotel.review_summary_positive}</div>
                          </div>
                        )}
                        {hotel.review_summary_passable && (
                          <div style={{ background: '#fffde7', padding: '0.8rem', borderRadius: '6px', borderLeft: '4px solid #fbc02d' }}>
                            <strong style={{ color: '#f57f17' }}>🟡 Passabel & Neutrales:</strong>
                            <div style={{ whiteSpace: 'pre-line', color: '#f57f17', marginTop: '0.3rem' }}>{hotel.review_summary_passable}</div>
                          </div>
                        )}
                        {hotel.review_summary_negative && (
                          <div style={{ background: '#ffebee', padding: '0.8rem', borderRadius: '6px', borderLeft: '4px solid #c62828' }}>
                            <strong style={{ color: '#b71c1c' }}>🔴 Schlechteste & Mängel:</strong>
                            <div style={{ whiteSpace: 'pre-line', color: '#c62828', marginTop: '0.3rem' }}>{hotel.review_summary_negative}</div>
                          </div>
                        )}
                        <div style={{ background: '#e0f7fa', padding: '0.8rem', borderRadius: '6px', borderLeft: '4px solid #00acc1' }}>
                          <strong style={{ color: '#006064' }}>🚽 Bad & WC Fokus:</strong>
                          <div style={{ whiteSpace: 'pre-line', color: '#006064', marginTop: '0.3rem' }}>
                            {hotel.review_summary_bathroom || 'Keine spezifischen Anmerkungen zum Bad/WC gefunden.'}
                          </div>
                        </div>
                      </div>
                    </details>
                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

```

--- DATEI: src/lib/db.ts ---
```
import { Pool } from 'pg';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});

export const query = (text: string, params?: any[]) => pool.query(text, params);

```

--- DATEI: tsconfig.json ---
```
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

```
