turash/bugulma/frontend/components/map/organizationIcons.ts

191 lines
5.6 KiB
TypeScript

import type { OrganizationSubtype } from '@/schemas/organizationSubtype.ts';
import {
Banknote,
Briefcase,
Building,
Building2,
Car,
Church,
Circle,
Construction,
Cpu,
Factory,
GraduationCap,
Heart,
Hotel,
Scissors,
ShoppingBag,
Theater,
Truck,
UtensilsCrossed,
Zap
} from 'lucide-react';
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
/**
* Database subtypes (actual values from database)
* These are the granular subtypes stored in the database
*/
export type DatabaseSubtype =
| 'retail'
| 'healthcare'
| 'educational'
| 'personal_services'
| 'food_beverage'
| 'automotive'
| 'religious'
| 'professional_services'
| 'energy'
| 'financial'
| 'government'
| 'manufacturing'
| 'cultural'
| 'hospitality'
| 'transportation'
| 'infrastructure'
| 'technology'
| 'other';
/**
* Get the appropriate Lucide icon component for an organization subtype
*/
function getLucideIconForSubtype(subtype: string): React.ComponentType<{ size?: number; color?: string }> {
const subtypeLower = subtype.toLowerCase().trim();
// Map database subtypes to Lucide icons
switch (subtypeLower) {
case 'retail':
return ShoppingBag;
case 'food_beverage':
return UtensilsCrossed;
case 'automotive':
return Car;
case 'personal_services':
return Scissors;
case 'professional_services':
return Briefcase;
case 'financial':
return Banknote;
case 'manufacturing':
return Factory;
case 'hospitality':
return Hotel;
case 'transportation':
return Truck;
case 'energy':
return Zap;
case 'technology':
return Cpu;
case 'commercial':
return Building2;
case 'cultural':
return Theater;
case 'government':
return Building;
case 'religious':
return Church;
case 'educational':
return GraduationCap;
case 'infrastructure':
return Construction;
case 'healthcare':
return Heart;
case 'other':
default:
return Circle;
}
}
/**
* Get organization icon SVG as string (for use in Leaflet DivIcon)
* Uses Lucide icons rendered to HTML strings
*/
export function getOrganizationIconSvg(
subtype: OrganizationSubtype | DatabaseSubtype | string | undefined | null,
size: number,
iconColor: string,
backgroundColor: string
): string {
// Debug logging in development
if (process.env.NODE_ENV === 'development') {
console.log('[organizationIcons] getOrganizationIconSvg called with:', {
subtype,
size,
iconColor,
backgroundColor,
sizeType: typeof size,
iconColorType: typeof iconColor,
backgroundColorType: typeof backgroundColor
});
}
// Ensure all parameters are valid with proper type checking
const safeSize = Math.max(1, Number.isFinite(size) && size > 0 ? size : 24);
// Increase icon size to 65% of marker size for better visibility (was 50%)
const safeIconSize = Math.round(safeSize * 0.65);
const subtypeValue = (subtype || '').toLowerCase().trim();
// Ensure colors are never undefined and are valid strings
const safeBackgroundColor = (typeof backgroundColor === 'string' && backgroundColor.trim()) ? backgroundColor : '#6b7280';
/**
* Calculate a contrasting color for the icon based on background brightness
* Returns a color that will stand out against the background
*/
function getContrastingIconColor(bgColor: string): string {
// Parse hex color
const hex = bgColor.replace('#', '');
if (hex.length !== 6) {
// If invalid color, return white as fallback
return '#ffffff';
}
// Convert to RGB
const r = parseInt(hex.substr(0, 2), 16);
const g = parseInt(hex.substr(2, 2), 16);
const b = parseInt(hex.substr(4, 2), 16);
// Calculate brightness (luminance)
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
// If background is light, use dark contrasting color
// If background is dark, use light contrasting color
if (brightness > 128) {
// Light background - use darker, more saturated color for contrast
// Darken and saturate the background color
const darkerR = Math.max(0, Math.min(255, r * 0.4));
const darkerG = Math.max(0, Math.min(255, g * 0.4));
const darkerB = Math.max(0, Math.min(255, b * 0.4));
return `rgb(${Math.round(darkerR)}, ${Math.round(darkerG)}, ${Math.round(darkerB)})`;
} else {
// Dark background - use lighter, contrasting color
// Lighten the background color or use a complementary light color
const lighterR = Math.min(255, r + (255 - r) * 0.7);
const lighterG = Math.min(255, g + (255 - g) * 0.7);
const lighterB = Math.min(255, b + (255 - b) * 0.7);
return `rgb(${Math.round(lighterR)}, ${Math.round(lighterG)}, ${Math.round(lighterB)})`;
}
}
// Calculate contrasting icon color based on background
const contrastingIconColor = getContrastingIconColor(safeBackgroundColor);
// Get the appropriate Lucide icon component
const IconComponent = getLucideIconForSubtype(subtypeValue);
// Render the icon to HTML string using renderToStaticMarkup (appropriate for Leaflet markers)
// Use contrasting color for better visibility against the background
const iconHtml = renderToStaticMarkup(
React.createElement(IconComponent, {
size: safeIconSize,
color: contrastingIconColor, // Use contrasting color instead of plain white
strokeWidth: 2.5, // Slightly thicker for better visibility
})
);
// Return the icon SVG directly (the wrapper div in iconCache.ts will handle centering and background)
return iconHtml;
}