turash/bugulma/frontend/components/map/OrganizationListItem.tsx

117 lines
4.7 KiB
TypeScript

import React, { useCallback } from 'react';
import { getSectorDisplay } from '@/constants.tsx';
import { mapBackendSectorToTranslationKey } from '@/lib/sector-mapper.ts';
import { getOrganizationSubtypeLabel } from '@/schemas/organizationSubtype.ts';
import { Organization } from '@/types.ts';
import { BadgeCheck } from 'lucide-react';
import Badge from '@/components/ui/Badge.tsx';
import HighlightedText from '@/components/ui/HighlightedText.tsx';
interface OrganizationListItemProps {
org: Organization;
isSelected: boolean;
onSelectOrg: (org: Organization) => void;
onHoverOrg: (id: string | null) => void;
searchTerm: string;
}
const OrganizationListItem: React.FC<OrganizationListItemProps> = React.memo(
({ org, isSelected, onSelectOrg, onHoverOrg, searchTerm }) => {
const sectorDisplay = getSectorDisplay(org.Sector);
const baseClasses =
'group rounded-xl cursor-pointer transition-all duration-300 ease-out border border-border/50 shadow-sm hover:shadow-md';
const stateClasses = isSelected
? 'bg-primary/10 border-primary shadow-md ring-2 ring-primary/20'
: 'bg-card/50 hover:bg-card hover:border-border';
const handleClick = useCallback(() => onSelectOrg(org), [onSelectOrg, org]);
const handleMouseEnter = useCallback(() => onHoverOrg(org.ID), [onHoverOrg, org.ID]);
const handleMouseLeave = useCallback(() => onHoverOrg(null), [onHoverOrg]);
return (
<div
onClick={handleClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
className={`${baseClasses} ${stateClasses} p-4`}
role="button"
aria-pressed={isSelected}
tabIndex={0}
onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && onSelectOrg(org)}
>
<div className="flex items-start gap-4">
{/* Enhanced Icon Container */}
<div className="h-14 w-14 rounded-xl flex items-center justify-center shrink-0 bg-gradient-to-br from-muted/80 to-muted/40 overflow-hidden border border-border/50 shadow-sm group-hover:shadow-md transition-shadow duration-300">
{org.LogoURL ? (
<img
src={org.LogoURL}
alt={`${org.Name} logo`}
className="w-full h-full object-cover"
loading="lazy"
decoding="async"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
) : (
org.Sector ? (
<div
className="w-full h-full flex items-center justify-center"
style={{
backgroundColor: `hsl(var(--sector-${sectorDisplay.colorKey}))`,
}}
>
{React.cloneElement(sectorDisplay.icon, {
className: 'h-7 w-7 text-white drop-shadow-sm',
})}
</div>
) : (
<div className="w-full h-full flex items-center justify-center bg-gradient-to-br from-primary/20 to-primary/10">
<span className="text-xl font-bold text-primary/50">{org.Name.charAt(0).toUpperCase()}</span>
</div>
)
)}
</div>
{/* Content */}
<div className="flex-1 min-w-0">
{/* Name and Verified Badge */}
<div className="flex items-start gap-2 mb-2">
<h3 className="font-semibold text-base leading-tight text-foreground group-hover:text-primary transition-colors duration-200 flex-1">
<HighlightedText text={org.Name} highlight={searchTerm} />
</h3>
{org.Verified && (
<BadgeCheck className="h-4 mt-0.5 shrink-0 text-current text-success-DEFAULT w-4" />
)}
</div>
{/* Subtype Badge */}
{org.Subtype && (
<div className="mb-2">
<Badge
variant="secondary"
className="text-xs font-medium px-2.5 py-0.5 bg-primary/10 text-primary border border-primary/20"
>
{getOrganizationSubtypeLabel(org.Subtype)}
</Badge>
</div>
)}
{/* Description */}
{org.Description && (
<p className="text-sm text-muted-foreground leading-relaxed line-clamp-2 group-hover:text-foreground/80 transition-colors duration-200">
<HighlightedText text={org.Description} highlight={searchTerm} />
</p>
)}
</div>
</div>
</div>
);
}
);
OrganizationListItem.displayName = 'OrganizationListItem';
export default OrganizationListItem;