import React, { useEffect, useState, useCallback, memo } from 'react'; import { Table, Empty, Spin, Typography, Tag, Drawer, Space, Avatar, Select, Button, Tooltip, Input, Badge, Tabs, Divider, theme, } from 'antd'; import { MergeOutlined, BranchesOutlined, UserOutlined, ClockCircleOutlined, CommentOutlined, ExportOutlined, SearchOutlined, CheckCircleOutlined, CloseCircleOutlined, ExclamationCircleOutlined, FileTextOutlined, ReloadOutlined, } 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, Paragraph, Title } = Typography; const { useToken } = theme; const STATE_CONFIG: Record = { opened: { color: 'blue', icon: , label: 'Open' }, merged: { color: 'purple', icon: , label: 'Merged' }, closed: { color: 'red', icon: , label: 'Closed' }, }; export const MergeRequests: React.FC = () => { const t = useT(); const { token } = useToken(); const api = useApp().apiClient; const { selectedRepo } = useGitManager(); const [mergeRequestsList, setMergeRequestsList] = useState([]); const [loading, setLoading] = useState(false); const [stateFilter, setStateFilter] = useState('opened'); const [searchText, setSearchText] = useState(''); const [pagination, setPagination] = useState({ page: 1, perPage: 20, total: 0, totalPages: 0 }); const [selectedMR, setSelectedMR] = useState(null); const [mrDetail, setMrDetail] = useState(null); const [detailLoading, setDetailLoading] = useState(false); const [notes, setNotes] = useState([]); const [notesLoading, setNotesLoading] = useState(false); const loadMergeRequests = useCallback( async (page = 1) => { if (!selectedRepo) return; setLoading(true); try { const { data } = await api.request({ url: 'gitManager:mergeRequests', params: { repositoryId: selectedRepo.id, state: stateFilter, search: searchText || undefined, page, perPage: 20, }, }); const responseData = data?.data || data; setMergeRequestsList(responseData?.data || []); if (responseData?.pagination) { setPagination(responseData.pagination); } } catch (err: any) { setMergeRequestsList([]); } finally { setLoading(false); } }, [api, selectedRepo, stateFilter, searchText], ); useEffect(() => { if (selectedRepo?.status === 'connected') { loadMergeRequests(1); } else { setMergeRequestsList([]); } }, [selectedRepo, stateFilter]); const handleSearch = () => { loadMergeRequests(1); }; const openMRDetail = async (mr: any) => { setSelectedMR(mr); setDetailLoading(true); setNotes([]); try { const [detailRes, notesRes] = await Promise.all([ api.request({ url: 'gitManager:mergeRequestDetail', params: { repositoryId: selectedRepo!.id, mrIid: mr.iid }, }), api.request({ url: 'gitManager:mergeRequestNotes', params: { repositoryId: selectedRepo!.id, mrIid: mr.iid }, }), ]); const detailData = detailRes?.data?.data?.data || detailRes?.data?.data; setMrDetail(detailData); const notesData = notesRes?.data?.data?.data || notesRes?.data?.data; setNotes(Array.isArray(notesData) ? notesData : []); } catch { setMrDetail(null); } finally { setDetailLoading(false); } }; if (!selectedRepo) { return ; } if (selectedRepo.status !== 'connected') { return ; } const columns = [ { title: '', key: 'state_icon', width: 40, render: (_: any, record: any) => { const cfg = STATE_CONFIG[record.state] || STATE_CONFIG.opened; return ( {cfg.icon} ); }, }, { title: t('Title'), key: 'title', render: (_: any, record: any) => (
{record.draft && ( DRAFT )} {record.title} {record.hasConflicts && ( )}
!{record.iid} ยท {record.author?.name || record.author?.username || 'Unknown'} ยท {formatDate(record.updatedAt)}
), }, { title: '', key: 'branches', width: 250, render: (_: any, record: any) => (
{record.sourceBranch} โ†’ {record.targetBranch}
), }, { title: '', key: 'labels', width: 180, render: (_: any, record: any) => ( {(record.labels || []).slice(0, 3).map((label: string) => ( {label} ))} {record.labels?.length > 3 && ( +{record.labels.length - 3} )} ), }, { title: '', key: 'meta', width: 120, render: (_: any, record: any) => ( {record.userNotesCount > 0 && ( )} {record.assignees?.length > 0 && ( {record.assignees.map((a: any) => ( {a.name?.[0]} ))} )} ), }, { title: '', key: 'actions', width: 160, render: (_: any, record: any) => ( e.stopPropagation()}>