← back to Norma

components/ManageTabsModal.tsx

236 lines

'use client';

import { useState, useMemo } from 'react';
import { Eye, EyeOff, Search, RotateCcw } from 'lucide-react';
import type { TabId } from './Sidebar';
import ResizableModal from './shared/ResizableModal';

/* ─── Types ──────────────────────────────────────────────────────────────── */

interface TabMeta {
  id: TabId;
  label: string;
  section: string;
}

interface ManageTabsModalProps {
  open: boolean;
  onClose: () => void;
  allTabs: TabMeta[];
  hiddenTabs: Set<TabId>;
  onToggleTab: (tabId: TabId) => void;
  onResetAll: () => void;
}

/* ─── Section display names ──────────────────────────────────────────────── */

const SECTION_LABELS: Record<string, string> = {
  workspace: 'Workspace',
  'email-sends': 'Email & Outreach',
  intelligence: 'Intelligence',
  geo: 'Geo Intelligence',
  pulse: 'Pulse',
  congress: 'Congress',
  nexus: 'Nexus',
  agents: 'AI Agents',
  universal: 'Universal',
};

/* ─── Component ──────────────────────────────────────────────────────────── */

export default function ManageTabsModal({
  open,
  onClose,
  allTabs,
  hiddenTabs,
  onToggleTab,
  onResetAll,
}: ManageTabsModalProps) {
  const [search, setSearch] = useState('');

  // Group tabs by section
  const sections = useMemo(() => {
    const map = new Map<string, TabMeta[]>();
    for (const tab of allTabs) {
      const filtered = search.trim()
        ? tab.label.toLowerCase().includes(search.trim().toLowerCase())
        : true;
      if (!filtered) continue;
      const list = map.get(tab.section) || [];
      list.push(tab);
      map.set(tab.section, list);
    }
    return map;
  }, [allTabs, search]);

  const hiddenCount = hiddenTabs.size;

  if (!open) return null;

  return (
    <ResizableModal
      modalId="manage-tabs"
      title={
        <div>
          <div style={{ fontSize: 16, fontWeight: 700 }}>Manage Tabs</div>
          <div style={{ fontSize: 11, color: 'var(--color-text-muted)', fontWeight: 400, marginTop: 2 }}>
            {hiddenCount === 0
              ? 'All tabs visible'
              : `${hiddenCount} tab${hiddenCount === 1 ? '' : 's'} hidden`}
          </div>
        </div>
      }
      onClose={onClose}
      defaultWidth={520}
    >
      {/* Search + Reset */}
      <div
        style={{
          display: 'flex',
          alignItems: 'center',
          gap: 8,
          marginBottom: 12,
        }}
      >
        <div style={{ position: 'relative', flex: 1 }}>
          <Search
            size={14}
            style={{
              position: 'absolute',
              left: 10,
              top: '50%',
              transform: 'translateY(-50%)',
              color: 'var(--color-text-muted)',
              pointerEvents: 'none',
            }}
          />
          <input
            type="text"
            className="input"
            placeholder="Filter tabs..."
            value={search}
            onChange={(e) => setSearch(e.target.value)}
            style={{
              paddingLeft: 32,
              fontSize: 13,
              height: 34,
              width: '100%',
            }}
          />
        </div>
        {hiddenCount > 0 && (
          <button
            type="button"
            onClick={onResetAll}
            className="btn btn-ghost btn-sm"
            style={{
              fontSize: 12,
              gap: 4,
              flexShrink: 0,
              whiteSpace: 'nowrap',
            }}
          >
            <RotateCcw size={13} />
            Show All
          </button>
        )}
      </div>

      {/* Tab list */}
      <div>
        {Array.from(sections.entries()).map(([sectionId, tabs]) => (
          <div key={sectionId} style={{ marginBottom: 12 }}>
            <div
              style={{
                fontSize: 10,
                fontWeight: 700,
                letterSpacing: '0.08em',
                textTransform: 'uppercase',
                color: 'var(--color-text-muted)',
                padding: '6px 8px 4px',
              }}
            >
              {SECTION_LABELS[sectionId] || sectionId}
            </div>
            {tabs.map((tab) => {
              const isHidden = hiddenTabs.has(tab.id);
              return (
                <button
                  key={tab.id}
                  type="button"
                  onClick={() => onToggleTab(tab.id)}
                  style={{
                    display: 'flex',
                    alignItems: 'center',
                    gap: 10,
                    width: '100%',
                    padding: '8px 10px',
                    borderRadius: 'var(--radius-sm)',
                    border: 'none',
                    cursor: 'pointer',
                    backgroundColor: 'transparent',
                    color: isHidden
                      ? 'var(--color-text-muted)'
                      : 'var(--color-text)',
                    fontSize: 13,
                    fontWeight: 500,
                    textAlign: 'left',
                    opacity: isHidden ? 0.5 : 1,
                    transition: 'background-color 0.15s, opacity 0.15s',
                  }}
                  onMouseEnter={(e) => {
                    e.currentTarget.style.backgroundColor =
                      'var(--color-surface-el)';
                  }}
                  onMouseLeave={(e) => {
                    e.currentTarget.style.backgroundColor = 'transparent';
                  }}
                >
                  {isHidden ? (
                    <EyeOff
                      size={15}
                      style={{ color: 'var(--color-text-muted)', flexShrink: 0 }}
                    />
                  ) : (
                    <Eye
                      size={15}
                      style={{ color: 'var(--color-primary)', flexShrink: 0 }}
                    />
                  )}
                  <span style={{ flex: 1 }}>{tab.label}</span>
                  {isHidden && (
                    <span
                      style={{
                        fontSize: 10,
                        color: 'var(--color-text-muted)',
                        padding: '1px 6px',
                        borderRadius: 10,
                        border: '1px solid var(--color-border)',
                      }}
                    >
                      hidden
                    </span>
                  )}
                </button>
              );
            })}
          </div>
        ))}

        {sections.size === 0 && (
          <div
            style={{
              textAlign: 'center',
              padding: '24px 0',
              color: 'var(--color-text-muted)',
              fontSize: 13,
            }}
          >
            No tabs match your search
          </div>
        )}
      </div>
    </ResizableModal>
  );
}