final plolish phase 2
This commit is contained in:
164
frontend/src/components/dashboard/NeedsAttentionBar.tsx
Normal file
164
frontend/src/components/dashboard/NeedsAttentionBar.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user