← back to Norma

components/drafts/DraftsTab.tsx

248 lines

'use client';

import { useState, useEffect, useCallback } from 'react';
import { Plus, RefreshCw, Inbox } from 'lucide-react';
import SessionCard from './SessionCard';
import AlertsFeed from './AlertsFeed';
import type { DraftData } from './DraftEditor';
import { useToast } from '../ToastProvider';
import { SkeletonList } from '../Skeleton';
import SortDropdown from '../shared/SortDropdown';
import CollapsibleSection from '../shared/CollapsibleSection';
import { useClientSort, SortConfig } from '@/hooks/useClientSort';

const SORT_OPTIONS = [
  { value: 'date_desc', label: 'Newest First' },
  { value: 'date_asc', label: 'Oldest First' },
  { value: 'status', label: 'Status A-Z' },
];

const SORT_CONFIGS: Record<string, SortConfig> = {
  date_desc: { key: 'created_at', direction: 'desc', type: 'date' },
  date_asc: { key: 'created_at', direction: 'asc', type: 'date' },
  status: { key: 'status', direction: 'asc', type: 'string' },
};

/* ─── Types ──────────────────────────────────────────────────────────────── */
interface Session {
  id: string;
  title?: string;
  session_date: string;
  status: string;
  planning_checklist?: Record<string, boolean>;
  planning_notes?: string;
  created_at: string;
  drafts: DraftData[];
}

/* ─── DraftsTab ──────────────────────────────────────────────────────────── */
interface DraftsTabProps {
  onNavigateToSettings?: () => void;
}

export default function DraftsTab({ onNavigateToSettings }: DraftsTabProps) {
  const { addToast } = useToast();
  const [sessions, setSessions] = useState<Session[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [creating, setCreating] = useState(false);
  const [sortBy, setSortBy] = useState('date_desc');
  const sortedSessions = useClientSort(sessions, sortBy, SORT_CONFIGS);

  const fetchSessions = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const res = await fetch('/api/sessions');
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const data = await res.json();
      const list: Session[] = data.sessions ?? data ?? [];
      /* Sorting handled by useClientSort (server returns ORDER BY session_date DESC) */
      setSessions(list);
    } catch {
      setError('Failed to load sessions. Check your connection and try again.');
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => { fetchSessions(); }, [fetchSessions]);

  async function handleNewBatch() {
    setCreating(true);
    try {
      const res = await fetch('/api/sessions', { method: 'POST' });
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      await fetchSessions();
      addToast('Morning batch created', 'success');
    } catch {
      addToast('Failed to create a new Morning Batch', 'error');
    } finally {
      setCreating(false);
    }
  }

  function handleSessionDeleted(id: string) {
    setSessions((prev) => prev.filter((s) => s.id !== id));
  }

  function handleStatusChange(id: string, status: string) {
    setSessions((prev) =>
      prev.map((s) => (s.id === id ? { ...s, status } : s))
    );
  }

  return (
    <div className="p-4 sm:p-6 max-w-4xl mx-auto">
      {/* ── Alerts Feed ──────────────────────────────────────────────────── */}
      <AlertsFeed onNavigateToSettings={onNavigateToSettings} />

      {/* ── Top bar ──────────────────────────────────────────────────────── */}
      <div className="flex items-center justify-between mb-5 gap-3">
        <div>
          <h1
            className="text-lg font-semibold"
            style={{ color: 'var(--color-text)' }}
          >
            Email Drafts
          </h1>
          <p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
            Morning batches with 3 lane drafts each
          </p>
        </div>
        <div className="flex items-center gap-2">
          <SortDropdown options={SORT_OPTIONS} value={sortBy} onChange={setSortBy} />
          <button
            type="button"
            onClick={fetchSessions}
            disabled={loading}
            className="btn btn-ghost btn-sm"
            aria-label="Refresh sessions"
            title="Refresh"
          >
            <RefreshCw
              size={14}
              aria-hidden="true"
              style={{
                animation: loading ? 'spin 0.7s linear infinite' : 'none',
              }}
            />
          </button>
          <button
            type="button"
            onClick={handleNewBatch}
            disabled={creating}
            className="btn btn-primary"
            aria-label="Create new morning batch"
          >
            {creating ? (
              <span
                className="spinner"
                style={{ width: 14, height: 14, borderWidth: 2 }}
              />
            ) : (
              <Plus size={15} aria-hidden="true" />
            )}
            {creating ? 'Creating...' : 'New Morning Batch'}
          </button>
        </div>
      </div>

      {/* ── Loading state ─────────────────────────────────────────────────── */}
      {loading && (
        <div aria-live="polite" aria-busy="true">
          <SkeletonList count={3} />
        </div>
      )}

      {/* ── Error state ───────────────────────────────────────────────────── */}
      {!loading && error && (
        <div
          className="rounded-xl flex flex-col items-center justify-center py-12 gap-3"
          style={{
            border: '1px solid rgba(239,68,68,0.3)',
            backgroundColor: 'rgba(239,68,68,0.05)',
          }}
          role="alert"
        >
          <p className="font-medium" style={{ color: 'var(--color-error)' }}>
            {error}
          </p>
          <button
            type="button"
            onClick={fetchSessions}
            className="btn btn-secondary btn-sm"
          >
            <RefreshCw size={13} aria-hidden="true" />
            Retry
          </button>
        </div>
      )}

      {/* ── Empty state ───────────────────────────────────────────────────── */}
      {!loading && !error && sessions.length === 0 && (
        <div
          className="rounded-xl flex flex-col items-center justify-center py-16 gap-3"
          style={{
            backgroundColor: 'var(--color-surface)',
            border: '1px dashed var(--color-border)',
          }}
        >
          <Inbox
            size={36}
            style={{ color: 'var(--color-text-muted)' }}
            aria-hidden="true"
          />
          <p
            className="font-medium text-lg"
            style={{ color: 'var(--color-text)' }}
          >
            No morning batches yet
          </p>
          <p className="text-sm text-center max-w-xs" style={{ color: 'var(--color-text-muted)' }}>
            Click "New Morning Batch" to create your first session with 3 lane
            drafts (Action, Policy, Resources).
          </p>
          <button
            type="button"
            onClick={handleNewBatch}
            disabled={creating}
            className="btn btn-primary mt-1"
          >
            <Plus size={15} aria-hidden="true" />
            New Morning Batch
          </button>
        </div>
      )}

      {/* ── Session list ──────────────────────────────────────────────────── */}
      {!loading && !error && sessions.length > 0 && (
        <CollapsibleSection
          id="drafts-sessions"
          title="Morning Batches"
          count={sessions.length}
          defaultOpen={true}
          noPadding
        >
          <div
            className="flex flex-col gap-4"
            role="feed"
            aria-label="Morning batch sessions"
            style={{ padding: '0 0 1rem' }}
          >
            {sortedSessions.map((session, i) => (
              <SessionCard
                key={session.id}
                session={session}
                defaultExpanded={i === 0}
                onDeleted={handleSessionDeleted}
                onStatusChange={handleStatusChange}
              />
            ))}
          </div>
        </CollapsibleSection>
      )}
    </div>
  );
}