111 lines
3.7 KiB
TypeScript
111 lines
3.7 KiB
TypeScript
'use client';
|
|
|
|
import { Service } from '@/lib/data';
|
|
|
|
interface ServiceCardProps {
|
|
service: Service;
|
|
}
|
|
|
|
const STATUS_CONFIG = {
|
|
online: {
|
|
bgColor: 'bg-green-50 dark:bg-green-950',
|
|
textColor: 'text-green-700 dark:text-green-300',
|
|
dotColor: 'bg-green-500',
|
|
label: 'Online',
|
|
},
|
|
warning: {
|
|
bgColor: 'bg-amber-50 dark:bg-amber-950',
|
|
textColor: 'text-amber-700 dark:text-amber-300',
|
|
dotColor: 'bg-amber-500',
|
|
label: 'Warnung',
|
|
},
|
|
offline: {
|
|
bgColor: 'bg-red-50 dark:bg-red-950',
|
|
textColor: 'text-red-700 dark:text-red-300',
|
|
dotColor: 'bg-red-500',
|
|
label: 'Offline',
|
|
},
|
|
} as const;
|
|
|
|
/**
|
|
* ServiceCard - Zeigt einen einzelnen Service mit Status, Icon und Öffnen-Button
|
|
* @param service - Das Service-Objekt mit Name, Beschreibung, Status, etc.
|
|
*/
|
|
export function ServiceCard({ service }: ServiceCardProps) {
|
|
const config = STATUS_CONFIG[service.status];
|
|
|
|
return (
|
|
<a
|
|
href={service.url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="group block"
|
|
>
|
|
<div className="h-full bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg shadow-sm hover:shadow-md transition-all duration-300 overflow-hidden hover:border-slate-300 dark:hover:border-slate-600">
|
|
<div className="p-6 flex flex-col gap-4 h-full">
|
|
{/* Header mit Icon, Name, Kategorie und Status */}
|
|
<div className="flex items-start justify-between gap-4">
|
|
<div className="flex-1 flex items-center gap-3">
|
|
{service.icon && (
|
|
<span className="text-2xl flex-shrink-0" aria-hidden="true">
|
|
{service.icon}
|
|
</span>
|
|
)}
|
|
<div className="min-w-0 flex-1">
|
|
<h3 className="text-lg font-semibold text-slate-900 dark:text-white group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors truncate">
|
|
{service.name}
|
|
</h3>
|
|
<p className="text-xs text-slate-500 dark:text-slate-400 truncate">
|
|
{service.category}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Status Badge */}
|
|
<div
|
|
className={`flex items-center gap-2 px-3 py-1 rounded-full whitespace-nowrap flex-shrink-0 ${config.bgColor}`}
|
|
>
|
|
<span
|
|
className={`w-2 h-2 rounded-full ${config.dotColor} animate-pulse`}
|
|
/>
|
|
<span className={`text-xs font-medium ${config.textColor}`}>
|
|
{config.label}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Beschreibung */}
|
|
<p className="text-sm text-slate-600 dark:text-slate-400 flex-grow line-clamp-2">
|
|
{service.description}
|
|
</p>
|
|
|
|
{/* Öffnen Button */}
|
|
<button
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
window.open(service.url, '_blank');
|
|
}}
|
|
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200 flex items-center justify-center gap-2 group/btn"
|
|
aria-label={`${service.name} öffnen`}
|
|
>
|
|
Öffnen
|
|
<svg
|
|
className="w-4 h-4 group-hover/btn:translate-x-1 transition-transform"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
aria-hidden="true"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
);
|
|
}
|