mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
117 lines
4.7 KiB
TypeScript
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;
|