summaryrefslogblamecommitdiff
path: root/makima/frontend/src/components/files/FileList.tsx
blob: 188a1df2d803cd1762739c707f8436a2b5f9c26a (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                               


                                                                             





                                 
                       


                                                                 

















                                                         







                                                                    




                          
           
                   
                   
                                 









                                                                            
                                             












                                                                            









                                                                                



                                                                                                                         
























                                                                                                                                                                                                  


























                                                                                        
                                                                                                  


                                                                  














                                                                                                                                 



















                                                                                                                                                                             
import { useRef } from "react";
import { useNavigate } from "react-router";
import type { FileSummary, BodyElement, ContractPhase } from "../../lib/api";
import { markdownToBody } from "../../lib/markdown";

interface FileListProps {
  files: FileSummary[];
  loading: boolean;
  onSelect: (id: string) => void;
  onDelete: (id: string) => void;
  onCreate: () => void;
  onUploadMarkdown?: (name: string, body: BodyElement[]) => void;
}

function formatDuration(seconds: number | null): string {
  if (seconds === null) return "-";
  const mins = Math.floor(seconds / 60);
  const secs = Math.floor(seconds % 60);
  return `${mins}:${secs.toString().padStart(2, "0")}`;
}

function formatDate(dateStr: string): string {
  const date = new Date(dateStr);
  return date.toLocaleDateString("en-US", {
    month: "short",
    day: "numeric",
    year: "numeric",
    hour: "2-digit",
    minute: "2-digit",
  });
}

const phaseColors: Record<ContractPhase, string> = {
  research: "bg-purple-500/20 text-purple-400 border-purple-400/30",
  specify: "bg-blue-500/20 text-blue-400 border-blue-400/30",
  plan: "bg-cyan-500/20 text-cyan-400 border-cyan-400/30",
  execute: "bg-green-500/20 text-green-400 border-green-400/30",
  review: "bg-yellow-500/20 text-yellow-400 border-yellow-400/30",
};

export function FileList({
  files,
  loading,
  onSelect,
  onDelete,
  onCreate,
  onUploadMarkdown,
}: FileListProps) {
  const navigate = useNavigate();
  const fileInputRef = useRef<HTMLInputElement>(null);

  const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (!file || !onUploadMarkdown) return;

    const reader = new FileReader();
    reader.onload = (e) => {
      const content = e.target?.result as string;
      if (content) {
        const body = markdownToBody(content);
        // Use filename without extension as the name
        const name = file.name.replace(/\.md$/i, '') || 'Imported Document';
        onUploadMarkdown(name, body);
      }
    };
    reader.readAsText(file);

    // Reset input so the same file can be uploaded again
    if (fileInputRef.current) {
      fileInputRef.current.value = '';
    }
  };

  if (loading) {
    return (
      <div className="panel h-full flex items-center justify-center">
        <div className="font-mono text-[#9bc3ff] text-sm">Loading files...</div>
      </div>
    );
  }

  return (
    <div className="panel h-full flex flex-col">
      <div className="flex items-center justify-between p-4 pb-2 border-b border-dashed border-[rgba(117,170,252,0.35)]">
        <div className="font-mono text-xs text-[#9bc3ff] tracking-wide uppercase">
          FILES//
        </div>
        <div className="flex items-center gap-2">
          {onUploadMarkdown && (
            <>
              <input
                ref={fileInputRef}
                type="file"
                accept=".md,.markdown,text/markdown"
                onChange={handleFileUpload}
                className="hidden"
              />
              <button
                onClick={() => fileInputRef.current?.click()}
                className="px-3 py-1 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] hover:bg-[rgba(117,170,252,0.05)] transition-colors uppercase"
              >
                Upload .md
              </button>
            </>
          )}
          <button
            onClick={onCreate}
            className="px-3 py-1 font-mono text-xs text-[#9bc3ff] border border-[rgba(117,170,252,0.25)] hover:border-[#3f6fb3] hover:bg-[rgba(117,170,252,0.05)] transition-colors uppercase"
          >
            + New
          </button>
        </div>
      </div>

      <div className="flex-1 overflow-y-auto">
        {files.length === 0 ? (
          <div className="text-center text-[#9bc3ff] text-sm font-mono opacity-60 py-8">
            No saved files yet. Start recording to create one.
          </div>
        ) : (
          <div className="divide-y divide-[rgba(117,170,252,0.15)]">
            {files.map((file) => (
              <div
                key={file.id}
                className="p-4 hover:bg-[rgba(117,170,252,0.05)] transition-colors"
              >
                <div className="flex items-start justify-between gap-4">
                  <button
                    onClick={() => onSelect(file.id)}
                    className="flex-1 text-left"
                  >
                    <h3 className="font-mono text-sm text-[#dbe7ff] mb-1">
                      {file.name}
                    </h3>
                    {file.description && (
                      <p className="font-mono text-xs text-[#9bc3ff] mb-2 line-clamp-2">
                        {file.description}
                      </p>
                    )}
                    <div className="flex items-center gap-4 font-mono text-[10px] text-[#75aafc]">
                      <span>{file.transcriptCount} segments</span>
                      <span>{formatDuration(file.duration)}</span>
                      <span>{formatDate(file.createdAt)}</span>
                      {file.contractId && file.contractName && (
                        <button
                          onClick={(e) => {
                            e.stopPropagation();
                            navigate(`/contracts/${file.contractId}`);
                          }}
                          className={`px-2 py-0.5 text-[9px] font-mono uppercase border rounded ${
                            file.contractPhase ? phaseColors[file.contractPhase] : "bg-[#21262d] text-[#8b949e] border-[#30363d]"
                          } hover:opacity-80 transition-opacity`}
                          title={`View contract: ${file.contractName}`}
                        >
                          {file.contractName}
                          {file.contractPhase && ` · ${file.contractPhase}`}
                        </button>
                      )}
                    </div>
                  </button>
                  <button
                    onClick={(e) => {
                      e.stopPropagation();
                      onDelete(file.id);
                    }}
                    className="px-2 py-1 font-mono text-[10px] text-red-400 hover:bg-red-400/10 border border-red-400/30 hover:border-red-400/50 transition-colors uppercase"
                  >
                    Delete
                  </button>
                </div>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}