← back to Norma

components/drafts/RalphyboyWidget.tsx

223 lines

'use client';

import { useState, useEffect, useCallback, useRef } from 'react';
import { Lightbulb, ChevronDown, ChevronRight } from 'lucide-react';

interface ChecklistState {
  reviewed_news: boolean;
  reviewed_x_posts: boolean;
  confirmed_audience: boolean;
  selected_voice: boolean;
  defined_cta: boolean;
}

const CHECKLIST_ITEMS: { key: keyof ChecklistState; label: string }[] = [
  { key: 'reviewed_news',      label: "Reviewed today's news"         },
  { key: 'reviewed_x_posts',   label: 'Reviewed X posts'              },
  { key: 'confirmed_audience', label: 'Confirmed target audience'     },
  { key: 'selected_voice',     label: 'Selected voice mode'           },
  { key: 'defined_cta',        label: 'Defined key message / CTA'    },
];

const DEFAULT_CHECKLIST: ChecklistState = {
  reviewed_news:      false,
  reviewed_x_posts:   false,
  confirmed_audience: false,
  selected_voice:     false,
  defined_cta:        false,
};

interface RalphyboyWidgetProps {
  sessionId: string;
  initialChecklist?: Partial<ChecklistState>;
  initialNotes?: string;
}

export default function RalphyboyWidget({
  sessionId,
  initialChecklist = {},
  initialNotes = '',
}: RalphyboyWidgetProps) {
  const [expanded, setExpanded] = useState(true);
  const [checklist, setChecklist] = useState<ChecklistState>({
    ...DEFAULT_CHECKLIST,
    ...initialChecklist,
  });
  const [notes, setNotes] = useState(initialNotes ?? '');
  const [saving, setSaving] = useState(false);
  const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  const completedCount = Object.values(checklist).filter(Boolean).length;
  const totalCount = CHECKLIST_ITEMS.length;

  const persist = useCallback(
    async (nextChecklist: ChecklistState, nextNotes: string) => {
      setSaving(true);
      try {
        await fetch(`/api/sessions/${sessionId}`, {
          method: 'PATCH',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            planning_checklist: nextChecklist,
            planning_notes: nextNotes,
          }),
        });
      } catch {
        /* Silent — non-critical */
      } finally {
        setSaving(false);
      }
    },
    [sessionId]
  );

  function scheduleAutoSave(nextChecklist: ChecklistState, nextNotes: string) {
    if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
    saveTimerRef.current = setTimeout(() => persist(nextChecklist, nextNotes), 1200);
  }

  /* Cleanup on unmount */
  useEffect(() => {
    return () => {
      if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
    };
  }, []);

  function handleCheck(key: keyof ChecklistState) {
    const next = { ...checklist, [key]: !checklist[key] };
    setChecklist(next);
    scheduleAutoSave(next, notes);
  }

  function handleNotes(e: React.ChangeEvent<HTMLTextAreaElement>) {
    const val = e.target.value;
    setNotes(val);
    scheduleAutoSave(checklist, val);
  }

  return (
    <div
      className="rounded-lg overflow-hidden"
      style={{
        border: '1px solid var(--color-border)',
        backgroundColor: 'var(--color-surface)',
      }}
    >
      {/* Header */}
      <button
        type="button"
        onClick={() => setExpanded((v) => !v)}
        className="flex items-center justify-between w-full px-3 py-2.5"
        style={{ backgroundColor: 'transparent', cursor: 'pointer' }}
        aria-expanded={expanded}
        aria-controls="ralphyboy-panel"
      >
        <div className="flex items-center gap-2">
          <Lightbulb
            size={14}
            aria-hidden="true"
            style={{ color: 'var(--color-secondary)', flexShrink: 0 }}
          />
          <span className="text-sm font-medium" style={{ color: 'var(--color-text)' }}>
            Planning Checklist
          </span>
          <span
            className="text-xs px-1.5 py-0.5 rounded-full font-medium"
            style={{
              backgroundColor:
                completedCount === totalCount
                  ? 'rgba(34, 197, 94, 0.15)'
                  : 'rgba(245, 158, 11, 0.15)',
              color:
                completedCount === totalCount
                  ? 'var(--color-success)'
                  : 'var(--color-warning)',
              border: `1px solid ${
                completedCount === totalCount
                  ? 'rgba(34, 197, 94, 0.3)'
                  : 'rgba(245, 158, 11, 0.3)'
              }`,
            }}
          >
            {completedCount}/{totalCount}
          </span>
          {saving && (
            <span className="text-xs" style={{ color: 'var(--color-text-muted)' }}>
              Saving...
            </span>
          )}
        </div>
        {expanded ? (
          <ChevronDown size={14} style={{ color: 'var(--color-text-muted)' }} aria-hidden="true" />
        ) : (
          <ChevronRight size={14} style={{ color: 'var(--color-text-muted)' }} aria-hidden="true" />
        )}
      </button>

      {/* Panel */}
      {expanded && (
        <div
          id="ralphyboy-panel"
          className="px-3 pb-3"
          style={{ borderTop: '1px solid var(--color-border)' }}
        >
          {/* Checkboxes */}
          <fieldset className="pt-2.5 pb-2">
            <legend className="sr-only">Planning checklist items</legend>
            <div className="flex flex-col gap-2">
              {CHECKLIST_ITEMS.map(({ key, label }) => (
                <label
                  key={key}
                  className="flex items-center gap-2.5 cursor-pointer group"
                >
                  <input
                    type="checkbox"
                    checked={checklist[key]}
                    onChange={() => handleCheck(key)}
                    className="w-4 h-4 rounded"
                    style={{
                      accentColor: 'var(--color-primary)',
                      flexShrink: 0,
                    }}
                  />
                  <span
                    className="text-sm transition-colors"
                    style={{
                      color: checklist[key]
                        ? 'var(--color-text-muted)'
                        : 'var(--color-text-secondary)',
                      textDecoration: checklist[key] ? 'line-through' : 'none',
                    }}
                  >
                    {label}
                  </span>
                </label>
              ))}
            </div>
          </fieldset>

          {/* Notes */}
          <div>
            <label
              htmlFor="planning-notes"
              className="block text-xs font-medium mb-1"
              style={{ color: 'var(--color-text-muted)' }}
            >
              Planning Notes
            </label>
            <textarea
              id="planning-notes"
              value={notes}
              onChange={handleNotes}
              placeholder="Add notes, key angles, story hooks..."
              rows={3}
              className="input"
              style={{ resize: 'vertical', fontSize: '0.8125rem' }}
            />
          </div>
        </div>
      )}
    </div>
  );
}