Initial commit - QR Master application
This commit is contained in:
258
src/app/(app)/dashboard/page.tsx
Normal file
258
src/app/(app)/dashboard/page.tsx
Normal file
@@ -0,0 +1,258 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { StatsGrid } from '@/components/dashboard/StatsGrid';
|
||||
import { QRCodeCard } from '@/components/dashboard/QRCodeCard';
|
||||
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
|
||||
interface QRCodeData {
|
||||
id: string;
|
||||
title: string;
|
||||
type: 'STATIC' | 'DYNAMIC';
|
||||
contentType: string;
|
||||
content?: any;
|
||||
slug: string;
|
||||
status: 'ACTIVE' | 'PAUSED';
|
||||
createdAt: string;
|
||||
scans: number;
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const { t } = useTranslation();
|
||||
const [qrCodes, setQrCodes] = useState<QRCodeData[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [stats, setStats] = useState({
|
||||
totalScans: 0,
|
||||
activeQRCodes: 0,
|
||||
conversionRate: 0,
|
||||
});
|
||||
|
||||
const mockQRCodes = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Support Phone',
|
||||
type: 'DYNAMIC' as const,
|
||||
contentType: 'PHONE',
|
||||
slug: 'support-phone-demo',
|
||||
status: 'ACTIVE' as const,
|
||||
createdAt: '2025-08-07T10:00:00Z',
|
||||
scans: 0,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Event Details',
|
||||
type: 'DYNAMIC' as const,
|
||||
contentType: 'URL',
|
||||
slug: 'event-details-demo',
|
||||
status: 'ACTIVE' as const,
|
||||
createdAt: '2025-08-07T10:01:00Z',
|
||||
scans: 0,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Product Demo',
|
||||
type: 'DYNAMIC' as const,
|
||||
contentType: 'URL',
|
||||
slug: 'product-demo-qr',
|
||||
status: 'ACTIVE' as const,
|
||||
createdAt: '2025-08-07T10:02:00Z',
|
||||
scans: 0,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: 'Company Website',
|
||||
type: 'DYNAMIC' as const,
|
||||
contentType: 'URL',
|
||||
slug: 'company-website-qr',
|
||||
status: 'ACTIVE' as const,
|
||||
createdAt: '2025-08-07T10:03:00Z',
|
||||
scans: 0,
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
title: 'Contact Email',
|
||||
type: 'DYNAMIC' as const,
|
||||
contentType: 'EMAIL',
|
||||
slug: 'contact-email-qr',
|
||||
status: 'ACTIVE' as const,
|
||||
createdAt: '2025-08-07T10:04:00Z',
|
||||
scans: 0,
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
title: 'Event Details',
|
||||
type: 'DYNAMIC' as const,
|
||||
contentType: 'URL',
|
||||
slug: 'event-details-dup',
|
||||
status: 'ACTIVE' as const,
|
||||
createdAt: '2025-08-07T10:05:00Z',
|
||||
scans: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const blogPosts = [
|
||||
{
|
||||
title: 'QR-Codes im Restaurant: Die digitale Revolution der Speisekarte',
|
||||
excerpt: 'Erfahren Sie, wie QR-Codes die Gastronomie revolutionieren...',
|
||||
readTime: '5 Min',
|
||||
slug: 'qr-codes-im-restaurant',
|
||||
},
|
||||
{
|
||||
title: 'Dynamische vs. Statische QR-Codes: Was ist der Unterschied?',
|
||||
excerpt: 'Ein umfassender Vergleich zwischen dynamischen und statischen QR-Codes...',
|
||||
readTime: '3 Min',
|
||||
slug: 'dynamische-vs-statische-qr-codes',
|
||||
},
|
||||
{
|
||||
title: 'QR-Code Marketing-Strategien für 2024',
|
||||
excerpt: 'Die besten Marketing-Strategien mit QR-Codes für Ihr Unternehmen...',
|
||||
readTime: '7 Min',
|
||||
slug: 'qr-code-marketing-strategien',
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
// Load real QR codes from API
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await fetch('/api/qrs');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setQrCodes(data);
|
||||
|
||||
// Calculate real stats
|
||||
const totalScans = data.reduce((sum: number, qr: QRCodeData) => sum + (qr.scans || 0), 0);
|
||||
const activeQRCodes = data.filter((qr: QRCodeData) => qr.status === 'ACTIVE').length;
|
||||
const conversionRate = activeQRCodes > 0 ? Math.round((totalScans / (activeQRCodes * 100)) * 100) : 0;
|
||||
|
||||
setStats({
|
||||
totalScans,
|
||||
activeQRCodes,
|
||||
conversionRate: Math.min(conversionRate, 100), // Cap at 100%
|
||||
});
|
||||
} else {
|
||||
// If not logged in, show zeros
|
||||
setQrCodes([]);
|
||||
setStats({
|
||||
totalScans: 0,
|
||||
activeQRCodes: 0,
|
||||
conversionRate: 0,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
setQrCodes([]);
|
||||
setStats({
|
||||
totalScans: 0,
|
||||
activeQRCodes: 0,
|
||||
conversionRate: 0,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const handleEdit = (id: string) => {
|
||||
console.log('Edit QR:', id);
|
||||
};
|
||||
|
||||
const handleDuplicate = (id: string) => {
|
||||
console.log('Duplicate QR:', id);
|
||||
};
|
||||
|
||||
const handlePause = (id: string) => {
|
||||
console.log('Pause QR:', id);
|
||||
};
|
||||
|
||||
const handleDelete = (id: string) => {
|
||||
console.log('Delete QR:', id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-900">{t('dashboard.title')}</h1>
|
||||
<p className="text-gray-600 mt-2">{t('dashboard.subtitle')}</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<StatsGrid stats={stats} />
|
||||
|
||||
{/* Recent QR Codes */}
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-xl font-semibold text-gray-900">{t('dashboard.recent_codes')}</h2>
|
||||
<Link href="/create">
|
||||
<Button>Create New QR Code</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{[1, 2, 3, 4, 5, 6].map((i) => (
|
||||
<Card key={i}>
|
||||
<CardContent className="p-4">
|
||||
<div className="animate-pulse">
|
||||
<div className="h-4 bg-gray-200 rounded w-3/4 mb-3"></div>
|
||||
<div className="h-24 bg-gray-200 rounded mb-3"></div>
|
||||
<div className="space-y-2">
|
||||
<div className="h-3 bg-gray-200 rounded"></div>
|
||||
<div className="h-3 bg-gray-200 rounded"></div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{qrCodes.map((qr) => (
|
||||
<QRCodeCard
|
||||
key={qr.id}
|
||||
qr={qr}
|
||||
onEdit={handleEdit}
|
||||
onDuplicate={handleDuplicate}
|
||||
onPause={handlePause}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Blog & Resources */}
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-6">{t('dashboard.blog_resources')}</h2>
|
||||
<div className="grid md:grid-cols-3 gap-6">
|
||||
{blogPosts.map((post) => (
|
||||
<Card key={post.slug} hover>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Badge variant="info">{post.readTime}</Badge>
|
||||
</div>
|
||||
<CardTitle className="text-lg">{post.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-gray-600 text-sm">{post.excerpt}</p>
|
||||
<Link
|
||||
href={`/blog/${post.slug}`}
|
||||
className="text-primary-600 hover:text-primary-700 text-sm font-medium mt-3 inline-block"
|
||||
>
|
||||
Read more →
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user