File size: 5,144 Bytes
619120c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import React, { useRef, useEffect, useState } from 'react';

const NetworkMap = ({ users }) => {
    const canvasRef = useRef(null);
    const [hoveredUser, setHoveredUser] = useState(null);

    useEffect(() => {
        const canvas = canvasRef.current;
        if (!canvas) return;
        const ctx = canvas.getContext('2d');
        let animationFrameId;

        // Simple auto-scaling projection
        const lats = users.map(u => u.location.lat);
        const lngs = users.map(u => u.location.lng);
        const minLat = Math.min(...lats) - 5;
        const maxLat = Math.max(...lats) + 5;
        const minLng = Math.min(...lngs) - 5;
        const maxLng = Math.max(...lngs) + 5;

        const project = (lat, lng) => {
            const x = ((lng - minLng) / (maxLng - minLng)) * canvas.width;
            const y = canvas.height - ((lat - minLat) / (maxLat - minLat)) * canvas.height;
            return { x, y };
        };

        const render = () => {
            // Resize canvas to parent
            if (canvas.width !== canvas.parentElement.clientWidth || canvas.height !== canvas.parentElement.clientHeight) {
                canvas.width = canvas.parentElement.clientWidth;
                canvas.height = canvas.parentElement.clientHeight;
            }

            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // Draw Grid
            ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
            ctx.lineWidth = 1;
            const gridSize = 40;
            for (let x = 0; x < canvas.width; x += gridSize) {
                ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke();
            }
            for (let y = 0; y < canvas.height; y += gridSize) {
                ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke();
            }

            // Draw Connections (just for visual flair)
            ctx.strokeStyle = 'rgba(0, 240, 255, 0.05)';
            ctx.lineWidth = 0.5;
            users.forEach((u, i) => {
                const p1 = project(u.location.lat, u.location.lng);
                // Connect to nearest neighbor (simplified, just connect to next few)
                for (let j = i + 1; j < Math.min(i + 3, users.length); j++) {
                    const p2 = project(users[j].location.lat, users[j].location.lng);
                    ctx.beginPath(); ctx.moveTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.stroke();
                }
            });

            // Draw Users
            users.forEach(user => {
                const { x, y } = project(user.location.lat, user.location.lng);
                const isActive = user.status === 'Online';
                const isRoaming = user.isRoaming;
                const color = isActive ? '#00ff9d' : (isRoaming ? '#ffbf00' : '#ff0055');

                // Pulse
                if (isActive) {
                    const time = Date.now() / 1000;
                    const radius = 4 + Math.sin(time * 2 + user.id) * 2;
                    ctx.beginPath();
                    ctx.arc(x, y, radius * 2, 0, Math.PI * 2);
                    ctx.fillStyle = `color-mix(in srgb, ${color} 20%, transparent)`;
                    ctx.fill();
                }

                ctx.beginPath();
                ctx.arc(x, y, 4, 0, Math.PI * 2);
                ctx.fillStyle = color;
                ctx.fill();

                // Label if active or Roaming
                if (isRoaming) {
                    ctx.fillStyle = 'rgba(255,255,255,0.5)';
                    ctx.font = '10px Inter';
                    ctx.fillText(user.location.city, x + 8, y + 3);
                }
            });

            animationFrameId = requestAnimationFrame(render);
        };

        render();
        return () => cancelAnimationFrame(animationFrameId);
    }, [users]);

    return (
        <div className="glass-panel" style={{ height: '100%', minHeight: '400px', position: 'relative', overflow: 'hidden' }}>

            <canvas ref={canvasRef} style={{ width: '100%', height: '100%', display: 'block' }} />

            <div style={{ position: 'absolute', top: 20, right: 20, background: 'rgba(0,0,0,0.5)', padding: '10px', borderRadius: '8px', fontSize: '12px' }}>

                <div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '4px' }}>

                    <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#00ff9d' }}></span> Online

                </div>

                <div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '4px' }}>

                    <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#ffbf00' }}></span> Roaming

                </div>

                <div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>

                    <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#ff0055' }}></span> Offline

                </div>

            </div>

        </div>
    );
};

export default NetworkMap;