import React, { useEffect, useState, useCallback, memo } from 'react'; import { Table, Empty, Spin, Typography, Tag, Drawer, List, Button, Space, Select, Input, theme } from 'antd'; import { BranchesOutlined, UserOutlined, ClockCircleOutlined, FileTextOutlined, PlusCircleOutlined, EditOutlined, DeleteOutlined, DiffOutlined, } from '@ant-design/icons'; import { useApp } from '@nocobase/client-v2'; import { useGitManager } from '../context/GitManagerContext'; import { RunReviewButton } from './RunReviewButton'; import { useT } from '../locale'; const { Text } = Typography; const { useToken } = theme; const STATUS_COLORS: Record = { A: { color: 'green', icon: , label: 'Added' }, M: { color: 'blue', icon: , label: 'Modified' }, D: { color: 'red', icon: , label: 'Deleted' }, R: { color: 'orange', icon: , label: 'Renamed' }, }; export const CommitHistory: React.FC = () => { const t = useT(); const { token } = useToken(); const api = useApp().apiClient; const { selectedRepo, branches: branchList, currentBranch } = useGitManager(); const [commits, setCommits] = useState([]); const [loading, setLoading] = useState(false); const [selectedCommit, setSelectedCommit] = useState(null); const [commitDetail, setCommitDetail] = useState(null); const [detailLoading, setDetailLoading] = useState(false); const [diffContent, setDiffContent] = useState(null); const [diffFile, setDiffFile] = useState(null); const [searchKeyword, setSearchKeyword] = useState(''); const loadHistory = useCallback(async () => { if (!selectedRepo) return; setLoading(true); try { const { data } = await api.request({ url: 'gitManager:log', params: { repositoryId: selectedRepo.id, maxCount: 100 }, }); const responseData = data?.data?.data || data?.data; setCommits(responseData?.all || []); } catch (error) { console.warn('Failed to load commit history:', error); setCommits([]); } finally { setLoading(false); } }, [api, selectedRepo]); useEffect(() => { if (selectedRepo?.status === 'connected') { loadHistory(); } else { setCommits([]); } }, [selectedRepo]); const filteredCommits = commits.filter((commit) => { const keyword = searchKeyword.trim().toLowerCase(); if (!keyword) return true; return String(commit.message || commit.subject || commit.body || '') .toLowerCase() .includes(keyword); }); const openCommitDetail = async (commit: any) => { setSelectedCommit(commit); setDetailLoading(true); setDiffContent(null); setDiffFile(null); try { const { data } = await api.request({ url: 'gitManager:commitDetail', params: { repositoryId: selectedRepo.id, commitHash: commit.hash }, }); const responseData = data?.data?.data || data?.data; setCommitDetail(responseData); } catch { setCommitDetail(null); } finally { setDetailLoading(false); } }; const loadFileDiff = async (commitHash: string, file: string) => { setDiffFile(file); try { const { data } = await api.request({ url: 'gitManager:diff', params: { repositoryId: selectedRepo.id, commitHash, file }, }); const responseData = data?.data?.data || data?.data; setDiffContent(responseData || ''); } catch { setDiffContent('// Failed to load diff'); } }; if (!selectedRepo) { return ; } if (selectedRepo.status !== 'connected') { return ; } const columns = [ { title: '', key: 'graph', width: 40, render: (_: any, __: any, index: number) => (
), }, { title: t('Message'), dataIndex: 'message', key: 'message', render: (text: string, record: any) => (
{text}
{record.author_name} · {formatDate(record.date)}
), }, { title: 'Hash', dataIndex: 'hash', key: 'hash', width: 100, render: (hash: string) => ( {hash?.substring(0, 7)} ), }, { title: '', key: 'action', width: 80, render: (_: any, record: any) => ( ), }, ]; return (