| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- import React, { useState, useEffect } from 'react';
- import { SearchHistoryItem, KnowledgeGroup } from '../types';
- import { searchHistoryService } from '../services/searchHistoryService';
- import { useToast } from '../contexts/ToastContext';
- import { useLanguage } from '../contexts/LanguageContext';
- import { useConfirm } from '../contexts/ConfirmContext';
- import { MessageCircle, Trash2, Clock, Users } from 'lucide-react';
- interface SearchHistoryListProps {
- groups: KnowledgeGroup[];
- onSelectHistory: (historyId: string) => void;
- onDeleteHistory?: (historyId: string) => void;
- }
- export const SearchHistoryList: React.FC<SearchHistoryListProps> = ({
- groups,
- onSelectHistory,
- onDeleteHistory
- }) => {
- const [histories, setHistories] = useState<SearchHistoryItem[]>([]);
- const [loading, setLoading] = useState(true);
- const [page, setPage] = useState(1);
- const [hasMore, setHasMore] = useState(true);
- const { showError, showSuccess } = useToast();
- const { confirm } = useConfirm();
- const { t, language } = useLanguage();
- const loadHistories = async (pageNum: number = 1, append: boolean = false) => {
- try {
- setLoading(true);
- const response = await searchHistoryService.getHistories(pageNum, 20);
- if (append) {
- setHistories(prev => [...prev, ...response.histories]);
- } else {
- setHistories(response.histories);
- }
- setHasMore(response.histories.length === 20);
- setPage(pageNum);
- } catch (error) {
- showError(t('loadingHistoriesFailed'));
- } finally {
- setLoading(false);
- }
- };
- useEffect(() => {
- loadHistories();
- }, []);
- const handleDelete = async (historyId: string, e: React.MouseEvent) => {
- e.stopPropagation();
- if (!(await confirm(t('confirmDeleteHistory')))) return;
- try {
- await searchHistoryService.deleteHistory(historyId);
- setHistories(prev => prev.filter(h => h.id !== historyId));
- onDeleteHistory?.(historyId);
- showSuccess(t('deleteHistorySuccess'));
- } catch (error) {
- showError(t('deleteHistoryFailed'));
- }
- };
- const loadMore = () => {
- if (!loading && hasMore) {
- loadHistories(page + 1, true);
- }
- };
- const formatDate = (dateString: string) => {
- const date = new Date(dateString);
- const now = new Date();
- const diffMs = now.getTime() - date.getTime();
- const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
- // Determine locale for standard date functions
- const localeMap: Record<string, string> = {
- 'zh': 'zh-CN',
- 'en': 'en-US',
- 'ja': 'ja-JP'
- };
- const locale = localeMap[language] || 'ja-JP';
- if (diffDays === 0) {
- return date.toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit' });
- } else if (diffDays === 1) {
- return t('yesterday');
- } else if (diffDays < 7) {
- return t('daysAgo', diffDays);
- } else {
- return date.toLocaleDateString(locale);
- }
- };
- const getGroupNames = (selectedGroups: string[] | null) => {
- if (!selectedGroups || selectedGroups.length === 0) {
- return t('allKnowledgeGroups');
- }
- return selectedGroups
- .map(id => groups.find(g => g.id === id)?.name)
- .filter(Boolean)
- .join(', ');
- };
- if (loading && histories.length === 0) {
- return (
- <div className="flex items-center justify-center py-8">
- <div className="text-gray-500">{t('loading')}</div>
- </div>
- );
- }
- if (histories.length === 0) {
- return (
- <div className="text-center py-8">
- <MessageCircle size={48} className="mx-auto text-gray-300 mb-4" />
- <div className="text-gray-500">{t('noHistory')}</div>
- <div className="text-sm text-gray-400 mt-1">{t('noHistoryDesc')}</div>
- </div>
- );
- }
- return (
- <div className="space-y-2">
- {histories.map((history) => (
- <div
- key={history.id}
- onClick={() => onSelectHistory(history.id)}
- className="p-4 bg-white rounded-lg border hover:shadow-sm cursor-pointer transition-all group"
- >
- <div className="flex items-start justify-between">
- <div className="flex-1 min-w-0">
- <div className="font-medium text-gray-900 truncate mb-1">
- {history.title}
- </div>
- <div className="flex items-center space-x-4 text-sm text-gray-500 mb-2">
- <div className="flex items-center space-x-1">
- <MessageCircle size={14} />
- <span>{t('historyMessages', history.messageCount)}</span>
- </div>
- <div className="flex items-center space-x-1">
- <Clock size={14} />
- <span>{formatDate(history.lastMessageAt)}</span>
- </div>
- </div>
- <div className="flex items-center space-x-1 text-xs text-gray-400">
- <Users size={12} />
- <span>{getGroupNames(history.selectedGroups)}</span>
- </div>
- </div>
- <button
- onClick={(e) => handleDelete(history.id, e)}
- className="opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-red-600 transition-all"
- >
- <Trash2 size={16} />
- </button>
- </div>
- </div>
- ))}
- {hasMore && (
- <button
- onClick={loadMore}
- disabled={loading}
- className="w-full py-3 text-center text-blue-600 hover:text-blue-700 disabled:opacity-50 transition-colors"
- >
- {loading ? t('loading') : t('loadMore')}
- </button>
- )}
- </div>
- );
- };
|