File size: 8,768 Bytes
24e6f5b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import { useState, useEffect } from 'react';
import { AlertTriangle, ShieldCheck, Zap } from 'lucide-react';
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Cell } from 'recharts';
import api from '../api/axios';
import { useSettings } from '../context/SettingsContext';

const AnomalyFeed = () => {
    const { currencySymbol } = useSettings();
    const [anomalies, setAnomalies] = useState([]);
    const [loading, setLoading] = useState(true);
    const [showAll, setShowAll] = useState(false);

    useEffect(() => {
        const fetchAnomalies = async () => {
            try {
                const response = await api.get('analytics/anomalies/');
                setAnomalies(response.data);
            } catch (err) {
                console.error("Anomaly Error:", err);
            } finally {
                setLoading(false);
            }
        };
        fetchAnomalies();
    }, []);

    // Prepare chart data - Recharts Scatter needs numeric X axis often for best results with type="number"
    // converting date string to timestamp
    const chartData = anomalies.map(a => ({
        ...a,
        x: new Date(a.date).getTime(),
        y: a.amount
    }));

    const displayedAnomalies = showAll ? anomalies : anomalies.slice(0, 6);

    if (loading) return (
        <div className="glass-panel" style={{ height: '100%', minHeight: '300px', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
            <div className="spinner"></div>
        </div>
    );

    return (
        <div className="glass-panel" style={{
            height: '550px', // Increased from 450px
            minHeight: '250px',
            padding: '0',
            overflow: 'hidden',
            display: 'flex', flexDirection: 'column',
            border: '1px solid rgba(239, 68, 68, 0.2)',
            boxShadow: '0 0 20px rgba(239, 68, 68, 0.05)'
        }}>
            <div style={{
                padding: '1rem',
                borderBottom: '1px solid var(--glass-border)',
                background: 'rgba(239, 68, 68, 0.05)',
                display: 'flex', justifyContent: 'space-between', alignItems: 'center'
            }}>
                <h3 style={{ margin: 0, fontSize: '1rem', display: 'flex', alignItems: 'center', gap: '0.5rem', color: '#f87171' }}>
                    <ShieldCheck size={18} /> Security Radar
                </h3>
                <span style={{
                    fontSize: '0.7rem', fontWeight: 'bold',
                    background: anomalies.length ? '#ef4444' : '#10b981',
                    color: 'white', padding: '0.1rem 0.5rem', borderRadius: '99px'
                }}>
                    {anomalies.length}
                </span>
            </div>

            {/* Anomaly Visualization Chart */}
            {anomalies.length > 0 && (
                <div style={{ height: '150px', width: '100%', borderBottom: '1px solid var(--glass-border)', background: 'linear-gradient(to bottom, rgba(0,0,0,0.2), transparent)' }}>
                    <ResponsiveContainer width="100%" height="100%">
                        <BarChart data={anomalies} margin={{ top: 20, right: 20, bottom: 20, left: 0 }}>
                            <XAxis
                                dataKey="date"
                                tick={false}
                                axisLine={false}
                            />
                            <YAxis
                                hide
                            />
                            <Tooltip
                                cursor={{ fill: 'rgba(255,255,255,0.05)' }}
                                contentStyle={{ backgroundColor: 'rgba(30, 41, 59, 0.95)', borderRadius: '8px', border: '1px solid rgba(255,255,255,0.1)', color: '#fff' }}
                                formatter={(value) => [`${currencySymbol}${value}`, 'Amount']}
                            />
                            <Bar dataKey="amount" radius={[4, 4, 0, 0]}>
                                {anomalies.map((entry, index) => (
                                    <Cell key={`cell-${index}`} fill={`rgba(239, 68, 68, ${(entry.score || 0.5) + 0.4})`} />
                                ))}
                            </Bar>
                        </BarChart>
                    </ResponsiveContainer>
                </div>
            )}

            <div style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden', padding: '0.75rem' }}>
                {anomalies.length === 0 ? (
                    <div style={{ height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', color: 'var(--text-muted)', opacity: 0.7 }}>
                        <ShieldCheck size={32} style={{ marginBottom: '0.5rem', color: '#10b981' }} />
                        <p style={{ fontSize: '0.9rem' }}>No anomalies detected.</p>
                    </div>
                ) : (
                    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '0.5rem' }}>
                        {displayedAnomalies.map((item, idx) => (
                            <div key={idx} style={{
                                background: 'rgba(255,255,255,0.03)',
                                padding: '0.6rem', // Reduced from 0.75rem
                                borderRadius: '0.5rem',
                                borderTop: '3px solid #f87171', // Top border for cards
                                display: 'flex', flexDirection: 'column',
                                transition: 'all 0.2s',
                                minHeight: '90px' // Reduced from 100px
                            }} className="hover:bg-white/5">
                                <div style={{ marginBottom: '0.5rem', display: 'flex', alignItems: 'center', gap: '0.3rem', fontSize: '0.8rem', fontWeight: '600', color: '#fff', overflow: 'hidden' }}>
                                    <AlertTriangle size={12} color="#f87171" style={{ flexShrink: 0 }} />
                                    <span style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{item.title}</span>
                                </div>
                                <div style={{ fontSize: '1rem', fontWeight: 'bold', color: '#fff', marginBottom: '0.25rem' }}>
                                    {currencySymbol}{item.amount.toLocaleString()}
                                </div>
                                <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', marginBottom: 'auto' }}>
                                    {item.date}
                                </div>
                                <div style={{ fontSize: '0.65rem', marginTop: '0.5rem', color: '#f87171', background: 'rgba(248, 113, 113, 0.1)', padding: '2px 6px', borderRadius: '4px', textAlign: 'center', alignSelf: 'flex-start' }}>
                                    {item.score ? `Risk: ${(item.score * 100).toFixed(0)}%` : 'Detected'}
                                </div>
                            </div>
                        ))}

                        {/* Show More Button - Spans all columns */}
                        {anomalies.length > 6 && (
                            <button
                                onClick={() => setShowAll(!showAll)}
                                style={{
                                    gridColumn: '1 / -1',
                                    padding: '0.5rem',
                                    marginTop: '0.25rem',
                                    background: 'rgba(255,255,255,0.05)',
                                    border: 'none',
                                    borderRadius: '0.5rem',
                                    color: 'var(--text-muted)',
                                    fontSize: '0.8rem',
                                    cursor: 'pointer',
                                    transition: 'background 0.2s'
                                }}
                                className="hover:bg-white/10"
                            >
                                {showAll ? 'Show Less' : `Show ${anomalies.length - 6} More`}
                            </button>
                        )}
                    </div>
                )}
            </div>

            <div style={{
                padding: '0.5rem', borderTop: '1px solid var(--glass-border)',
                fontSize: '0.65rem', color: 'var(--text-muted)', textAlign: 'center',
                background: 'rgba(0,0,0,0.2)'
            }}>
                <Zap size={10} style={{ marginRight: '0.25rem', display: 'inline' }} />
                AI Anomaly Detector
            </div>
        </div>
    );
};

export default AnomalyFeed;