infor annotations
This commit is contained in:
@@ -21,7 +21,7 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
const [endDeviceId, setEndDeviceId] = useState<string | null>(null);
|
const [endDeviceId, setEndDeviceId] = useState<string | null>(null);
|
||||||
|
|
||||||
const isCableTool = ['fiber', 'cat6', 'cat6_poe'].includes(activeTool);
|
const isCableTool = ['fiber', 'cat6', 'cat6_poe'].includes(activeTool);
|
||||||
const isDeviceTool = ['switch', 'indoor_ap', 'outdoor_ap'].includes(activeTool);
|
const isDeviceTool = ['switch', 'indoor_ap', 'outdoor_ap', 'info'].includes(activeTool);
|
||||||
const isWirelessTool = activeTool === 'wireless_mesh';
|
const isWirelessTool = activeTool === 'wireless_mesh';
|
||||||
|
|
||||||
// Load all map items for snapping
|
// Load all map items for snapping
|
||||||
@@ -37,7 +37,7 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
loadItems();
|
loadItems();
|
||||||
}, [mapId]);
|
}, [mapId]);
|
||||||
|
|
||||||
// Find nearby device for snapping
|
// Find nearby device for snapping (exclude info markers)
|
||||||
const findNearbyDevice = (lat: number, lng: number, radiusMeters = 5): any | null => {
|
const findNearbyDevice = (lat: number, lng: number, radiusMeters = 5): any | null => {
|
||||||
const devices = allItems.filter(item =>
|
const devices = allItems.filter(item =>
|
||||||
['switch', 'indoor_ap', 'outdoor_ap'].includes(item.type) &&
|
['switch', 'indoor_ap', 'outdoor_ap'].includes(item.type) &&
|
||||||
@@ -109,17 +109,25 @@ export function DrawingHandler({ mapId, onItemCreated }: DrawingHandlerProps) {
|
|||||||
// Device placement - single click
|
// Device placement - single click
|
||||||
if (isDeviceTool) {
|
if (isDeviceTool) {
|
||||||
try {
|
try {
|
||||||
|
const properties: any = {
|
||||||
|
name: activeTool === 'info'
|
||||||
|
? `Info at ${lat.toFixed(4)}, ${lng.toFixed(4)}`
|
||||||
|
: `${activeTool} at ${lat.toFixed(4)}, ${lng.toFixed(4)}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only add port_count and connections if it's not an info marker
|
||||||
|
if (activeTool !== 'info') {
|
||||||
|
properties.port_count = activeTool === 'switch' ? 5 : activeTool === 'outdoor_ap' ? 1 : 4;
|
||||||
|
properties.connections = [];
|
||||||
|
}
|
||||||
|
|
||||||
await mapItemService.createMapItem(mapId, {
|
await mapItemService.createMapItem(mapId, {
|
||||||
type: activeTool as any,
|
type: activeTool as any,
|
||||||
geometry: {
|
geometry: {
|
||||||
type: 'Point',
|
type: 'Point',
|
||||||
coordinates: [lng, lat],
|
coordinates: [lng, lat],
|
||||||
},
|
},
|
||||||
properties: {
|
properties,
|
||||||
name: `${activeTool} at ${lat.toFixed(4)}, ${lng.toFixed(4)}`,
|
|
||||||
port_count: activeTool === 'switch' ? 5 : activeTool === 'outdoor_ap' ? 1 : 4,
|
|
||||||
connections: [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
onItemCreated();
|
onItemCreated();
|
||||||
// Reload items for snapping
|
// Reload items for snapping
|
||||||
|
|||||||
@@ -86,6 +86,19 @@ const outdoorApIcon = new L.DivIcon({
|
|||||||
iconAnchor: [20, 40],
|
iconAnchor: [20, 40],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const infoIcon = new L.DivIcon({
|
||||||
|
html: `<div class="info-marker-icon">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" strokeWidth="2"/>
|
||||||
|
<path d="M12 16v-4" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
|
||||||
|
<circle cx="12" cy="8" r="1.2"/>
|
||||||
|
</svg>
|
||||||
|
</div>`,
|
||||||
|
className: 'custom-info-marker',
|
||||||
|
iconSize: [20, 20],
|
||||||
|
iconAnchor: [10, 20],
|
||||||
|
});
|
||||||
|
|
||||||
export function MapItemsLayer({ mapId, refreshTrigger, readOnly = false }: MapItemsLayerProps) {
|
export function MapItemsLayer({ mapId, refreshTrigger, readOnly = false }: MapItemsLayerProps) {
|
||||||
const [items, setItems] = useState<MapItem[]>([]);
|
const [items, setItems] = useState<MapItem[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -137,6 +150,8 @@ export function MapItemsLayer({ mapId, refreshTrigger, readOnly = false }: MapIt
|
|||||||
return indoorApIcon;
|
return indoorApIcon;
|
||||||
case 'outdoor_ap':
|
case 'outdoor_ap':
|
||||||
return outdoorApIcon;
|
return outdoorApIcon;
|
||||||
|
case 'info':
|
||||||
|
return infoIcon;
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -343,9 +358,9 @@ export function MapItemsLayer({ mapId, refreshTrigger, readOnly = false }: MapIt
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render devices
|
// Render devices and info markers
|
||||||
if (
|
if (
|
||||||
['switch', 'indoor_ap', 'outdoor_ap'].includes(item.type) &&
|
['switch', 'indoor_ap', 'outdoor_ap', 'info'].includes(item.type) &&
|
||||||
item.geometry.type === 'Point'
|
item.geometry.type === 'Point'
|
||||||
) {
|
) {
|
||||||
const [lng, lat] = item.geometry.coordinates;
|
const [lng, lat] = item.geometry.coordinates;
|
||||||
@@ -373,7 +388,9 @@ export function MapItemsLayer({ mapId, refreshTrigger, readOnly = false }: MapIt
|
|||||||
<Popup>
|
<Popup>
|
||||||
<div className="text-sm dark:bg-gray-800 dark:text-white" style={{ minWidth: '200px' }}>
|
<div className="text-sm dark:bg-gray-800 dark:text-white" style={{ minWidth: '200px' }}>
|
||||||
<div className="font-semibold text-gray-900 dark:text-white">{item.properties.name || item.type}</div>
|
<div className="font-semibold text-gray-900 dark:text-white">{item.properties.name || item.type}</div>
|
||||||
<div className="text-gray-600 dark:text-gray-400">Type: {item.type}</div>
|
{item.type !== 'info' && (
|
||||||
|
<div className="text-gray-600 dark:text-gray-400">Type: {item.type}</div>
|
||||||
|
)}
|
||||||
{item.properties.port_count && (
|
{item.properties.port_count && (
|
||||||
<div className="text-gray-600 dark:text-gray-400">
|
<div className="text-gray-600 dark:text-gray-400">
|
||||||
Ports: {item.properties.connections?.length || 0} / {item.properties.port_count}
|
Ports: {item.properties.connections?.length || 0} / {item.properties.port_count}
|
||||||
|
|||||||
@@ -74,6 +74,13 @@ const DEVICE_TOOLS: ToolButton[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const INFO_TOOL: ToolButton = {
|
||||||
|
id: 'info',
|
||||||
|
label: 'Info',
|
||||||
|
icon: 'info',
|
||||||
|
description: 'Information Marker',
|
||||||
|
};
|
||||||
|
|
||||||
export function Toolbar({ mapId, readOnly = false }: ToolbarProps) {
|
export function Toolbar({ mapId, readOnly = false }: ToolbarProps) {
|
||||||
const { activeTool, setActiveTool } = useDrawingStore();
|
const { activeTool, setActiveTool } = useDrawingStore();
|
||||||
|
|
||||||
@@ -122,6 +129,17 @@ export function Toolbar({ mapId, readOnly = false }: ToolbarProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tool.icon === 'info') {
|
||||||
|
return (
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" style={{ color: '#6366F1' }}>
|
||||||
|
<circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" strokeWidth="2"/>
|
||||||
|
<path d="M12 16v-4"/>
|
||||||
|
<circle cx="12" cy="8" r="1"/>
|
||||||
|
<line x1="12" y1="12" x2="12" y2="16" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className="text-lg"
|
className="text-lg"
|
||||||
@@ -180,6 +198,12 @@ export function Toolbar({ mapId, readOnly = false }: ToolbarProps) {
|
|||||||
{/* Devices section */}
|
{/* Devices section */}
|
||||||
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 px-1 mb-2">Devices</h3>
|
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 px-1 mb-2">Devices</h3>
|
||||||
{DEVICE_TOOLS.map(renderToolButton)}
|
{DEVICE_TOOLS.map(renderToolButton)}
|
||||||
|
|
||||||
|
<div className="border-t border-gray-200 dark:border-gray-700 my-2"></div>
|
||||||
|
|
||||||
|
{/* Info section */}
|
||||||
|
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 px-1 mb-2">Annotations</h3>
|
||||||
|
{renderToolButton(INFO_TOOL)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,30 @@
|
|||||||
color: #F59E0B;
|
color: #F59E0B;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Info marker icon */
|
||||||
|
.custom-info-marker {
|
||||||
|
background: transparent !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-marker-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
border: 2px solid #6366F1;
|
||||||
|
color: #6366F1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .info-marker-icon {
|
||||||
|
background: rgb(31, 41, 55);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
/* Distance labels on map */
|
/* Distance labels on map */
|
||||||
.distance-label {
|
.distance-label {
|
||||||
background-color: rgba(255, 255, 255, 0.95) !important;
|
background-color: rgba(255, 255, 255, 0.95) !important;
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export function Dashboard() {
|
|||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
flex flex-col bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 transition-all duration-300 ease-in-out
|
flex flex-col bg-white dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 transition-all duration-300 ease-in-out
|
||||||
fixed md:relative h-full w-80
|
fixed md:relative h-full w-80 overflow-y-auto
|
||||||
${isSidebarOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}
|
${isSidebarOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}
|
||||||
`}
|
`}
|
||||||
style={{ zIndex: 9999 }}
|
style={{ zIndex: 9999 }}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type MapItemType = 'cable' | 'switch' | 'indoor_ap' | 'outdoor_ap' | 'wireless_mesh';
|
export type MapItemType = 'cable' | 'switch' | 'indoor_ap' | 'outdoor_ap' | 'wireless_mesh' | 'info';
|
||||||
|
|
||||||
export type CableType = 'fiber' | 'cat6' | 'cat6_poe';
|
export type CableType = 'fiber' | 'cat6' | 'cat6_poe';
|
||||||
|
|
||||||
@@ -60,7 +60,8 @@ export type DrawingTool =
|
|||||||
| 'switch'
|
| 'switch'
|
||||||
| 'indoor_ap'
|
| 'indoor_ap'
|
||||||
| 'outdoor_ap'
|
| 'outdoor_ap'
|
||||||
| 'wireless_mesh';
|
| 'wireless_mesh'
|
||||||
|
| 'info';
|
||||||
|
|
||||||
export const CABLE_COLORS: Record<CableType, string> = {
|
export const CABLE_COLORS: Record<CableType, string> = {
|
||||||
fiber: '#3B82F6', // Blue
|
fiber: '#3B82F6', // Blue
|
||||||
|
|||||||
Reference in New Issue
Block a user