web-app/src/components/dashboard/TabContainer.jsx
@@ -1,4 +1,8 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { useCaseData } from '../../contexts/CaseDataContext';
import { formatDuration, formatSuccessRate, formatRoundCount } from '../../utils/stateTranslator';
import ProcessAPIService from '../../services/ProcessAPIService';
import { message, Spin } from 'antd';
/**
 * 选项卡容器组件 - 4个选项卡
@@ -40,7 +44,7 @@
        {/* AI调解实时看板 */}
        <div className={`tab-pane ${activeTab === 'mediation-board' ? 'active' : ''}`}>
          <div className="tab-content-area">
            <MediationBoard />
            <MediationBoard activeTab={activeTab} />
          </div>
        </div>
@@ -66,6 +70,15 @@
 * 调解数据看板
 */
const MediationDataBoard = () => {
  const { caseData } = useCaseData();
  const timeline = caseData || {};
  // 从 timeline 获取数据
  const gapContent = timeline.result || '暂无分歧分析';
  const updateTime = formatDuration(timeline.before_duration);
  const successRate = formatSuccessRate(timeline.mediation?.success_rate);
  const roundCount = formatRoundCount(timeline.mediation?.mediation_count);
  return (
    <div className="mediation-metrics">
      {/* 左侧:诉求差距分析 */}
@@ -80,20 +93,9 @@
              <i className="fas fa-exclamation-circle"></i>
              主要分歧点
            </div>
            <div className="gap-update-time">3小时前更新</div>
            <div className="gap-update-time">{updateTime}</div>
            <div className="gap-content">
              双方在以下方面存在明显分歧:
              <ul>
                <li>
                  <strong>拖欠工资金额</strong>:李晓明主张拖欠3个月工资¥42,000,公司承认拖欠但只认可¥35,000
                </li>
                <li>
                  <strong>克扣绩效奖金</strong>:李晓明主张应发绩效奖金¥10,800,公司以未完成KPI为由拒绝支付
                </li>
                <li>
                  <strong>经济补偿金</strong>:李晓明要求支付解除劳动合同经济补偿¥12,000,公司认为员工主动辞职不应支付
                </li>
              </ul>
              {gapContent}
            </div>
          </div>
        </div>
@@ -107,14 +109,14 @@
        </div>
        <div className="metric-content">
          <div className="success-metric">
            <div className="success-value">68%</div>
            <div className="success-value">{successRate}</div>
            <div className="success-label">预计调解成功概率</div>
            <div className="success-change">
              <i className="fas fa-arrow-up"></i>
              <span>较3小时前 +8%</span>
              {/* <i className="fas fa-arrow-up"></i><span>较{updateTime} +8%</span> */}
            </div>
            <div style={{ marginTop: 15, fontSize: '0.9rem', color: 'var(--gray-color)' }}>
              协商沟通:<span style={{ color: 'var(--dark-color)', fontWeight: 600 }}>第6轮</span>
              协商沟通:<span style={{ color: 'var(--dark-color)', fontWeight: 600 }}>{roundCount}</span>
            </div>
          </div>
        </div>
@@ -126,11 +128,152 @@
/**
 * AI调解实时看板
 */
const MediationBoard = () => {
  const boardItems = [
const MediationBoard = ({ activeTab }) => {
  // 状态管理
  const [records, setRecords] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  // 获取案件数据
  const { caseData } = useCaseData();
  const timeline = caseData || {};
  // person_type到avatar类型的映射
  const getAvatarType = (personType) => {
    const typeMap = {
      '1': 'ai',
      '2': 'applicant',
      '3': 'respondent',
      '4': 'mediator'
    };
    return typeMap[personType] || 'ai';
  };
  // tag_style到UI标签样式的映射
  const getTagStyleType = (tagStyle) => {
    const styleMap = {
      'red': 'tag-type-5',
      'blue': 'tag-type-1',
      'green': 'tag-type-2',
      'orange': 'tag-type-3'
    };
    return styleMap[tagStyle] || 'tag-type-1';
  };
  // 获取角色显示名称
  const getRoleDisplayName = (personType, creatorName) => {
    const roleMap = {
      '1': 'AI调解员',
      '2': `申请人(${creatorName})`,
      '3': `被申请人(${creatorName})`,
      '4': `调解员(${creatorName})`
    };
    return roleMap[personType] || creatorName;
  };
  // 数据格式化函数
  const formatRecordData = (apiRecords) => {
    return apiRecords.map(record => ({
      avatar: getAvatarType(record.person_type),
      name: getRoleDisplayName(record.person_type, record.creator),
      avatarText: record.creator?.charAt(0) || '',  // 头像显示名字第一个字
      time: record.create_time,
      content: record.result,
      tags: record.tagList?.map(tag => ({
        text: tag.tag_name,
        type: getTagStyleType(tag.tag_style)
      })) || []
    }));
  };
  // 获取调解记录数据
  const loadMediationRecords = async () => {
    setLoading(true);
    setError(null);
    try {
      // 从timeline中获取mediation_id
      const mediationId = timeline.mediation?.id;
      if (!mediationId) {
        throw new Error('未找到调解ID');
      }
      // 调用API获取记录列表
      const response = await ProcessAPIService.getProcessRecords({
        mediation_id: mediationId
      });
      // 格式化数据
      const formattedRecords = formatRecordData(response.data || []);
      setRecords(formattedRecords);
    } catch (err) {
      setError(err.message);
      console.error('获取调解记录失败:', err);
      message.error(`获取调解记录失败: ${err.message}`);
    } finally {
      setLoading(false);
    }
  };
  // 监听Tab切换
  useEffect(() => {
    if (activeTab === 'mediation-board') {
      loadMediationRecords();
    }
  }, [activeTab]);
  // 如果还在加载中,显示Loading状态
  if (loading) {
    return (
      <div style={{
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
        height: '400px'
      }}>
        <Spin size="large" tip="正在加载调解记录..." />
      </div>
    );
  }
  // 如果有错误,显示错误信息
  if (error) {
    return (
      <div style={{
        textAlign: 'center',
        padding: '40px',
        color: '#ff4d4f'
      }}>
        <div style={{ fontSize: '1.2rem', marginBottom: '10px' }}>
          <i className="fas fa-exclamation-circle"></i>
          数据加载失败
        </div>
        <div>{error}</div>
        <button
          onClick={loadMediationRecords}
          style={{
            marginTop: '15px',
            padding: '8px 16px',
            backgroundColor: '#1890ff',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          重新加载
        </button>
      </div>
    );
  }
  // 使用动态数据或默认mock数据
  const displayRecords = records.length > 0 ? records : [
    {
      avatar: 'ai',
      name: 'AI调解员',
      avatarText: '',
      time: '09:30:05',
      content: '已分别联系李晓明(申请人)和广东好又多贸易有限公司(被申请人)。双方均表示愿意接受调解,希望通过协商解决劳动争议/拖欠、克扣工资纠纷。',
      tags: [{ text: '意愿调查', type: 'tag-type-1' }, { text: '初步接触', type: 'tag-type-2' }],
@@ -138,6 +281,7 @@
    {
      avatar: 'applicant',
      name: '申请人(李晓明)',
      avatarText: '李',
      time: '09:35:22',
      content: '我在好又多公司担任销售经理3年,公司拖欠我3个月工资共¥42,000,还克扣了季度绩效奖金¥10,800。另外,公司单方面解除劳动合同,应支付经济补偿金¥12,000。我要求公司支付总共¥52,800。',
      tags: [{ text: '诉求表达', type: 'tag-type-3' }, { text: '情绪激动', type: 'tag-type-5' }],
@@ -145,12 +289,16 @@
    {
      avatar: 'respondent',
      name: '被申请人(好又多公司)',
      avatarText: '好',
      time: '09:40:15',
      content: '公司确实遇到了资金周转困难,拖欠工资问题承认。但李晓明主张的金额有误:1) 3个月工资应为¥35,000(含请假扣款);2) 绩效奖金因未完成KPI指标不应发放;3) 是李晓明主动提出辞职,不应支付经济补偿金。公司最多支付¥38,500。',
      tags: [{ text: '诉求表达', type: 'tag-type-3' }],
    },
  ];
  // 从timeline获取沟通情况总结
  const communicationSummary = timeline.mediation?.summary || '暂无沟通情况总结';
  const getAvatarClass = (avatar) => {
    const map = {
      ai: 'avatar-ai',
@@ -171,12 +319,10 @@
    return map[avatar] || 'content-ai';
  };
  const getAvatarContent = (avatar) => {
  const getAvatarContent = (avatar, avatarText) => {
    if (avatar === 'ai') return <i className="fas fa-robot"></i>;
    if (avatar === 'applicant') return '李';
    if (avatar === 'respondent') return '好';
    if (avatar === 'mediator') return '调';
    return <i className="fas fa-robot"></i>;
    // 显示名字的第一个字
    return avatarText || (avatar === 'applicant' ? '申' : avatar === 'respondent' ? '被' : '调');
  };
  return (
@@ -195,12 +341,12 @@
          沟通情况总结
        </div>
        <div style={{ lineHeight: 1.5 }}>
          截至目前,AI调解员已与申请双方进行协商沟通6轮,期间申请人补充材料2次,被申请人补充材料2次,双方对调解方案表现出积极协商态度。
          {communicationSummary}
        </div>
      </div>
      <div className="board-container" style={{ maxHeight: 450, overflowY: 'auto' }}>
        {boardItems.map((item, index) => (
        {displayRecords.map((item, index) => (
          <div key={index} className="board-item" style={{
            background: '#f8f9fa',
            borderRadius: 'var(--border-radius)',
@@ -224,7 +370,7 @@
                  item.avatar === 'respondent' ? 'linear-gradient(135deg, #e9c46a, #e76f51)' :
                  'linear-gradient(135deg, #7209b7, #3a0ca3)',
              }}>
                {getAvatarContent(item.avatar)}
                {getAvatarContent(item.avatar, item.avatarText)}
              </div>
              <div className="item-source">
                <div style={{ fontWeight: 600, fontSize: '0.95rem', color: 'var(--dark-color)', marginBottom: 2 }}>{item.name}</div>