Files
igny8/frontend/src/components/dashboard/NeedsAttentionBar.tsx
IGNY8 VPS (Salman) b9e4b6f7e2 final plolish phase 2
2025-12-27 15:25:05 +00:00

165 lines
6.1 KiB
TypeScript

/**
* NeedsAttentionBar - Compact alert bar for items needing user attention
*
* Shows at the top of dashboard when there are:
* - Content pending review
* - WordPress sync failures
* - Incomplete site setup
* - Automation failures
*
* Collapsible and only visible when there are items to show.
*/
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import { AlertIcon, ArrowRightIcon, ChevronDownIcon, RefreshIcon, CloseIcon } from '../../icons';
export type AttentionType = 'pending_review' | 'sync_failed' | 'setup_incomplete' | 'automation_failed' | 'credits_low';
export interface AttentionItem {
id: string;
type: AttentionType;
title: string;
count?: number;
actionLabel: string;
actionUrl?: string;
onAction?: () => void;
onRetry?: () => void;
severity: 'warning' | 'error' | 'info';
}
interface NeedsAttentionBarProps {
items: AttentionItem[];
onDismiss?: (id: string) => void;
className?: string;
}
const severityStyles = {
warning: {
bg: 'bg-amber-50 dark:bg-amber-500/10',
border: 'border-amber-200 dark:border-amber-500/30',
icon: 'text-amber-500',
text: 'text-amber-800 dark:text-amber-200',
button: 'bg-amber-100 hover:bg-amber-200 text-amber-700 dark:bg-amber-500/20 dark:hover:bg-amber-500/30 dark:text-amber-200',
},
error: {
bg: 'bg-red-50 dark:bg-red-500/10',
border: 'border-red-200 dark:border-red-500/30',
icon: 'text-red-500',
text: 'text-red-800 dark:text-red-200',
button: 'bg-red-100 hover:bg-red-200 text-red-700 dark:bg-red-500/20 dark:hover:bg-red-500/30 dark:text-red-200',
},
info: {
bg: 'bg-blue-50 dark:bg-blue-500/10',
border: 'border-blue-200 dark:border-blue-500/30',
icon: 'text-blue-500',
text: 'text-blue-800 dark:text-blue-200',
button: 'bg-blue-100 hover:bg-blue-200 text-blue-700 dark:bg-blue-500/20 dark:hover:bg-blue-500/30 dark:text-blue-200',
},
};
export default function NeedsAttentionBar({ items, onDismiss, className = '' }: NeedsAttentionBarProps) {
const [isCollapsed, setIsCollapsed] = useState(false);
if (items.length === 0) return null;
// Group items by severity for display priority
const errorItems = items.filter(i => i.severity === 'error');
const warningItems = items.filter(i => i.severity === 'warning');
const infoItems = items.filter(i => i.severity === 'info');
const sortedItems = [...errorItems, ...warningItems, ...infoItems];
const totalCount = items.reduce((sum, item) => sum + (item.count || 1), 0);
return (
<div className={`mb-6 ${className}`}>
{/* Header bar - always visible */}
<button
onClick={() => setIsCollapsed(!isCollapsed)}
className="w-full flex items-center justify-between p-3 bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/30 rounded-lg hover:bg-amber-100 dark:hover:bg-amber-500/15 transition-colors"
>
<div className="flex items-center gap-3">
<AlertIcon className="w-5 h-5 text-amber-500" />
<span className="text-sm font-medium text-amber-800 dark:text-amber-200">
{totalCount} item{totalCount !== 1 ? 's' : ''} need{totalCount === 1 ? 's' : ''} attention
</span>
</div>
<ChevronDownIcon
className={`w-5 h-5 text-amber-500 transition-transform ${isCollapsed ? '' : 'rotate-180'}`}
/>
</button>
{/* Expandable content */}
{!isCollapsed && (
<div className="mt-2 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-2">
{sortedItems.map((item) => {
const styles = severityStyles[item.severity];
return (
<div
key={item.id}
className={`flex items-center justify-between p-3 rounded-lg border ${styles.bg} ${styles.border}`}
>
<div className="flex items-center gap-2 flex-1 min-w-0">
<AlertIcon className={`w-4 h-4 flex-shrink-0 ${styles.icon}`} />
<span className={`text-sm font-medium truncate ${styles.text}`}>
{item.count ? `${item.count} ` : ''}{item.title}
</span>
</div>
<div className="flex items-center gap-1 flex-shrink-0 ml-2">
{item.onRetry && (
<button
onClick={(e) => {
e.stopPropagation();
item.onRetry?.();
}}
className={`p-1.5 rounded ${styles.button} transition-colors`}
title="Retry"
>
<RefreshIcon className="w-3.5 h-3.5" />
</button>
)}
{item.actionUrl ? (
<Link
to={item.actionUrl}
className={`px-2 py-1 text-xs font-medium rounded ${styles.button} transition-colors flex items-center gap-1`}
>
{item.actionLabel}
<ArrowRightIcon className="w-3 h-3" />
</Link>
) : item.onAction ? (
<button
onClick={(e) => {
e.stopPropagation();
item.onAction?.();
}}
className={`px-2 py-1 text-xs font-medium rounded ${styles.button} transition-colors`}
>
{item.actionLabel}
</button>
) : null}
{onDismiss && (
<button
onClick={(e) => {
e.stopPropagation();
onDismiss(item.id);
}}
className="p-1 rounded hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
title="Dismiss"
>
<CloseIcon className="w-3.5 h-3.5 text-gray-400" />
</button>
)}
</div>
</div>
);
})}
</div>
)}
</div>
);
}