bookwiz.io / components / ChatHistory.tsx
ChatHistory.tsx
Raw
'use client'

import { useState } from 'react'
import { Chat } from '@/lib/types/database'
import { IoTimeOutline, IoClose, IoTrashOutline, IoPencilOutline, IoCheckmarkOutline } from 'react-icons/io5'
import DeleteConfirmationDialog from './DeleteConfirmationDialog'

interface ChatHistoryProps {
  isOpen: boolean
  onClose: () => void
  chatHistory: Chat[]
  currentChatId?: string | null
  isLoading: boolean
  onLoadChat: (chatId: string) => void
  onDeleteChat: (chatId: string) => void
  onUpdateChatTitle: (chatId: string, title: string) => void
}

export default function ChatHistory({
  isOpen,
  onClose,
  chatHistory,
  currentChatId,
  isLoading,
  onLoadChat,
  onDeleteChat,
  onUpdateChatTitle,
}: ChatHistoryProps) {
  const [editingChatId, setEditingChatId] = useState<string | null>(null)
  const [editingTitle, setEditingTitle] = useState('')
  const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
  const [chatToDelete, setChatToDelete] = useState<Chat | null>(null)

  const handleStartEdit = (chat: Chat) => {
    setEditingChatId(chat.id)
    setEditingTitle(chat.title)
  }

  const handleSaveEdit = async (chatId: string) => {
    if (editingTitle.trim()) {
      await onUpdateChatTitle(chatId, editingTitle.trim())
    }
    setEditingChatId(null)
    setEditingTitle('')
  }

  const handleCancelEdit = () => {
    setEditingChatId(null)
    setEditingTitle('')
  }

  const handleDeleteConfirm = () => {
    if (chatToDelete) {
      onDeleteChat(chatToDelete.id)
      setIsDeleteDialogOpen(false)
      setChatToDelete(null)
    }
  }

  const formatDate = (dateString: string) => {
    const date = new Date(dateString)
    const now = new Date()
    const diffTime = Math.abs(now.getTime() - date.getTime())
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))

    if (diffDays === 1) {
      // Check if it's actually today (same calendar day)
      const isToday = date.toDateString() === now.toDateString()
      if (isToday) {
        return `Today at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
      }
      return 'Today'
    } else if (diffDays === 2) {
      return 'Yesterday'
    } else if (diffDays <= 7) {
      return `${diffDays - 1} days ago`
    } else {
      return date.toLocaleDateString()
    }
  }

  if (!isOpen) return null

  return (
    <div className="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm">
      <div className="flex">
        {/* Click outside to close */}
        <div className="flex-1" onClick={onClose} />
        
        {/* Sidebar */}
        <div className="w-80 h-screen bg-slate-800 border-l border-slate-700 flex flex-col">
          {/* Header */}
          <div className="h-12 bg-slate-800 border-b border-slate-700 flex items-center justify-between px-4">
            <div className="flex items-center gap-2">
              <IoTimeOutline className="w-4 h-4 text-slate-400" />
              <h2 className="text-sm font-semibold text-slate-200">Chat History</h2>
            </div>
            <button 
              onClick={onClose}
              className="p-1.5 text-slate-400 hover:text-slate-200 hover:bg-slate-700 rounded-md transition-colors"
            >
              <IoClose className="w-4 h-4" />
            </button>
          </div>

          {/* Chat List */}
          <div className="flex-1 overflow-y-auto px-3 py-3">
            {isLoading ? (
              <div className="flex items-center justify-center py-8">
                <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-teal-500"></div>
              </div>
            ) : chatHistory.length === 0 ? (
              <div className="text-center py-8 text-slate-400">
                <IoTimeOutline className="w-8 h-8 mx-auto mb-2 opacity-50" />
                <p className="text-sm">No chat history yet</p>
                <p className="text-xs mt-1">Start a conversation to see it here</p>
              </div>
            ) : (
              <div className="space-y-2">
                {chatHistory.map((chat) => (
                  <div
                    key={chat.id}
                    className={`group relative p-3 rounded-lg border transition-all cursor-pointer ${
                      currentChatId === chat.id
                        ? 'bg-teal-600/20 border-teal-500/50 text-teal-100'
                        : 'bg-slate-700/50 border-slate-600 hover:bg-slate-700 hover:border-slate-500 text-slate-200'
                    }`}
                    onClick={() => !editingChatId && onLoadChat(chat.id)}
                  >
                    {/* Chat Title */}
                    <div className="mb-2">
                      {editingChatId === chat.id ? (
                        <div className="flex items-center gap-2">
                          <input
                            type="text"
                            value={editingTitle}
                            onChange={(e) => setEditingTitle(e.target.value)}
                            className="flex-1 bg-slate-600 text-slate-200 text-sm px-2 py-1 rounded border border-slate-500 focus:outline-none focus:border-teal-500"
                            autoFocus
                            onKeyDown={(e) => {
                              if (e.key === 'Enter') {
                                handleSaveEdit(chat.id)
                              } else if (e.key === 'Escape') {
                                handleCancelEdit()
                              }
                            }}
                          />
                          <button
                            onClick={(e) => {
                              e.stopPropagation()
                              handleSaveEdit(chat.id)
                            }}
                            className="p-1 text-teal-400 hover:text-teal-300"
                          >
                            <IoCheckmarkOutline className="w-4 h-4" />
                          </button>
                        </div>
                      ) : (
                        <h3 className="text-sm font-medium line-clamp-2 group-hover:text-slate-100">
                          {chat.title}
                        </h3>
                      )}
                    </div>

                    {/* Chat Metadata */}
                    <div className="flex items-center justify-between text-xs">
                      <div className="flex items-center gap-3 text-slate-400">
                        <span>{chat.total_messages} messages</span>
                        <span>{formatDate(chat.updated_at)}</span>
                      </div>
                      
                      {/* Action Buttons */}
                      {editingChatId !== chat.id && (
                        <div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
                          <button
                            onClick={(e) => {
                              e.stopPropagation()
                              handleStartEdit(chat)
                            }}
                            className="p-1 text-slate-400 hover:text-slate-200 hover:bg-slate-600 rounded"
                            title="Rename chat"
                          >
                            <IoPencilOutline className="w-3 h-3" />
                          </button>
                          <button
                            onClick={(e) => {
                              e.stopPropagation()
                              setChatToDelete(chat)
                              setIsDeleteDialogOpen(true)
                            }}
                            className="p-1 text-slate-400 hover:text-red-400 hover:bg-slate-600 rounded"
                            title="Delete chat"
                          >
                            <IoTrashOutline className="w-3 h-3" />
                          </button>
                        </div>
                      )}
                    </div>

                    {/* Model Badge */}
                    {chat.model && (
                      <div className="mt-2">
                        <span className="inline-block px-2 py-1 bg-slate-600 text-slate-300 text-xs rounded">
                          {chat.model}
                        </span>
                      </div>
                    )}
                  </div>
                ))}
              </div>
            )}
          </div>
        </div>
      </div>

      {/* Delete Confirmation Dialog */}
      <DeleteConfirmationDialog
        isOpen={isDeleteDialogOpen}
        onClose={() => {
          setIsDeleteDialogOpen(false)
          setChatToDelete(null)
        }}
        onConfirm={handleDeleteConfirm}
        loading={false}
        title="Delete Chat"
        message={`Delete "${chatToDelete?.title}"? This action cannot be undone.`}
        confirmText="Delete Chat"
      />
    </div>
  )
}