aaa

import RecordCountDisplay from '@/components/RecordCountDisplay';
import { useTableScrollY } from '@/hooks/useTableScrollY';
import { Alert, Button, Card, Flex } from 'antd';
import dayjs from 'dayjs';
import { useCallback, useMemo, useState } from 'react';
import ListModeSelector from './components/ListModeSelectorCopy';
import ProposalSearchPanel from './components/ProposalSearchCopy';
import TableForProject from './components/TableForProject';
import { PROPOSAL_LIST } from './constantsCopy';

// 当月 表示样式
const CURRENT_MONTH_BG = '#f0f7ff'; // 淡蓝色背景
const CURRENT_MONTH_COLOR = '#1677ff'; // Antd 主题蓝
const MonthHeaderLabel: React.FC<{ monthStr: string }> = ({ monthStr }) => {
  // 将 "2026/10" 或 "2026-10" 统一转换为 dayjs 对象进行安全比对
  const isCurrentMonth = dayjs(monthStr).isSame(dayjs(), 'month');
  if (!isCurrentMonth) {
    return <>{monthStr}</>;
  }

  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
      <span style={{ fontWeight: 600 }}>{monthStr}</span>
      <span
        style={{
          fontSize: 10,
          lineHeight: '16px',
          padding: '0 6px',
          color: CURRENT_MONTH_COLOR,
          background: CURRENT_MONTH_BG,
          borderRadius: 8,
          fontWeight: 400,
        }}
      >
        当月
      </span>
    </span>
  );
};

const formatTreeData = (list: any[]): any[] => {
  if (!list || !Array.isArray(list)) return [];

  return list.map((row, index) => {
    // 1. 浅拷贝基础字段
    const formattedRow = { ...row };

    // 🛠️ 核心修复点 1:为第一层合计父行,生成唯一的 key。例如 "parent_1"
    // 这样就算不同大组的 sssAccountID 重复,加上前缀也绝对不会撞车!
    formattedRow.key = `parent_${row.sssAccountID || index}`;

    // 2. 核心转化:遍历当前行的 dataList 数组
    if (row.dataList && Array.isArray(row.dataList)) {
      row.dataList.forEach((dataItem: any) => {
        if (dataItem.monthly) {
          const monthKey = `monthly_${dataItem.monthly}`;
          formattedRow[monthKey] = { ...dataItem };
        }
      });
    }

    // 3. ✨ 核心修复点 2:为子明细行注入【绝不撞车】的独立 key 身份证!
    if (row.children && Array.isArray(row.children)) {
      formattedRow.children = row.children.map((child, cIdx) => {
        const formattedChild = { ...child };

        // 🎯 降维打击:子行的 key 拼装为 "child_父行ID_子行索引"(例如 "child_1_0", "child_1_1")
        // 这样它们的 key 就绝对变成了全网唯一,跟父行的 'parent_1' 彻底绝缘!
        formattedChild.key = `child_${row.sssAccountID || index}_${cIdx}`;

        // 同时把子项里的 dataList 数组也洗一遍
        if (child.dataList && Array.isArray(child.dataList)) {
          child.dataList.forEach((dataItem: any) => {
            if (dataItem.monthly) {
              const monthKey = `monthly_${dataItem.monthly}`;
              formattedChild[monthKey] = { ...dataItem };
            }
          });
        }

        return formattedChild;
      });
    } else {
      formattedRow.children = undefined; // 抹平空子行
    }

    return formattedRow;
  });
};

const ProposalList: React.FC = () => {
  const { containerRef, tableContainerRef } = useTableScrollY();
  const [selectDate, setSelectDate] = useState(['2026-09']);
  const [loading, setLoading] = useState(false);
  const [data, setData] = useState([]);
  const [currentMode, setCurrentMode] = useState('');
  const [layout, setLayout] = useState('project');

  const handleSearch = (nextDate) => {
    console.log('111111', nextDate);
    setSelectDate(nextDate);
    return fetchData(nextDate);
  };
  const fetchData = useCallback(async ({ nextDate }) => {
    setLoading(true);
    const params: any = {
      selectDate: nextDate,
    };
    console.log(params);
    //   const res = await getProjectList(params);
    //   console.log('res', res);
    // const dataResponse: any = [
    //   {
    //     sssAccountID: '1',
    //     sssDept: 'DX推進室',
    //     sssTeam: 'チームA',
    //     secDept: '第一開発',
    //     secTeam: 'チーム1',
    //     dlDept: '大連3部',
    //     dlRole: 'PM',
    //     vendonContractName: 'ベンダ契約-0015',
    //     secContractName: '証券契約-0015',
    //     dlContractName: '直接契約',
    //     hasTotal: true,
    //     dataList: [
    //       {
    //         monthly: '202609',
    //         holidayRate: '100',
    //         securityRate: '333',
    //         memo: '1111',
    //       },
    //       {
    //         monthly: '202610',
    //         holidayRate: '200',
    //         securityRate: '444',
    //         memo: '2222',
    //       },
    //     ],
    //   },
    //   {
    //     sssAccountID: '2',
    //     sssDept: 'DX推進室',
    //     sssTeam: 'チームA',
    //     secDept: '第一開発',
    //     secTeam: 'チーム1',
    //     dlDept: '大連3部',
    //     dlRole: 'PM',
    //     vendonContractName: 'ベンダ契約-0015',
    //     secContractName: '証券契約-0015',
    //     dlContractName: '直接契約',
    //     dataList: [
    //       {
    //         monthly: '202609',
    //         holidayRate: '100',
    //         securityRate: '333',
    //         memo: '1111',
    //       },
    //     ],
    //   },
    //   {
    //     sssAccountID: '2',
    //     sssDept: '第一開発',
    //     sssTeam: 'チームB',
    //     secDept: 'インフラ部',
    //     secTeam: 'チーム2',
    //     dlDept: '大連1部',
    //     dlRole: 'PL',
    //     vendonContractName: 'ベンダ契約-0001',
    //     secContractName: '証券契約-0001',
    //     dlContractName: '直接契約',
    //     dataList: [
    //       {
    //         monthly: '202610',
    //         holidayRate: '200',
    //         securityRate: '444',
    //         memo: '2222',
    //       },
    //     ],
    //   },
    // ];
    const dataResponse: any = [
      {
        sssAccountID: '1',
        sssDept: 'DX推進室',
        sssTeam: 'チームA',
        secDept: '第一開発',
        secTeam: 'チーム1',
        dlDept: '大連3部',
        dlRole: 'PM',
        vendonContractName: 'ベンダ契約-0015',
        secContractName: '証券契約-0015',
        dlContractName: '直接契約',
        hasTotal: true,
        dataList: [
          {
            monthly: '202607',
            holidayRate: '1.0',
            securityRate: '333',
            memo: '1111',
          },
          {
            monthly: '202608',
            holidayRate: '1.0',
            securityRate: '444',
            memo: '2222',
          },
          {
            monthly: '202609',
            holidayRate: '1.0',
            securityRate: '444',
            memo: '2222',
          },
        ],
        children: [
          {
            sssAccountID: '1',
            sssDept: 'DX推進室',
            sssTeam: 'チームA',
            secDept: '第一開発',
            secTeam: 'チーム1',
            dlDept: '大連3部',
            dlRole: 'PM',
            vendonContractName: 'ベンダ契約-0015',
            secContractName: '証券契約-0015',
            dlContractName: '直接契約',
            hasTotal: false,
            dataList: [
              {
                monthly: '202607',
                holidayRate: '0.3',
                securityRate: '333',
                memo: '1111',
              },
              {
                monthly: '202608',
                holidayRate: '0.4',
                securityRate: '444',
                memo: '2222',
              },
              {
                monthly: '202609',
                holidayRate: '1.0',
                securityRate: '444',
                memo: '2222',
              },
            ],
          },
          {
            sssAccountID: '1',
            sssDept: 'DX推進室',
            sssTeam: 'チームB',
            secDept: '第一開発',
            secTeam: 'チーム1',
            dlDept: '大連3部',
            dlRole: 'PM',
            vendonContractName: 'ベンダ契約-0015',
            secContractName: '証券契約-0015',
            dlContractName: '直接契約',
            hasTotal: false,
            dataList: [
              {
                monthly: '202607',
                holidayRate: '0.7',
                securityRate: '333',
                memo: '1111',
              },
              {
                monthly: '202608',
                holidayRate: '0.6',
                securityRate: '444',
                memo: '2222',
              },
              {
                monthly: '202609',
                holidayRate: '0',
                securityRate: '444',
                memo: '2222',
              },
            ],
          },
        ],
      },
      {
        sssAccountID: '2',
        sssDept: 'DX推進室',
        sssTeam: 'チームA',
        secDept: '第一開発',
        secTeam: 'チーム1',
        dlDept: '大連3部',
        dlRole: 'PM',
        vendonContractName: 'ベンダ契約-0015',
        secContractName: '証券契約-0015',
        dlContractName: '直接契約',
        hasTotal: false,
        dataList: [
          {
            monthly: '202607',
            holidayRate: '2.0',
            securityRate: '333',
            memo: '1111',
          },
          {
            monthly: '202608',
            holidayRate: '3.0',
            securityRate: '444',
            memo: '2222',
          },
          {
            monthly: '202609',
            holidayRate: '4.0',
            securityRate: '444',
            memo: '2222',
          },
        ],
        children: [],
      },
      // {
      //   sssAccountID: '1',
      //   sssDept: 'DX推進室',
      //   sssTeam: 'チームA',
      //   secDept: '第一開発',
      //   secTeam: 'チーム1',
      //   dlDept: '大連3部',
      //   dlRole: 'PM',
      //   vendonContractName: 'ベンダ契約-0015',
      //   secContractName: '証券契約-0015',
      //   dlContractName: '直接契約',
      //   hasTotal: false,
      //   dataList: [
      //     {
      //       monthly: '202607',
      //       holidayRate: '0.3',
      //       securityRate: '333',
      //       memo: '1111',
      //     },
      //     {
      //       monthly: '202608',
      //       holidayRate: '0.4',
      //       securityRate: '444',
      //       memo: '2222',
      //     },
      //     {
      //       monthly: '202609',
      //       holidayRate: '1.0',
      //       securityRate: '444',
      //       memo: '2222',
      //     },
      //   ],
      // },
      // {
      //   sssAccountID: '1',
      //   sssDept: 'DX推進室',
      //   sssTeam: 'チームB',
      //   secDept: '第一開発',
      //   secTeam: 'チーム1',
      //   dlDept: '大連3部',
      //   dlRole: 'PM',
      //   vendonContractName: 'ベンダ契約-0015',
      //   secContractName: '証券契約-0015',
      //   dlContractName: '直接契約',
      //   hasTotal: false,
      //   dataList: [
      //     {
      //       monthly: '202607',
      //       holidayRate: '0.7',
      //       securityRate: '333',
      //       memo: '1111',
      //     },
      //     {
      //       monthly: '202608',
      //       holidayRate: '0.6',
      //       securityRate: '444',
      //       memo: '2222',
      //     },
      //     {
      //       monthly: '202609',
      //       holidayRate: '0',
      //       securityRate: '444',
      //       memo: '2222',
      //     },
      //   ],
      // },
      // {
      //   sssAccountID: '2',
      //   sssDept: 'DX推進室',
      //   sssTeam: 'チームA',
      //   secDept: '第一開発',
      //   secTeam: 'チーム1',
      //   dlDept: '大連3部',
      //   dlRole: 'PM',
      //   vendonContractName: 'ベンダ契約-0015',
      //   secContractName: '証券契約-0015',
      //   dlContractName: '直接契約',
      //   dataList: [
      //     {
      //       monthly: '202609',
      //       holidayRate: '100',
      //       securityRate: '333',
      //       memo: '1111',
      //     },
      //   ],
      // },
      // {
      //   sssAccountID: '2',
      //   sssDept: '第一開発',
      //   sssTeam: 'チームB',
      //   secDept: 'インフラ部',
      //   secTeam: 'チーム2',
      //   dlDept: '大連1部',
      //   dlRole: 'PL',
      //   vendonContractName: 'ベンダ契約-0001',
      //   secContractName: '証券契約-0001',
      //   dlContractName: '直接契約',
      //   dataList: [
      //     {
      //       monthly: '202610',
      //       holidayRate: '200',
      //       securityRate: '444',
      //       memo: '2222',
      //     },
      //   ],
      // },
    ];
    const formatData: any = formatTreeData(dataResponse);
    setData(formatData);
    // setData(dataResponse);
    setLoading(false);
  }, []);

  const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);

  const handleModeChange = useCallback((value: string) => {
    console.log('选择哪个Mode了', value);
    setCurrentMode(value);
  }, []);

  const handleLayoutChange = useCallback((value: string) => {
    console.log('选择哪个Layout了', value);
    setLayout(value);
  }, []);

  // // TODO: 列权限 控制
  const getMonthsInRange = (selectDate) => {
    if (!selectDate || !selectDate[0] || !selectDate[1]) {
      const currentMonth = dayjs().format('YYYY/MM'); // 例如今天的日期会生成 "2026/10"
      return [currentMonth];
    }
    const start = dayjs(selectDate?.[0]);
    const end = dayjs(selectDate?.[1]);

    const months: string[] = [];
    let current = start;

    // 生成期间的每一个月份
    while (current.isBefore(end) || current.isSame(end, 'month')) {
      months.push(current.format('YYYY/MM'));
      current = current.add(1, 'month'); // 每次增加一个月
    }
    return months;
  };
  const generateChildrenColumn = (current, totalDate: string[]) => {
    return totalDate.map((item) => {
      const isCurrent = dayjs(item).isSame(dayjs(), 'month');

      return {
        title: <MonthHeaderLabel monthStr={item} />,
        dataIndex: [`monthly_${item.replace('/', '')}`, current.itemVar],
        align: 'right' as const,
        width: 180,
        onHeaderCell: () => ({
          style: isCurrent ? { backgroundColor: '#fafcff' } : {},
        }),
        onCell: () => ({
          style: isCurrent ? { backgroundColor: '#fafcff' } : {},
        }),
      };
    });
  };

  // children: [
  //         {
  //           title: '2026/09',
  //           dataIndex: ['monthly_202609', item.key],
  //         },
  //         {
  //           title: '2026/10',
  //           dataIndex: ['monthly_202610', item.key],
  //         },
  //       ],
  // 項目軸表示

  const buildColumnsForTableProject = (selectDate) => {
    console.log('column接收到的日期', selectDate);
    const totalDate = getMonthsInRange(selectDate);

    console.log('totalDate', totalDate);
    return PROPOSAL_LIST.map((current) => {
      const hasChildren = current.dynamic && totalDate && totalDate.length > 0;
      const baseCol: any = {
        title: current.itemName,
        ...(hasChildren ? {} : { dataIndex: current.itemVar }),
        key: current.itemVar,
        ...(hasChildren ? { children: generateChildrenColumn(current, totalDate) } : {}),
        width: 150,
      };
      if (current.itemName === '合計有無') {
        baseCol.render = (hasTotal: boolean, record: any) => {
          console.log('recordxxxxxxxxxx', record);
          if (!hasTotal) return <span style={{ color: '#bfbfbf' }}>无</span>;

          const parentKey = record.key || record.sssAccountID;
          const isExpanded = expandedKeys.includes(parentKey);
          console.log('isExpanded', isExpanded);
          return (
            <span
              onClick={(e) => {
                e.stopPropagation(); // 阻断行点击,防止产生多余副作用
                setExpandedKeys((prev) => (isExpanded ? prev.filter((k) => k !== parentKey) : [...prev, parentKey]));
              }}
              style={{
                cursor: 'pointer',
                display: 'inline-flex',
                alignItems: 'center',
                gap: 6,
                paddingLeft: 8,
                color: '#1677ff',
                userSelect: 'none',
              }}
            >
              <span
                style={{
                  fontSize: 12,
                  transition: 'transform 0.2s',
                  display: 'inline-block',
                  transform: isExpanded ? 'rotate(90deg)' : 'none',
                }}
              >
                ▶
              </span>
              <span style={{ fontWeight: 500 }}>有</span>
            </span>
          );
        };
      }

      return baseCol;
    });
  };

  // 期間軸表示
  const buildColumnsForTableDateRange = (selectDate) => {
    const totalDate = getMonthsInRange(selectDate); // 这里拿到的 totalDate 绝对是数组,最少也是 []

    // const staticColumns = PROPOSAL_LIST.filter((item) => !item.dynamic).map((item) => ({
    //   title: item.itemName,
    //   dataIndex: item.itemVar,
    //   key: item.itemVar,
    //   width: 120,
    // }));

    const staticColumns = PROPOSAL_LIST.filter((item) => !item.dynamic).map((item) => {
      const baseCol: any = {
        title: item.itemName,
        dataIndex: item.itemVar,
        key: item.itemVar,
        width: 120,
      };
      if (item.itemName === '合計有無') {
        baseCol.render = (hasTotal: boolean, record: any) => {
          console.log('recordxxxxxxxxxx', record);
          if (!hasTotal) return <span style={{ color: '#bfbfbf' }}>无</span>;

          const parentKey = record.key || record.sssAccountID;
          const isExpanded = expandedKeys.includes(parentKey);
          console.log('isExpanded', isExpanded);
          return (
            <span
              onClick={(e) => {
                e.stopPropagation(); // 阻断行点击,防止产生多余副作用
                setExpandedKeys((prev) => (isExpanded ? prev.filter((k) => k !== parentKey) : [...prev, parentKey]));
              }}
              style={{
                cursor: 'pointer',
                display: 'inline-flex',
                alignItems: 'center',
                gap: 6,
                color: '#1677ff',
                userSelect: 'none',
              }}
            >
              <span
                style={{
                  fontSize: 12,
                  transition: 'transform 0.2s',
                  display: 'inline-block',
                  transform: isExpanded ? 'rotate(90deg)' : 'none',
                }}
              >
                ▶
              </span>
              <span style={{ fontWeight: 500 }}>有</span>
            </span>
          );
        };
      }
      return baseCol;
    });

    const dynamicItems = PROPOSAL_LIST.filter((item) => item.dynamic);

    const monthGroups = totalDate.map((month) => {
      const isCurrent = dayjs(month).isSame(dayjs(), 'month');
      console.log('isCurrent', isCurrent);
      return {
        title: <MonthHeaderLabel monthStr={month} />,
        key: `group-${month}`,
        align: 'center' as const,
        onHeaderCell: () => ({
          style: isCurrent ? { backgroundColor: '#fafcff' } : {},
        }),
        children: dynamicItems.map((item) => {
          const cleanMonth = month.replace('/', '');
          const monthKey = `monthly_${cleanMonth}`;

          return {
            title: item.itemName,
            key: `${monthKey}__${item.itemVar}`,
            dataIndex: [monthKey, item.itemVar],
            align: 'left' as const,
            width: 180,
            onHeaderCell: () => {
              return {
                style: isCurrent ? { backgroundColor: '#fafcff' } : {},
              };
            },
            onCell: () => {
              return {
                style: isCurrent ? { backgroundColor: '#fafcff' } : {},
              };
            },
          };
        }),
      };
    });

    return [...staticColumns, ...monthGroups];
  };

  const columnsForProject = useMemo(() => {
    return buildColumnsForTableProject(selectDate);
  }, [selectDate, expandedKeys, setExpandedKeys]);

  console.log('columnsForProject', columnsForProject);

  const columnsForDateRange = useMemo(() => {
    return buildColumnsForTableDateRange(selectDate);
  }, [selectDate, expandedKeys, setExpandedKeys]);

  console.log('columnsForDateRange', columnsForDateRange);

  return (
    <div ref={containerRef} className="scroll-card-container">
      <Card>
        <div>
          <ProposalSearchPanel onSearch={handleSearch} />
          <Alert
            description="対象月が未選択のため、当月(2026/09)の情報を表示しています。"
            type="info"
            showIcon
            banner
            style={{ padding: '5px 12px' }}
          />
        </div>
        <Flex justify="space-between" className="mt-5 mb-0 h-8">
          <ListModeSelector onModeChange={handleModeChange} onLayoutChange={handleLayoutChange}></ListModeSelector>
          <Flex gap="small">
            <Button type="primary" size="small">
              追加
            </Button>
          </Flex>
        </Flex>
        <div className="ml-2">
          <RecordCountDisplay count={100} />
        </div>
        <div ref={tableContainerRef} className="scroll-table-container">
          {/* {layout === 'project' ? (
            <TableForProject
              selectDate={selectDate}
              loading={loading}
              resData={data}
              currentMode={currentMode}
              columns={columns}
            ></TableForProject>
          ) : (
            <TableForDateRange columns={columnsForDateRange}></TableForDateRange>
          )} */}

          <TableForProject
            selectDate={selectDate}
            loading={loading}
            resData={data}
            currentMode={currentMode}
            columns={layout === 'project' ? columnsForProject : columnsForDateRange}
            expandedKeys={expandedKeys}
            setExpandedKeys={setExpandedKeys}
          ></TableForProject>
        </div>
      </Card>
    </div>
  );
};

export default ProposalList;
posted @ 2026-10-09 14:16  小兔子09  阅读(4)  评论(0)    收藏  举报