← back to Norma

components/connections/OrgConnections.tsx

333 lines

'use client';

import { useState, useEffect } from 'react';
import { Network, ArrowRight, Zap, Target, Users, Award, Search } from 'lucide-react';
import { useOrg, type OrgProfile } from '@/components/OrgProvider';

/* ─── Types ──────────────────────────────────────────────────────────────── */
interface Connection {
  type: 'keyword' | 'focus' | 'location' | 'grant';
  label: string;
  strength: 'strong' | 'medium' | 'weak';
}

/* ─── Helpers ────────────────────────────────────────────────────────────── */
function findConnections(a: OrgProfile, b: OrgProfile): Connection[] {
  const conns: Connection[] = [];

  // Shared keywords
  const aKw = new Set((a.search_keywords || []).map((k) => k.toLowerCase()));
  const bKw = new Set((b.search_keywords || []).map((k) => k.toLowerCase()));
  for (const kw of aKw) {
    if (bKw.has(kw)) {
      conns.push({ type: 'keyword', label: kw, strength: 'strong' });
    }
  }

  // Shared focus areas
  const aFocus = new Set((a.focus_areas || []).map((f) => f.toLowerCase()));
  const bFocus = new Set((b.focus_areas || []).map((f) => f.toLowerCase()));
  for (const fa of aFocus) {
    if (bFocus.has(fa)) {
      conns.push({ type: 'focus', label: fa, strength: 'strong' });
    }
  }

  // Partial keyword overlaps (one org's keyword found in other's focus area)
  for (const kw of aKw) {
    for (const fa of bFocus) {
      if (fa.includes(kw) || kw.includes(fa)) {
        if (!conns.some((c) => c.label === kw || c.label === fa)) {
          conns.push({ type: 'keyword', label: `${kw} ~ ${fa}`, strength: 'medium' });
        }
      }
    }
  }
  for (const kw of bKw) {
    for (const fa of aFocus) {
      if (fa.includes(kw) || kw.includes(fa)) {
        if (!conns.some((c) => c.label === kw || c.label === fa)) {
          conns.push({ type: 'keyword', label: `${kw} ~ ${fa}`, strength: 'medium' });
        }
      }
    }
  }

  // Same state
  if (a.address_state && b.address_state && a.address_state === b.address_state) {
    conns.push({ type: 'location', label: `Both in ${a.address_state}`, strength: 'medium' });
  }

  // Same city
  if (a.address_city && b.address_city && a.address_city.toLowerCase() === b.address_city.toLowerCase()) {
    conns.push({ type: 'location', label: `Both in ${a.address_city}, ${a.address_state}`, strength: 'strong' });
  }

  return conns;
}

const STRENGTH_COLORS: Record<string, string> = {
  strong: '#10b981',
  medium: '#f59e0b',
  weak: '#6b7280',
};

const TYPE_ICONS: Record<string, typeof Network> = {
  keyword: Search,
  focus: Target,
  location: Users,
  grant: Award,
};

/* ─── Component ──────────────────────────────────────────────────────────── */
export default function OrgConnections() {
  const { orgs } = useOrg();
  const [orgA, setOrgA] = useState<string>('');
  const [orgB, setOrgB] = useState<string>('');
  const [connections, setConnections] = useState<Connection[]>([]);

  useEffect(() => {
    if (!orgA || !orgB || orgA === orgB) {
      setConnections([]);
      return;
    }
    const a = orgs.find((o) => o.id === orgA);
    const b = orgs.find((o) => o.id === orgB);
    if (a && b) {
      setConnections(findConnections(a, b));
    }
  }, [orgA, orgB, orgs]);

  const selectedA = orgs.find((o) => o.id === orgA);
  const selectedB = orgs.find((o) => o.id === orgB);

  return (
    <div className="p-4 sm:p-6 max-w-4xl mx-auto">
      <div className="mb-6">
        <h1 className="text-lg font-semibold" style={{ color: 'var(--color-text)' }}>
          Org Connections
        </h1>
        <p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
          Select two organizations to discover shared keywords, focus areas, and collaboration opportunities.
        </p>
      </div>

      {/* ── Org Selectors ──────────────────────────────────────────────── */}
      <div className="flex items-center gap-4 mb-6 flex-wrap">
        {/* Org A */}
        <div className="flex-1 min-w-[200px]">
          <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>
            Organization A
          </label>
          <select
            className="input w-full"
            value={orgA}
            onChange={(e) => setOrgA(e.target.value)}
          >
            <option value="">Select org...</option>
            {orgs.filter((o) => o.id !== orgB).map((o) => (
              <option key={o.id} value={o.id}>{o.org_name}</option>
            ))}
          </select>
        </div>

        <div className="flex items-center pt-5">
          <Network size={20} style={{ color: 'var(--color-text-muted)' }} />
        </div>

        {/* Org B */}
        <div className="flex-1 min-w-[200px]">
          <label className="block text-xs font-medium mb-1" style={{ color: 'var(--color-text-secondary)' }}>
            Organization B
          </label>
          <select
            className="input w-full"
            value={orgB}
            onChange={(e) => setOrgB(e.target.value)}
          >
            <option value="">Select org...</option>
            {orgs.filter((o) => o.id !== orgA).map((o) => (
              <option key={o.id} value={o.id}>{o.org_name}</option>
            ))}
          </select>
        </div>
      </div>

      {/* ── Org Cards ──────────────────────────────────────────────────── */}
      {selectedA && selectedB && (
        <div className="grid grid-cols-2 gap-4 mb-6">
          {[selectedA, selectedB].map((o) => (
            <div
              key={o.id}
              className="card p-4"
              style={{ borderTop: `3px solid ${o.brand_color}` }}
            >
              <div className="flex items-center gap-3 mb-3">
                <div
                  style={{
                    width: 32,
                    height: 32,
                    borderRadius: 8,
                    background: `linear-gradient(135deg, ${o.brand_color}, ${o.brand_color}cc)`,
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                    flexShrink: 0,
                  }}
                >
                  <span style={{ color: '#fff', fontSize: 12, fontWeight: 800 }}>
                    {(o.short_name || o.org_name.slice(0, 2)).slice(0, 4)}
                  </span>
                </div>
                <div>
                  <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--color-text)' }}>
                    {o.org_name}
                  </div>
                  <div style={{ fontSize: 11, color: 'var(--color-text-muted)' }}>
                    {o.address_city}{o.address_city && o.address_state ? ', ' : ''}{o.address_state}
                  </div>
                </div>
              </div>

              {/* Focus areas */}
              {(o.focus_areas || []).length > 0 && (
                <div className="mb-2">
                  <span className="text-xs font-medium" style={{ color: 'var(--color-text-secondary)' }}>Focus: </span>
                  <span className="text-xs" style={{ color: 'var(--color-text-muted)' }}>
                    {o.focus_areas.join(', ')}
                  </span>
                </div>
              )}

              {/* Keywords */}
              {(o.search_keywords || []).length > 0 && (
                <div className="flex flex-wrap gap-1 mt-2">
                  {o.search_keywords.slice(0, 8).map((kw) => (
                    <span
                      key={kw}
                      className="text-xs px-2 py-0.5 rounded-full"
                      style={{
                        backgroundColor: `${o.brand_color}20`,
                        color: o.brand_color,
                        fontSize: 10,
                      }}
                    >
                      {kw}
                    </span>
                  ))}
                  {o.search_keywords.length > 8 && (
                    <span className="text-xs" style={{ color: 'var(--color-text-muted)' }}>
                      +{o.search_keywords.length - 8} more
                    </span>
                  )}
                </div>
              )}
            </div>
          ))}
        </div>
      )}

      {/* ── Connections ─────────────────────────────────────────────────── */}
      {selectedA && selectedB && (
        <div className="card p-5">
          <div className="flex items-center gap-2 mb-4">
            <Zap size={16} style={{ color: '#f59e0b' }} />
            <h3 className="text-sm font-semibold" style={{ color: 'var(--color-text)' }}>
              Connections Found
            </h3>
            <span
              className="badge"
              style={{
                backgroundColor: connections.length > 0 ? 'rgba(16, 185, 129, 0.15)' : 'rgba(107, 114, 128, 0.15)',
                color: connections.length > 0 ? '#10b981' : '#6b7280',
                fontSize: '0.6875rem',
                padding: '1px 8px',
              }}
            >
              {connections.length}
            </span>
          </div>

          {connections.length === 0 ? (
            <div className="text-center py-8">
              <Network size={32} style={{ color: 'var(--color-text-muted)', margin: '0 auto 8px' }} />
              <p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
                No direct connections found between these organizations.
              </p>
              <p className="text-xs mt-1" style={{ color: 'var(--color-text-muted)' }}>
                Add more keywords in Settings to discover potential overlaps.
              </p>
            </div>
          ) : (
            <div className="space-y-2">
              {connections.map((conn, i) => {
                const Icon = TYPE_ICONS[conn.type] || Network;
                return (
                  <div
                    key={i}
                    className="flex items-center gap-3 p-3 rounded-lg"
                    style={{
                      backgroundColor: 'var(--color-surface-el)',
                      border: '1px solid var(--color-border)',
                    }}
                  >
                    <Icon size={14} style={{ color: STRENGTH_COLORS[conn.strength], flexShrink: 0 }} />
                    <div className="flex-1 min-w-0">
                      <span className="text-sm" style={{ color: 'var(--color-text)' }}>
                        {conn.label}
                      </span>
                    </div>
                    <div className="flex items-center gap-2 shrink-0">
                      <span
                        className="text-xs px-2 py-0.5 rounded-full capitalize"
                        style={{
                          backgroundColor: `${STRENGTH_COLORS[conn.strength]}20`,
                          color: STRENGTH_COLORS[conn.strength],
                        }}
                      >
                        {conn.strength}
                      </span>
                      <span
                        className="text-xs px-2 py-0.5 rounded-full capitalize"
                        style={{
                          backgroundColor: 'rgba(99, 102, 241, 0.15)',
                          color: '#818cf8',
                        }}
                      >
                        {conn.type}
                      </span>
                    </div>
                  </div>
                );
              })}
            </div>
          )}

          {/* Summary */}
          {connections.length > 0 && (
            <div
              className="mt-4 p-3 rounded-lg"
              style={{
                backgroundColor: 'rgba(16, 185, 129, 0.08)',
                border: '1px solid rgba(16, 185, 129, 0.2)',
              }}
            >
              <p className="text-sm font-medium" style={{ color: '#10b981' }}>
                Collaboration Potential
              </p>
              <p className="text-xs mt-1" style={{ color: 'var(--color-text-secondary)' }}>
                {selectedA.org_name} and {selectedB.org_name} share{' '}
                {connections.filter((c) => c.strength === 'strong').length} strong connections
                {connections.filter((c) => c.type === 'focus').length > 0
                  ? `, including ${connections.filter((c) => c.type === 'focus').length} shared focus areas`
                  : ''}
                . Consider joint petitions, shared grant applications, or co-branded advocacy campaigns.
              </p>
            </div>
          )}
        </div>
      )}
    </div>
  );
}