New alert service
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
interface LoaderProps {
|
||||
export interface LoaderProps {
|
||||
size?: 'sm' | 'md' | 'lg' | 'default';
|
||||
text?: string;
|
||||
className?: string;
|
||||
|
||||
@@ -16,14 +16,15 @@ import {
|
||||
Phone,
|
||||
ExternalLink
|
||||
} from 'lucide-react';
|
||||
import { EnrichedAlert, AlertTypeClass } from '../../../types/alerts';
|
||||
import { Alert, AlertTypeClass, getPriorityColor, getTypeClassBadgeVariant, formatTimeUntilConsequence } from '../../../api/types/events';
|
||||
import { renderEventTitle, renderEventMessage, renderActionLabel, renderAIReasoning } from '../../../utils/i18n/alertRendering';
|
||||
import { useSmartActionHandler } from '../../../utils/smartActionHandlers';
|
||||
import { getPriorityColor, getTypeClassBadgeVariant, formatTimeUntilConsequence } from '../../../types/alerts';
|
||||
import { useAuthUser } from '../../../stores/auth.store';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface NotificationPanelProps {
|
||||
notifications: NotificationData[];
|
||||
enrichedAlerts?: EnrichedAlert[];
|
||||
enrichedAlerts?: Alert[];
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onMarkAsRead: (id: string) => void;
|
||||
@@ -50,12 +51,13 @@ const formatTimestamp = (timestamp: string) => {
|
||||
|
||||
// Enriched Alert Item Component
|
||||
const EnrichedAlertItem: React.FC<{
|
||||
alert: EnrichedAlert;
|
||||
alert: Alert;
|
||||
isMobile: boolean;
|
||||
onMarkAsRead: (id: string) => void;
|
||||
onRemove: (id: string) => void;
|
||||
actionHandler: any;
|
||||
}> = ({ alert, isMobile, onMarkAsRead, onRemove, actionHandler }) => {
|
||||
const { t } = useTranslation();
|
||||
const isUnread = alert.status === 'active';
|
||||
const priorityColor = getPriorityColor(alert.priority_level);
|
||||
|
||||
@@ -109,14 +111,14 @@ const EnrichedAlertItem: React.FC<{
|
||||
<p className={`font-medium leading-tight text-[var(--text-primary)] ${
|
||||
isMobile ? 'text-base mb-2' : 'text-sm mb-1'
|
||||
}`}>
|
||||
{alert.title}
|
||||
{renderEventTitle(alert, t)}
|
||||
</p>
|
||||
|
||||
{/* Message */}
|
||||
<p className={`leading-relaxed text-[var(--text-secondary)] ${
|
||||
isMobile ? 'text-sm mb-3' : 'text-xs mb-2'
|
||||
}`}>
|
||||
{alert.message}
|
||||
{renderEventMessage(alert, t)}
|
||||
</p>
|
||||
|
||||
{/* Context Badges */}
|
||||
@@ -133,10 +135,10 @@ const EnrichedAlertItem: React.FC<{
|
||||
<span>€{alert.business_impact.financial_impact_eur.toFixed(0)} en riesgo</span>
|
||||
</div>
|
||||
)}
|
||||
{alert.urgency_context?.time_until_consequence_hours && (
|
||||
{alert.urgency?.hours_until_consequence && (
|
||||
<div className="flex items-center gap-1 px-2 py-1 rounded-md bg-error/10 text-error text-xs">
|
||||
<Clock className="w-3 h-3" />
|
||||
<span>{formatTimeUntilConsequence(alert.urgency_context.time_until_consequence_hours)}</span>
|
||||
<span>{formatTimeUntilConsequence(alert.urgency.hours_until_consequence)}</span>
|
||||
</div>
|
||||
)}
|
||||
{alert.trend_context && (
|
||||
@@ -148,21 +150,21 @@ const EnrichedAlertItem: React.FC<{
|
||||
</div>
|
||||
|
||||
{/* AI Reasoning Summary */}
|
||||
{alert.ai_reasoning_summary && (
|
||||
{renderAIReasoning(alert, t) && (
|
||||
<div className="mb-3 p-2 rounded-md bg-primary/5 border border-primary/20">
|
||||
<div className="flex items-start gap-2">
|
||||
<Bot className="w-4 h-4 text-primary mt-0.5 flex-shrink-0" />
|
||||
<p className="text-xs text-[var(--text-secondary)] italic">
|
||||
{alert.ai_reasoning_summary}
|
||||
{renderAIReasoning(alert, t)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Smart Actions */}
|
||||
{alert.actions && alert.actions.length > 0 && (
|
||||
{alert.smart_actions && alert.smart_actions.length > 0 && (
|
||||
<div className={`flex flex-wrap gap-2 ${isMobile ? 'mb-3' : 'mb-2'}`}>
|
||||
{alert.actions.slice(0, 3).map((action, idx) => (
|
||||
{alert.smart_actions.slice(0, 3).map((action, idx) => (
|
||||
<Button
|
||||
key={idx}
|
||||
size={isMobile ? "sm" : "xs"}
|
||||
@@ -173,9 +175,9 @@ const EnrichedAlertItem: React.FC<{
|
||||
action.variant === 'danger' ? 'text-error hover:text-error-dark' : ''
|
||||
}`}
|
||||
>
|
||||
{action.type === 'call_supplier' && <Phone className="w-3 h-3 mr-1" />}
|
||||
{action.type === 'navigate' && <ExternalLink className="w-3 h-3 mr-1" />}
|
||||
{action.label}
|
||||
{action.action_type === 'call_supplier' && <Phone className="w-3 h-3 mr-1" />}
|
||||
{action.action_type === 'navigate' && <ExternalLink className="w-3 h-3 mr-1" />}
|
||||
{renderActionLabel(action, t)}
|
||||
{action.estimated_time_minutes && (
|
||||
<span className="ml-1 opacity-60">({action.estimated_time_minutes}m)</span>
|
||||
)}
|
||||
@@ -344,17 +346,23 @@ export const NotificationPanel: React.FC<NotificationPanelProps> = ({
|
||||
key={notification.id}
|
||||
alert={{
|
||||
...notification,
|
||||
event_class: 'alert',
|
||||
tenant_id: user?.tenant_id || '',
|
||||
status: notification.read ? 'acknowledged' : 'active',
|
||||
created_at: notification.timestamp,
|
||||
enriched_at: notification.timestamp,
|
||||
alert_metadata: notification.metadata || {},
|
||||
event_metadata: notification.metadata || {},
|
||||
service: 'notification-service',
|
||||
alert_type: notification.item_type,
|
||||
actions: notification.actions || [],
|
||||
is_group_summary: false,
|
||||
placement: notification.placement || ['notification_panel']
|
||||
} as EnrichedAlert}
|
||||
event_type: notification.item_type,
|
||||
event_domain: 'notification',
|
||||
smart_actions: notification.actions || [],
|
||||
entity_links: {},
|
||||
i18n: {
|
||||
title_key: notification.title || '',
|
||||
message_key: notification.message || '',
|
||||
title_params: {},
|
||||
message_params: {}
|
||||
}
|
||||
} as Alert}
|
||||
isMobile={isMobile}
|
||||
onMarkAsRead={onMarkAsRead}
|
||||
onRemove={onRemoveNotification}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
export { default as Button } from './Button';
|
||||
export { default as Input } from './Input';
|
||||
export { default as Textarea } from './Textarea/Textarea';
|
||||
export { default as Card, CardHeader, CardBody, CardFooter } from './Card';
|
||||
export { default as Card, CardHeader, CardBody, CardFooter, CardContent, CardTitle, CardDescription } from './Card';
|
||||
export { default as Modal, ModalHeader, ModalBody, ModalFooter } from './Modal';
|
||||
export { default as Table } from './Table';
|
||||
export { Badge, CountBadge, StatusDot, SeverityBadge } from './Badge';
|
||||
@@ -48,9 +48,9 @@ export { SettingsSearch } from './SettingsSearch';
|
||||
export type { ButtonProps } from './Button';
|
||||
export type { InputProps } from './Input';
|
||||
export type { TextareaProps } from './Textarea';
|
||||
export type { CardProps, CardHeaderProps, CardBodyProps, CardFooterProps } from './Card';
|
||||
export type { CardProps, CardHeaderProps, CardBodyProps, CardFooterProps, CardContentProps, CardTitleProps } from './Card';
|
||||
export type { ModalProps, ModalHeaderProps, ModalBodyProps, ModalFooterProps } from './Modal';
|
||||
export type { TableProps, TableColumn, TableRow } from './Table';
|
||||
export type { TableProps, TableColumn } from './Table';
|
||||
export type { BadgeProps, CountBadgeProps, StatusDotProps, SeverityBadgeProps, SeverityLevel } from './Badge';
|
||||
export type { AvatarProps } from './Avatar';
|
||||
export type { TooltipProps } from './Tooltip';
|
||||
|
||||
Reference in New Issue
Block a user