Untitled

 avatar
user_0601895
plain_text
10 months ago
12 kB
18
Indexable
'use client';
import Image from 'next/image';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { usePathname } from 'next/navigation';

import {
  Users2,
  Images as ImagesIcon,
  MonitorSmartphone,
  LogOut,
  PanelLeftClose,
  PanelLeftOpen,
} from 'lucide-react';

interface Body {
  purpose: string;
  value: string;
}
interface Annotation {
  body?: Body[];
}
interface SidebarProps {
  minimal?: boolean;
  annotations?: Annotation[];
  version?: string;
}

type ThumbItem = {
  id: string;
  thumb: string;
  file: string;
  tileSource: string;
};

function cn(...c: Array<string | false | null | undefined>) {
  return c.filter(Boolean).join(' ');
}

function NavItem({
  href,
  label,
  icon: Icon,
  active,
  collapsed,
}: {
  href: string;
  label: string;
  icon: React.ElementType;
  active: boolean;
  collapsed: boolean;
}) {
  return (
    <Link
      href={href}
      className={cn(
        'group flex items-center gap-3 rounded-xl px-3 py-2 text-sm font-medium transition-colors',
        collapsed && 'justify-center',
        active
          ? 'bg-[var(--sb-active-bg)] text-[var(--sb-fg)]'
          : 'text-[var(--sb-muted)] hover:bg-[var(--sb-hover-bg)] hover:text-[var(--sb-fg)]'
      )}
      aria-current={active ? 'page' : undefined}
      title={collapsed ? label : undefined}
    >
      <span
        className={cn(
          'h-1.5 w-1.5 rounded-full transition-colors',
          active
            ? 'bg-[var(--sb-accent)]'
            : 'bg-[color-mix(in_oklab,var(--sb-fg)_30%,transparent)] group-hover:bg-[color-mix(in_oklab,var(--sb-fg)_55%,transparent)]'
        )}
        aria-hidden
      />
      <Icon className="h-4 w-4 opacity-90" />
      {!collapsed && <span className="truncate">{label}</span>}
    </Link>
  );
}

export default function Sidebar({ minimal, annotations = [] }: SidebarProps) {
  const pathname = usePathname();
  const [activeId, setActiveId] = useState<string | null>(null);
  const [collapsed, setCollapsed] = useState(false);
  const [thumbnails, setThumbnails] = useState<ThumbItem[]>([]);

  // 🔹 Dinamik slide fetch
  useEffect(() => {
    fetch('/slides')
      .then((res) => res.json())
      .then((files: string[]) => {
        const items = files.map((f) => {
          const id = f.replace('.tif', '');
          return {
            id,
            file: f,
            thumb: `/${id}.jpg`, // TODO: gerçek thumbnail varsa buraya bağla
            tileSource: `/slides/${id}/dzi`,
          };
        });
        setThumbnails(items);
      })
      .catch((err) => {
        console.error('Slide fetch error:', err);
      });
  }, []);

  // 🔹 LocalStorage state restore
  useEffect(() => {
    try {
      const last = localStorage.getItem('lastSlideId');
      if (last) setActiveId(last);
      const col = localStorage.getItem('sidebarCollapsed');
      if (col) setCollapsed(col === '1');
    } catch {}
  }, []);

  const toggleCollapsed = () => {
    setCollapsed((v) => {
      const next = !v;
      try {
        localStorage.setItem('sidebarCollapsed', next ? '1' : '0');
      } catch {}
      return next;
    });
  };

  // 🔹 Viewer’a event gönder
  const sendClick = (item: ThumbItem) => {
    setActiveId(item.id);
    try {
      localStorage.setItem('lastSlideId', item.id);
    } catch {}
    window.dispatchEvent(new CustomEvent('thumbnailClick', { detail: item.tileSource }));
  };

  const sidebarVars: React.CSSProperties = {
    ['--sb-bg' as any]: 'var(--surface-2)',
    ['--sb-fg' as any]: 'var(--foreground)',
    ['--sb-muted' as any]: 'color-mix(in oklab, var(--foreground) 72%, transparent)',
    ['--sb-border' as any]: 'var(--border-strong)',
    ['--sb-accent' as any]: 'var(--accent)',
    ['--sb-hover-bg' as any]: 'color-mix(in oklab, var(--foreground) 6%, transparent)',
    ['--sb-active-bg' as any]: 'color-mix(in oklab, var(--accent) 18%, transparent)',
    ['--sb-chip-bg' as any]: 'color-mix(in oklab, var(--surface-0) 75%, var(--accent) 8%)',
  };

  return (
    <aside
      className={cn(
        'h-dvh shrink-0 overflow-y-auto border-r transition-[width] duration-200',
        collapsed ? 'w-20' : 'w-72'
      )}
      style={{
        ...sidebarVars,
        background: 'var(--sb-bg)',
        color: 'var(--sb-fg)',
        borderColor: 'var(--sb-border)',
      }}
    >
      {/* HEADER */}
      <div className="sticky top-0 z-10 border-b px-3 py-4 backdrop-blur-sm border-[var(--sb-border)]">
        <div className="flex flex-col items-center justify-center">
          <Image
            src="/indir.png"
            alt="Detagen Logo"
            width={collapsed ? 36 : 48}
            height={collapsed ? 36 : 48}
            className="rounded-lg ring-1 ring-[color-mix(in_oklab,var(--sb-fg)_22%,transparent)]"
            priority
          />
          {!collapsed ? (
            <div className="mt-2 leading-tight text-center">
              <div className="text-sm font-semibold tracking-tight text-[var(--sb-fg)]">Detagen</div>
              <div className="text-[11px] text-[var(--sb-muted)]">Patoloji Görüntüleme</div>
            </div>
          ) : (
            <div className="mt-2 text-[10px] font-medium uppercase tracking-widest text-[var(--sb-muted)] text-center">
              Detagen
            </div>
          )}
        </div>
        {!minimal && (
          <button
            onClick={toggleCollapsed}
            className={cn(
              'mt-3 inline-flex items-center gap-2 rounded-lg border px-2 py-1.5 text-[11px] font-medium transition-colors',
              'text-[var(--sb-fg)] border-[var(--sb-border)] hover:bg-[var(--sb-hover-bg)]',
              collapsed && 'w-full justify-center'
            )}
            title={collapsed ? 'Genişlet' : 'Daralt'}
          >
            {collapsed ? <PanelLeftOpen className="h-4 w-4" /> : <PanelLeftClose className="h-4 w-4" />}
            {!collapsed && <span>Paneli {collapsed ? 'aç' : 'daralt'}</span>}
          </button>
        )}
      </div>

      {/* NAV */}
      {!minimal && (
        <nav className="px-3 pt-3">
          {!collapsed && (
            <div className="mb-2 px-3 text-[11px] uppercase tracking-wider text-[var(--sb-muted)]">Menü</div>
          )}
          <div className="space-y-1">
            <NavItem
              href="/dashboard"
              label="Hastalar"
              icon={Users2}
              active={pathname === '/dashboard'}
              collapsed={collapsed}
            />
            <NavItem
              href="/images"
              label="Görseller"
              icon={ImagesIcon}
              active={pathname === '/images'}
              collapsed={collapsed}
            />
            <NavItem
              href="/viewer"
              label="Görüntüleyici"
              icon={MonitorSmartphone}
              active={pathname === '/viewer'}
              collapsed={collapsed}
            />
          </div>
          <div className="mt-6 h-px w-full bg-[color-mix(in_oklab,var(--sb-fg)_12%,transparent)]" />
          <Link
            href="/"
            className={cn(
              'mt-4 flex items-center gap-3 rounded-xl px-3 py-2 text-sm font-medium transition-colors',
              'text-[color-mix(in_oklab,red_80%,var(--sb-fg)_20%)] hover:bg-[var(--sb-hover-bg)] hover:text-[color-mix(in_oklab,red_90%,var(--sb-fg)_10%)]',
              collapsed && 'justify-center'
            )}
            title={collapsed ? 'Çıkış Yap' : undefined}
          >
            <LogOut className="h-4 w-4" />
            {!collapsed && <span>Çıkış Yap</span>}
          </Link>
        </nav>
      )}

      {/* VIEWER PANELS */}
      {pathname === '/viewer' && (
        <div className={cn('px-3 py-5', collapsed && 'px-2')}>
          {/* Görseller */}
          <div
            className={cn('rounded-2xl border p-3 shadow-sm backdrop-blur', collapsed && 'p-2')}
            style={{
              background: 'color-mix(in oklab, var(--surface-1) 92%, transparent)',
              borderColor: 'var(--border-strong)',
              color: 'var(--text-primary)',
            }}
          >
            <div className={cn('mb-3 flex items-center', collapsed ? 'justify-center' : 'justify-between')}>
              {!collapsed ? (
                <>
                  <h2 className="text-sm font-semibold text-[var(--text-primary)]">Görseller</h2>
                  <span
                    className="rounded-full px-2 py-0.5 text-[10px] font-medium"
                    style={{ background: 'var(--sb-chip-bg)', color: 'var(--sb-fg)' }}
                  >
                    {thumbnails.length}
                  </span>
                </>
              ) : (
                <span className="text-[11px] font-semibold text-[var(--text-primary)]">Görseller</span>
              )}
            </div>
            <div className={cn('grid gap-3', collapsed ? 'grid-cols-1' : 'grid-cols-2')}>
              {thumbnails.map((item) => {
                const isActive = activeId === item.id;
                return (
                  <button
                    key={item.id}
                    onClick={() => sendClick(item)}
                    title={item.file}
                    className={cn(
                      'group relative overflow-hidden rounded-xl border transition shadow-sm hover:shadow-md focus:outline-none focus:ring-2',
                      isActive ? 'ring-2 ring-[var(--accent)]' : 'ring-0'
                    )}
                    style={{ borderColor: 'var(--border-strong)' }}
                  >
                    <div className={cn('relative', collapsed ? 'h-[76px] w-full' : 'aspect-square')}>
                      <Image
                        src={item.thumb}
                        alt={item.id}
                        fill
                        className="object-cover"
                        sizes={collapsed ? '160px' : '180px'}
                        priority={false}
                      />
                    </div>
                    {!collapsed && (
                      <div className="absolute bottom-0 left-0 right-0 bg-[color-mix(in_oklab,black_55%,transparent)] px-2 py-1 text-center text-[10px] font-medium text-white">
                        {item.id}
                      </div>
                    )}
                    {isActive && (
                      <div className="absolute right-2 top-2 h-2 w-2 rounded-full bg-[var(--accent)]" aria-hidden />
                    )}
                  </button>
                );
              })}
            </div>
          </div>

          {/* Anotasyonlar */}
          <div
            className={cn('mt-5 rounded-2xl border p-3 shadow-sm backdrop-blur', collapsed && 'p-2')}
            style={{
              background: 'color-mix(in oklab, var(--surface-1) 92%, transparent)',
              borderColor: 'var(--border-strong)',
              color: 'var(--text-primary)',
            }}
          >
            {!collapsed && <h2 className="mb-3 text-sm font-semibold text-[var(--text-primary)]">Anotasyonlar</h2>}
            {annotations.length === 0 ? (
              <p className={cn('text-[11px] text-[var(--text-secondary)]', collapsed && 'text-center')}>
                Henüz eklenmemiş.
              </p>
            ) : (
              <ul className="space-y-2">
                {annotations.map((anno, idx) => (
                  <li key={idx} className="truncate text-[11px] text-[var(--text-primary)]">
                    {anno.body?.map((b, i) => (
                      <span key={i}>
                        {b.purpose}: {b.value}
                        {i < (anno.body?.length || 0) - 1 ? ', ' : ''}
                      </span>
                    ))}
                  </li>
                ))}
              </ul>
            )}
          </div>
        </div>
      )}
    </aside>
  );
}
Editor is loading...
Leave a Comment