MVP
This commit is contained in:
parent
d21f1c760b
commit
9d78fd16da
|
|
@ -1,138 +1,150 @@
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import {
|
||||||
import { Link } from "react-router-dom";
|
Mail,
|
||||||
import { Mail, Phone, MapPin, Facebook, Twitter, Linkedin, Instagram } from "lucide-react";
|
Phone,
|
||||||
|
MapPin,
|
||||||
|
Facebook,
|
||||||
|
Twitter,
|
||||||
|
Linkedin,
|
||||||
|
Instagram,
|
||||||
|
Calculator,
|
||||||
|
TrendingUp,
|
||||||
|
Shield,
|
||||||
|
Award
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
const Footer = () => {
|
const Footer = () => {
|
||||||
return (
|
return (
|
||||||
<footer className="bg-primary text-primary-foreground">
|
<footer className="bg-gray-900 text-white mt-20">
|
||||||
<div className="container mx-auto px-4 py-16">
|
<div className="container mx-auto px-4 py-12">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||||
{/* Company Info */}
|
{/* Company Info */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-8 h-8 bg-gradient-hero rounded-lg flex items-center justify-center">
|
<div className="w-8 h-8 bg-gradient-to-r from-orange-500 to-blue-500 rounded-lg flex items-center justify-center">
|
||||||
<span className="text-white font-bold text-lg">E</span>
|
<span className="text-white font-bold text-lg">E</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xl font-bold">EnergieProfis</span>
|
<span className="text-xl font-bold">EnergieProfis</span>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-gray-300 text-sm leading-relaxed">
|
||||||
<p className="text-primary-foreground/80 leading-relaxed">
|
Ihr vertrauensvoller Partner für erneuerbare Energien.
|
||||||
Ihr vertrauensvoller Partner für erneuerbare Energielösungen in Deutschland.
|
Finden Sie qualifizierte Solar- und Wind-Installateure in Ihrer Region.
|
||||||
Wir verbinden Sie mit den besten Installateuren für eine nachhaltige Zukunft.
|
|
||||||
</p>
|
</p>
|
||||||
|
<div className="flex space-x-4">
|
||||||
<div className="space-y-3">
|
<Button variant="ghost" size="sm" className="text-gray-300 hover:text-white p-2">
|
||||||
<div className="flex items-center space-x-3">
|
<Facebook className="w-5 h-5" />
|
||||||
<Phone className="w-5 h-5 text-solar" />
|
</Button>
|
||||||
<span>+49 (0) 800 123 4567</span>
|
<Button variant="ghost" size="sm" className="text-gray-300 hover:text-white p-2">
|
||||||
</div>
|
<Twitter className="w-5 h-5" />
|
||||||
<div className="flex items-center space-x-3">
|
</Button>
|
||||||
<Mail className="w-5 h-5 text-wind" />
|
<Button variant="ghost" size="sm" className="text-gray-300 hover:text-white p-2">
|
||||||
<span>info@energieprofis.de</span>
|
<Linkedin className="w-5 h-5" />
|
||||||
</div>
|
</Button>
|
||||||
<div className="flex items-center space-x-3">
|
<Button variant="ghost" size="sm" className="text-gray-300 hover:text-white p-2">
|
||||||
<MapPin className="w-5 h-5 text-geo" />
|
<Instagram className="w-5 h-5" />
|
||||||
<span>München, Deutschland</span>
|
</Button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Energy Types */}
|
{/* Quick Links */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-4">
|
||||||
<h3 className="text-lg font-semibold">Energielösungen</h3>
|
<h3 className="text-lg font-semibold">Schnellzugriff</h3>
|
||||||
<ul className="space-y-3">
|
<ul className="space-y-2 text-sm">
|
||||||
<li>
|
<li>
|
||||||
<Link to="/solar" className="text-primary-foreground/80 hover:text-solar transition-colors">
|
<a href="/" className="text-gray-300 hover:text-white transition-colors">
|
||||||
Solar-Installateure
|
Solar-Installation
|
||||||
</Link>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Link to="/wind" className="text-primary-foreground/80 hover:text-wind transition-colors">
|
<a href="/" className="text-gray-300 hover:text-white transition-colors">
|
||||||
Wind-Installateure
|
Windenergie
|
||||||
</Link>
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/" className="text-gray-300 hover:text-white transition-colors">
|
||||||
|
Installateur finden
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/" className="text-gray-300 hover:text-white transition-colors">
|
||||||
|
Förderungen 2025
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/" className="text-gray-300 hover:text-white transition-colors">
|
||||||
|
Kostenrechner
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Services */}
|
{/* Services */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-4">
|
||||||
<h3 className="text-lg font-semibold">Services</h3>
|
<h3 className="text-lg font-semibold">Unsere Services</h3>
|
||||||
<ul className="space-y-3">
|
<ul className="space-y-2 text-sm">
|
||||||
<li>
|
<li className="flex items-center gap-2 text-gray-300">
|
||||||
<Link to="/installateur-finden" className="text-primary-foreground/80 hover:text-white transition-colors">
|
<Calculator className="w-4 h-4" />
|
||||||
Installateur Finden
|
Solar-Einsparungsrechner
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li className="flex items-center gap-2 text-gray-300">
|
||||||
<Link to="/unternehmen-listen" className="text-primary-foreground/80 hover:text-white transition-colors">
|
<TrendingUp className="w-4 h-4" />
|
||||||
Unternehmen Listen
|
Windenergie-Rechner
|
||||||
</Link>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li className="flex items-center gap-2 text-gray-300">
|
||||||
<Link to="/preisvergleich" className="text-primary-foreground/80 hover:text-white transition-colors">
|
<Shield className="w-4 h-4" />
|
||||||
Preisvergleich
|
Verifizierte Installateure
|
||||||
</Link>
|
</li>
|
||||||
|
<li className="flex items-center gap-2 text-gray-300">
|
||||||
|
<Award className="w-4 h-4" />
|
||||||
|
Zertifizierte Fachbetriebe
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Newsletter */}
|
{/* Contact Info */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-4">
|
||||||
<h3 className="text-lg font-semibold">Newsletter</h3>
|
<h3 className="text-lg font-semibold">Kontakt</h3>
|
||||||
<p className="text-primary-foreground/80">
|
<div className="space-y-3 text-sm">
|
||||||
Bleiben Sie informiert über die neuesten Entwicklungen
|
<div className="flex items-center gap-3 text-gray-300">
|
||||||
in der erneuerbaren Energie.
|
<Mail className="w-4 h-4" />
|
||||||
</p>
|
<span>info@energieprofis.de</span>
|
||||||
|
</div>
|
||||||
<div className="space-y-3">
|
<div className="flex items-center gap-3 text-gray-300">
|
||||||
<Input
|
<Phone className="w-4 h-4" />
|
||||||
placeholder="Ihre E-Mail-Adresse"
|
<span>+49 (0) 800 123 456</span>
|
||||||
className="bg-white/10 border-white/20 text-white placeholder:text-white/60"
|
</div>
|
||||||
/>
|
<div className="flex items-start gap-3 text-gray-300">
|
||||||
<Button variant="hero" className="w-full bg-white text-primary hover:bg-white/90">
|
<MapPin className="w-4 h-4 mt-0.5" />
|
||||||
Anmelden
|
<span>
|
||||||
</Button>
|
EnergieProfis GmbH<br />
|
||||||
</div>
|
Musterstraße 123<br />
|
||||||
|
10115 Berlin
|
||||||
{/* Social Media */}
|
</span>
|
||||||
<div className="flex space-x-4">
|
</div>
|
||||||
<Button variant="ghost" size="icon" className="hover:bg-white/10">
|
|
||||||
<Facebook className="w-5 h-5" />
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" size="icon" className="hover:bg-white/10">
|
|
||||||
<Twitter className="w-5 h-5" />
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" size="icon" className="hover:bg-white/10">
|
|
||||||
<Linkedin className="w-5 h-5" />
|
|
||||||
</Button>
|
|
||||||
<Button variant="ghost" size="icon" className="hover:bg-white/10">
|
|
||||||
<Instagram className="w-5 h-5" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom Bar */}
|
{/* Bottom Section */}
|
||||||
<div className="border-t border-white/20 mt-12 pt-8">
|
<div className="border-t border-gray-700 mt-12 pt-8">
|
||||||
<div className="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
|
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
<p className="text-primary-foreground/60 text-sm">
|
<div className="text-sm text-gray-400">
|
||||||
© 2024 EnergieProfis. Alle Rechte vorbehalten.
|
© 2025 EnergieProfis. Alle Rechte vorbehalten.
|
||||||
</p>
|
</div>
|
||||||
|
|
||||||
<div className="flex space-x-6 text-sm">
|
<div className="flex space-x-6 text-sm">
|
||||||
<Link to="/datenschutz" className="text-primary-foreground/60 hover:text-white transition-colors">
|
<a href="/" className="text-gray-400 hover:text-white transition-colors">
|
||||||
Datenschutz
|
|
||||||
</Link>
|
|
||||||
<Link to="/agb" className="text-primary-foreground/60 hover:text-white transition-colors">
|
|
||||||
AGB
|
|
||||||
</Link>
|
|
||||||
<Link to="/impressum" className="text-primary-foreground/60 hover:text-white transition-colors">
|
|
||||||
Impressum
|
Impressum
|
||||||
</Link>
|
</a>
|
||||||
<Link to="/kontakt" className="text-primary-foreground/60 hover:text-white transition-colors">
|
<a href="/" className="text-gray-400 hover:text-white transition-colors">
|
||||||
Kontakt
|
Datenschutz
|
||||||
</Link>
|
</a>
|
||||||
|
<a href="/" className="text-gray-400 hover:text-white transition-colors">
|
||||||
|
AGB
|
||||||
|
</a>
|
||||||
|
<a href="/" className="text-gray-400 hover:text-white transition-colors">
|
||||||
|
Cookie-Einstellungen
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -14,28 +14,64 @@ export const installerService = {
|
||||||
// Get all installers with optional filters
|
// Get all installers with optional filters
|
||||||
async getInstallers(filters?: {
|
async getInstallers(filters?: {
|
||||||
energyType?: string;
|
energyType?: string;
|
||||||
location?: string;
|
bundesland?: string;
|
||||||
searchTerm?: string;
|
searchTerm?: string;
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
}) {
|
}) {
|
||||||
let query = supabase
|
// First, get the total count
|
||||||
|
let countQuery = supabase
|
||||||
|
.from('installers')
|
||||||
|
.select('*', { count: 'exact', head: true })
|
||||||
|
.eq('status', 'active');
|
||||||
|
|
||||||
|
if (filters?.energyType && filters.energyType !== 'all') {
|
||||||
|
countQuery = countQuery.eq('energy_type', filters.energyType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.bundesland && filters.bundesland !== 'all') {
|
||||||
|
countQuery = countQuery.ilike('location', `%${filters.bundesland}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.searchTerm) {
|
||||||
|
countQuery = countQuery.or(`name.ilike.%${filters.searchTerm}%,description.ilike.%${filters.searchTerm}%,specialties.cs.{${filters.searchTerm}}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { count, error: countError } = await countQuery;
|
||||||
|
|
||||||
|
if (countError) {
|
||||||
|
console.error('Error fetching installer count:', countError);
|
||||||
|
throw countError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then get the actual data with pagination
|
||||||
|
let dataQuery = supabase
|
||||||
.from('installers')
|
.from('installers')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('status', 'active')
|
.eq('status', 'active')
|
||||||
.order('rating', { ascending: false });
|
.order('rating', { ascending: false });
|
||||||
|
|
||||||
if (filters?.energyType && filters.energyType !== 'all') {
|
if (filters?.energyType && filters.energyType !== 'all') {
|
||||||
query = query.eq('energy_type', filters.energyType);
|
dataQuery = dataQuery.eq('energy_type', filters.energyType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters?.location) {
|
if (filters?.bundesland && filters.bundesland !== 'all') {
|
||||||
query = query.ilike('location', `%${filters.location}%`);
|
dataQuery = dataQuery.ilike('location', `%${filters.bundesland}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters?.searchTerm) {
|
if (filters?.searchTerm) {
|
||||||
query = query.or(`name.ilike.%${filters.searchTerm}%,description.ilike.%${filters.searchTerm}%,specialties.cs.{${filters.searchTerm}}`);
|
dataQuery = dataQuery.or(`name.ilike.%${filters.searchTerm}%,description.ilike.%${filters.searchTerm}%,specialties.cs.{${filters.searchTerm}}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await query;
|
// Apply pagination
|
||||||
|
if (filters?.limit) {
|
||||||
|
dataQuery = dataQuery.limit(filters.limit);
|
||||||
|
}
|
||||||
|
if (filters?.offset) {
|
||||||
|
dataQuery = dataQuery.range(filters.offset, filters.offset + (filters.limit || 10) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await dataQuery;
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('Error fetching installers:', error);
|
console.error('Error fetching installers:', error);
|
||||||
|
|
@ -48,7 +84,7 @@ export const installerService = {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return { data: data || [], totalCount: count || 0 };
|
||||||
},
|
},
|
||||||
|
|
||||||
// Get installer by ID
|
// Get installer by ID
|
||||||
|
|
@ -182,14 +218,16 @@ export const useInstallers = () => {
|
||||||
|
|
||||||
const fetchInstallers = async (filters?: {
|
const fetchInstallers = async (filters?: {
|
||||||
energyType?: string;
|
energyType?: string;
|
||||||
location?: string;
|
bundesland?: string;
|
||||||
searchTerm?: string;
|
searchTerm?: string;
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const data = await installerService.getInstallers(filters);
|
const result = await installerService.getInstallers(filters);
|
||||||
setInstallers(data || []);
|
setInstallers(result.data || []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'An error occurred');
|
setError(err instanceof Error ? err.message : 'An error occurred');
|
||||||
setInstallers([]);
|
setInstallers([]);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Search, MapPin, Star, Phone, Mail, Globe, Filter, AlertCircle, Calculator, Award, Shield, TrendingUp, Map, Users, Clock, CheckCircle } from "lucide-react";
|
import { Search, MapPin, Star, Phone, Mail, Globe, Filter, AlertCircle, Calculator, Award, Shield, TrendingUp, Map, Users, Clock, CheckCircle } from "lucide-react";
|
||||||
import Header from "@/components/Header";
|
import Header from "@/components/Header";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
import { installerService, analyticsService } from "@/lib/database";
|
import { installerService, analyticsService } from "@/lib/database";
|
||||||
import { cleanAndReseedDatabase } from "@/lib/cleanDatabase";
|
import { cleanAndReseedDatabase } from "@/lib/cleanDatabase";
|
||||||
import { debugDatabase, forceDeleteAll, testConnection } from "@/lib/debugDatabase";
|
import { debugDatabase, forceDeleteAll, testConnection } from "@/lib/debugDatabase";
|
||||||
|
|
@ -22,7 +23,13 @@ const InstallateurFinden = () => {
|
||||||
|
|
||||||
const [searchTerm, setSearchTerm] = useState(searchParams.get("search") || "");
|
const [searchTerm, setSearchTerm] = useState(searchParams.get("search") || "");
|
||||||
const [energyType, setEnergyType] = useState(searchParams.get("type") || "all");
|
const [energyType, setEnergyType] = useState(searchParams.get("type") || "all");
|
||||||
const [location, setLocation] = useState(searchParams.get("location") || "");
|
const [bundesland, setBundesland] = useState(searchParams.get("bundesland") || "all");
|
||||||
|
|
||||||
|
// Pagination state
|
||||||
|
const [displayedInstallers, setDisplayedInstallers] = useState<Installer[]>([]);
|
||||||
|
const [currentPage, setCurrentPage] = useState(0);
|
||||||
|
const [totalInstallers, setTotalInstallers] = useState(0);
|
||||||
|
const [showAll, setShowAll] = useState(false);
|
||||||
|
|
||||||
// SEO: Add structured data for the page
|
// SEO: Add structured data for the page
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -33,8 +40,8 @@ const InstallateurFinden = () => {
|
||||||
"name": "Solar- und Wind-Installateure in Deutschland",
|
"name": "Solar- und Wind-Installateure in Deutschland",
|
||||||
"description": "Qualifizierte Fachbetriebe für Photovoltaik und Windenergie finden und vergleichen",
|
"description": "Qualifizierte Fachbetriebe für Photovoltaik und Windenergie finden und vergleichen",
|
||||||
"url": window.location.href,
|
"url": window.location.href,
|
||||||
"numberOfItems": installers.length,
|
"numberOfItems": displayedInstallers.length,
|
||||||
"itemListElement": installers.map((installer, index) => ({
|
"itemListElement": displayedInstallers.map((installer, index) => ({
|
||||||
"@type": "ListItem",
|
"@type": "ListItem",
|
||||||
"position": index + 1,
|
"position": index + 1,
|
||||||
"item": {
|
"item": {
|
||||||
|
|
@ -152,18 +159,27 @@ const InstallateurFinden = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Load installers from database
|
// Load installers from database
|
||||||
const loadInstallers = async () => {
|
const loadInstallers = async (resetPagination = true) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
if (resetPagination) {
|
||||||
|
setCurrentPage(0);
|
||||||
|
setShowAll(false);
|
||||||
|
}
|
||||||
|
|
||||||
const filters = {
|
const filters = {
|
||||||
energyType: energyType && energyType !== "all" ? energyType : undefined,
|
energyType: energyType && energyType !== "all" ? energyType : undefined,
|
||||||
location: location || undefined,
|
bundesland: bundesland && bundesland !== "all" ? bundesland : undefined,
|
||||||
searchTerm: searchTerm || undefined
|
searchTerm: searchTerm || undefined,
|
||||||
|
limit: showAll ? undefined : 10,
|
||||||
|
offset: showAll ? undefined : (currentPage * 10)
|
||||||
};
|
};
|
||||||
|
|
||||||
const data = await installerService.getInstallers(filters);
|
const result = await installerService.getInstallers(filters);
|
||||||
|
const data = result.data;
|
||||||
|
const totalCount = result.totalCount;
|
||||||
|
|
||||||
// If no installers found, automatically seed the database
|
// If no installers found, automatically seed the database
|
||||||
if (!data || data.length === 0) {
|
if (!data || data.length === 0) {
|
||||||
|
|
@ -171,19 +187,38 @@ const InstallateurFinden = () => {
|
||||||
try {
|
try {
|
||||||
await cleanAndReseedDatabase();
|
await cleanAndReseedDatabase();
|
||||||
// Try to load installers again after seeding
|
// Try to load installers again after seeding
|
||||||
const reseededData = await installerService.getInstallers(filters);
|
const reseededResult = await installerService.getInstallers(filters);
|
||||||
setInstallers(reseededData || []);
|
if (resetPagination) {
|
||||||
|
setInstallers(reseededResult.data || []);
|
||||||
|
setDisplayedInstallers(reseededResult.data || []);
|
||||||
|
setTotalInstallers(reseededResult.totalCount || 0);
|
||||||
|
} else {
|
||||||
|
const newInstallers = [...displayedInstallers, ...(reseededResult.data || [])];
|
||||||
|
setDisplayedInstallers(newInstallers);
|
||||||
|
setTotalInstallers(reseededResult.totalCount || 0);
|
||||||
|
}
|
||||||
} catch (seedError) {
|
} catch (seedError) {
|
||||||
console.error('Error seeding database:', seedError);
|
console.error('Error seeding database:', seedError);
|
||||||
setError('Datenbank wird initialisiert. Bitte versuchen Sie es in wenigen Sekunden erneut.');
|
setError('Datenbank wird initialisiert. Bitte versuchen Sie es in wenigen Sekunden erneut.');
|
||||||
setInstallers([]);
|
setInstallers([]);
|
||||||
|
setDisplayedInstallers([]);
|
||||||
|
setTotalInstallers(0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setInstallers(data);
|
if (resetPagination) {
|
||||||
|
setInstallers(data);
|
||||||
|
setDisplayedInstallers(data);
|
||||||
|
setTotalInstallers(totalCount);
|
||||||
|
} else {
|
||||||
|
// Append new data for pagination
|
||||||
|
const newInstallers = [...displayedInstallers, ...data];
|
||||||
|
setDisplayedInstallers(newInstallers);
|
||||||
|
setTotalInstallers(totalCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track search event
|
// Track search event
|
||||||
if (searchTerm || energyType !== "all" || location) {
|
if (searchTerm || energyType !== "all" || bundesland !== "all") {
|
||||||
analyticsService.trackEvent({
|
analyticsService.trackEvent({
|
||||||
event_type: 'installer_search',
|
event_type: 'installer_search',
|
||||||
page_url: window.location.pathname,
|
page_url: window.location.pathname,
|
||||||
|
|
@ -196,6 +231,7 @@ const InstallateurFinden = () => {
|
||||||
console.error('Error loading installers:', err);
|
console.error('Error loading installers:', err);
|
||||||
setError('Fehler beim Laden der Installateure. Bitte versuchen Sie es später erneut.');
|
setError('Fehler beim Laden der Installateure. Bitte versuchen Sie es später erneut.');
|
||||||
setInstallers([]);
|
setInstallers([]);
|
||||||
|
setDisplayedInstallers([]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -209,25 +245,65 @@ const InstallateurFinden = () => {
|
||||||
// Reload when filters change
|
// Reload when filters change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
loadInstallers();
|
loadInstallers(true); // Reset pagination when filters change
|
||||||
}, 500); // Debounce search
|
}, 500); // Debounce search
|
||||||
|
|
||||||
return () => clearTimeout(timeoutId);
|
return () => clearTimeout(timeoutId);
|
||||||
}, [searchTerm, energyType, location]);
|
}, [searchTerm, energyType, bundesland]);
|
||||||
|
|
||||||
// Update URL params when filters change
|
// Update URL params when filters change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (searchTerm) params.set("search", searchTerm);
|
if (searchTerm) params.set("search", searchTerm);
|
||||||
if (energyType && energyType !== "all") params.set("type", energyType);
|
if (energyType && energyType !== "all") params.set("type", energyType);
|
||||||
if (location) params.set("location", location);
|
if (bundesland && bundesland !== "all") params.set("bundesland", bundesland);
|
||||||
setSearchParams(params);
|
setSearchParams(params);
|
||||||
}, [searchTerm, energyType, location, setSearchParams]);
|
}, [searchTerm, energyType, bundesland, setSearchParams]);
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
setSearchTerm("");
|
setSearchTerm("");
|
||||||
setEnergyType("all");
|
setEnergyType("all");
|
||||||
setLocation("");
|
setBundesland("all");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle "Show More" button click
|
||||||
|
const handleShowMore = async (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault(); // Prevent default button behavior
|
||||||
|
e.stopPropagation(); // Stop event bubbling
|
||||||
|
|
||||||
|
if (currentPage >= 2) {
|
||||||
|
// After 3 clicks (30 installers), show all remaining
|
||||||
|
setShowAll(true);
|
||||||
|
setCurrentPage(0);
|
||||||
|
await loadInstallers(false);
|
||||||
|
} else {
|
||||||
|
// Load next 10 installers
|
||||||
|
const nextPage = currentPage + 1;
|
||||||
|
setCurrentPage(nextPage);
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const filters = {
|
||||||
|
energyType: energyType && energyType !== "all" ? energyType : undefined,
|
||||||
|
bundesland: bundesland && bundesland !== "all" ? bundesland : undefined,
|
||||||
|
searchTerm: searchTerm || undefined,
|
||||||
|
limit: 10,
|
||||||
|
offset: nextPage * 10
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await installerService.getInstallers(filters);
|
||||||
|
const newInstallers = [...displayedInstallers, ...(result.data || [])];
|
||||||
|
setDisplayedInstallers(newInstallers);
|
||||||
|
setTotalInstallers(result.totalCount || 0);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error loading more installers:', err);
|
||||||
|
setError('Fehler beim Laden weiterer Installateure. Bitte versuchen Sie es später erneut.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Test connection
|
// Test connection
|
||||||
|
|
@ -258,7 +334,7 @@ const InstallateurFinden = () => {
|
||||||
try {
|
try {
|
||||||
await cleanAndReseedDatabase();
|
await cleanAndReseedDatabase();
|
||||||
alert('Deutsche Installateure erfolgreich geladen!');
|
alert('Deutsche Installateure erfolgreich geladen!');
|
||||||
loadInstallers(); // Reload the data
|
loadInstallers(true); // Reload the data with pagination reset
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error seeding database:', error);
|
console.error('Error seeding database:', error);
|
||||||
alert('Fehler beim Laden der Installateure. Details in der Konsole.');
|
alert('Fehler beim Laden der Installateure. Details in der Konsole.');
|
||||||
|
|
@ -482,15 +558,30 @@ const InstallateurFinden = () => {
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<div className="relative">
|
<Select value={bundesland} onValueChange={setBundesland}>
|
||||||
<MapPin className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
|
<SelectTrigger>
|
||||||
<Input
|
<SelectValue placeholder="Bundesland wählen" />
|
||||||
placeholder="PLZ oder Stadt"
|
</SelectTrigger>
|
||||||
value={location}
|
<SelectContent>
|
||||||
onChange={(e) => setLocation(e.target.value)}
|
<SelectItem value="all">Alle Bundesländer</SelectItem>
|
||||||
className="pl-10"
|
<SelectItem value="Baden-Württemberg">Baden-Württemberg</SelectItem>
|
||||||
/>
|
<SelectItem value="Bayern">Bayern</SelectItem>
|
||||||
</div>
|
<SelectItem value="Berlin">Berlin</SelectItem>
|
||||||
|
<SelectItem value="Brandenburg">Brandenburg</SelectItem>
|
||||||
|
<SelectItem value="Bremen">Bremen</SelectItem>
|
||||||
|
<SelectItem value="Hamburg">Hamburg</SelectItem>
|
||||||
|
<SelectItem value="Hessen">Hessen</SelectItem>
|
||||||
|
<SelectItem value="Mecklenburg-Vorpommern">Mecklenburg-Vorpommern</SelectItem>
|
||||||
|
<SelectItem value="Niedersachsen">Niedersachsen</SelectItem>
|
||||||
|
<SelectItem value="Nordrhein-Westfalen">Nordrhein-Westfalen</SelectItem>
|
||||||
|
<SelectItem value="Rheinland-Pfalz">Rheinland-Pfalz</SelectItem>
|
||||||
|
<SelectItem value="Saarland">Saarland</SelectItem>
|
||||||
|
<SelectItem value="Sachsen">Sachsen</SelectItem>
|
||||||
|
<SelectItem value="Sachsen-Anhalt">Sachsen-Anhalt</SelectItem>
|
||||||
|
<SelectItem value="Schleswig-Holstein">Schleswig-Holstein</SelectItem>
|
||||||
|
<SelectItem value="Thüringen">Thüringen</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
<Button onClick={handleReset} variant="outline" className="w-full">
|
<Button onClick={handleReset} variant="outline" className="w-full">
|
||||||
Filter zurücksetzen
|
Filter zurücksetzen
|
||||||
|
|
@ -533,7 +624,7 @@ const InstallateurFinden = () => {
|
||||||
<h3 className="text-lg font-semibold text-red-800 mb-2">Fehler beim Laden</h3>
|
<h3 className="text-lg font-semibold text-red-800 mb-2">Fehler beim Laden</h3>
|
||||||
<p className="text-red-700 mb-4">{error}</p>
|
<p className="text-red-700 mb-4">{error}</p>
|
||||||
<div className="flex gap-2 justify-center">
|
<div className="flex gap-2 justify-center">
|
||||||
<Button onClick={loadInstallers} variant="outline" className="border-red-300 text-red-700 hover:bg-red-100">
|
<Button onClick={() => loadInstallers(true)} variant="outline" className="border-red-300 text-red-700 hover:bg-red-100">
|
||||||
Erneut versuchen
|
Erneut versuchen
|
||||||
</Button>
|
</Button>
|
||||||
{process.env.NODE_ENV === 'development' && (
|
{process.env.NODE_ENV === 'development' && (
|
||||||
|
|
@ -562,13 +653,13 @@ const InstallateurFinden = () => {
|
||||||
{/* Results */}
|
{/* Results */}
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
{installers.length} Installateur{installers.length !== 1 ? 'e' : ''} gefunden
|
{displayedInstallers.length} von {totalInstallers} Installateur{totalInstallers !== 1 ? 'e' : ''} angezeigt
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Installer Cards */}
|
{/* Installer Cards */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
{installers.map((installer) => (
|
{displayedInstallers.map((installer) => (
|
||||||
<Card key={installer.id} className="hover:shadow-lg transition-shadow">
|
<Card key={installer.id} className="hover:shadow-lg transition-shadow">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex justify-between items-start">
|
<div className="flex justify-between items-start">
|
||||||
|
|
@ -677,7 +768,22 @@ const InstallateurFinden = () => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{installers.length === 0 && (
|
{/* Show More Button */}
|
||||||
|
{displayedInstallers.length > 0 && displayedInstallers.length < totalInstallers && (
|
||||||
|
<div className="text-center mt-12 mb-16">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={handleShowMore}
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
className="px-12 py-3 text-lg font-semibold"
|
||||||
|
>
|
||||||
|
{currentPage >= 2 ? 'Alle anzeigen' : 'Mehr anzeigen'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{displayedInstallers.length === 0 && (
|
||||||
<Card className="text-center py-12">
|
<Card className="text-center py-12">
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<p className="text-muted-foreground text-lg mb-4">
|
<p className="text-muted-foreground text-lg mb-4">
|
||||||
|
|
@ -759,6 +865,9 @@ const InstallateurFinden = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue