RR

const MonthHeaderLabel: React.FC<{ m: string; isCurrent: boolean; monthLabel: (v: string) => string }> = ({
  m,
  isCurrent,
  monthLabel,
}) => {
  if (!isCurrent) return monthLabel(m);
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
      <span style={{ fontWeight: 600 }}>{monthLabel(m)}</span>
      <span
        style={{
          fontSize: 10,
          lineHeight: '16px',
          padding: '0 6px',
          color: '#1677ff',
          background: '#f0f7ff',
          borderRadius: 8,
          fontWeight: 400,
        }}
      >
        当月
      </span>
    </span>
  );
};
const getOperationColumn = (renderOperation: CommonBuilderParams['renderOperation']) => {
  return {
    title: '操作',
    key: 'operation',
    width: 57,
    fixed: 'right' as const,
    align: 'center' as const,
    onCell: () => ({ style: { textAlign: 'center' as const } }),
    onHeaderCell: () => ({ style: { textAlign: 'center' as const } }),
    render: renderOperation,
  };
};


const ProposalList: React.FC = () => {
 
const buildStaticCol = (params: {
    col: ColConfig;
    fixed: 'left' | 'right' | undefined;
    common: CommonBuilderParams;
  }) => {
    const { col, fixed, common } = params;
    const { hasData, dataSource, calcTextWidth } = common;
    const dataIndex = col.dataIndex as keyof ProposalData;

    const headerWidth = calcTextWidth(col.title);
    const dataWidth = dataSource.reduce((max, row) => Math.max(max, calcTextWidth(String(row[dataIndex] ?? ''))), 0);
    const colWidth = Math.max(headerWidth, dataWidth) + 56;

    return {
      title: col.title,
      dataIndex: col.dataIndex,
      key: col.key,
      width: colWidth,
      ...(fixed ? { fixed } : {}),
      onCell: () => ({ style: { whiteSpace: 'nowrap' } }),
      onHeaderCell: () => ({ style: { whiteSpace: 'nowrap' } }),
      sorter: hasData
        ? (a: ProposalData, b: ProposalData) => localeCompareJa(String(a[dataIndex] ?? ''), String(b[dataIndex] ?? ''))
        : (false as const),
      ...(hasData
        ? {
            filterDropdown: (props: FilterDropdownProps) => (
              <VirtualFilterDropdown {...props} filters={generateColumnFilters(dataSource, dataIndex)} />
            ),
            onFilter: (value: React.Key | boolean, record: ProposalData) =>
              matchColumnFilter(record[dataIndex], [value as React.Key]),
          }
        : {}),
    };
  };
  const buildMonthChildCol = (params: { col; m: string; isCurrent: boolean; common; extraWidth?: number }) => {
    const { col, m, isCurrent, common, extraWidth = 0 } = params;
    const { hasData, dataSource, calcTextWidth, rightFixedKey } = common;
    const monthKey = `${col.key}__${m}`;
    const isNumeric = col.numeric;

    const headerWidth = calcTextWidth(col.title);
    const dataWidth = dataSource.reduce(
      (max, row) => Math.max(max, calcTextWidth(String(row.monthly?.[monthKey] ?? ''))),
      0
    );
    const colWidth = Math.max(headerWidth, dataWidth) + 40 + extraWidth;
    const isRightFixed = !!rightFixedKey?.includes(col.key);

    const displayTitle =
      layout === 'period' ? col.title : <MonthHeaderLabel m={m} isCurrent={isCurrent} monthLabel={common.monthLabel} />;
    return {
      title: displayTitle,
      key: monthKey,
      dataIndex: ['monthly', monthKey],
      width: colWidth,
      fixed: isRightFixed ? ('right' as const) : undefined,
      align: isNumeric ? ('right' as const) : undefined,
      onCell: () => ({
        style: {
          whiteSpace: 'nowrap',
          ...(isNumeric ? { textAlign: 'right' as const } : {}),
          ...(isCurrent ? { backgroundColor: '#fafcff' } : {}),
        },
      }),
      onHeaderCell: () => ({
        style: { whiteSpace: 'nowrap', ...(isCurrent ? { backgroundColor: '#fafcff' } : {}) },
      }),
      render: (_: unknown, record: ProposalData) => record.monthly?.[monthKey] ?? '',
      sorter: hasData
        ? (a: ProposalData, b: ProposalData) =>
            localeCompareJa(String(a.monthly?.[monthKey] ?? ''), String(b.monthly?.[monthKey] ?? ''))
        : false,
    };
  };
  // 構築項目軸
  const buildItemColumns = (common, effectiveMonths: string[]) => {
    const { visibleCols, leftFixedKey, rightFixedKey, currentMonthKey, CURRENT_MONTH } = common;
    const leftItemCols: TableColumnsType<ProposalData> = [];
    const normalStaticItemCols: TableColumnsType<ProposalData> = [];
    const monthlyItemCols: TableColumnsType<ProposalData> = [];
    visibleCols.forEach((col) => {
      // --- 月次項目:選択された月ごとにサブ列を展開(固定しない・通常列扱い) ---
      if (col.monthly) {
        const subMonths = currentMonthKey?.includes(col.key) ? [CURRENT_MONTH] : effectiveMonths;
        monthlyItemCols.push({
          title: col.title,
          key: col.key,
          fixed: rightFixedKey?.includes(col.key) ? 'right' : undefined,
          children: subMonths.map((m) => {
            return buildMonthChildCol({ col, m, isCurrent: m === CURRENT_MONTH, common, extraWidth: 44 });
          }),
        });
        return;
      }

      if (leftFixedKey?.includes(col.key)) {
        leftItemCols.push(buildStaticCol({ col, fixed: 'left', common }));
      } else {
        normalStaticItemCols.push(buildStaticCol({ col, fixed: undefined, common }));
      }
    });

    const operationCol = getOperationColumn(common.renderOperation);

    const currentMonthCols = monthlyItemCols.filter((item) => currentMonthKey?.includes(item.key as string));
    const totalMonthCols = monthlyItemCols.filter(
      (item) => !currentMonthKey?.includes(item.key as string) && !item.fixed
    );

    const rightItemsCols = monthlyItemCols.filter((item) => item.fixed === 'right') ?? [];

    // 項目軸と列順を統一:左固定 → 通常静的 → 当月→ 月次項目 → 右固定 →操作列(右固定)
    return [
      ...leftItemCols,
      ...normalStaticItemCols,
      ...currentMonthCols,
      ...totalMonthCols,
      ...rightItemsCols,
      operationCol,
    ];
  };

  // 構築期間軸
  const buildPeriodColumns = (common, months: string[]) => {
    const { visibleCols, leftFixedKey, rightFixedKey, currentMonthKey, CURRENT_MONTH } = common;
    const staticVisibleCols = visibleCols.filter((col) => !col.monthly);
    const monthlyVisibleCols = visibleCols.filter((col) => col.monthly);
    const leftStaticCols: TableColumnsType<ProposalData> = [];
    const normalStaticCols: TableColumnsType<ProposalData> = [];
    staticVisibleCols.forEach((col) => {
      if (leftFixedKey?.includes(col.key)) {
        leftStaticCols.push(buildStaticCol({ col, fixed: 'left', common }));
      } else {
        normalStaticCols.push(buildStaticCol({ col, fixed: undefined, common }));
      }
    });
    const monthGroups = months.map((m) => {
      const isCurrent = m === CURRENT_MONTH;
      const childrenCols = monthlyVisibleCols
        .filter((col) => !currentMonthKey?.includes(col.key))
        .map((col) => buildMonthChildCol({ col, m, isCurrent, common }));

      return {
        title: <MonthHeaderLabel m={m} isCurrent={isCurrent} monthLabel={common.monthLabel} />,
        key: `group_${m}`,
        onHeaderCell: () => ({ style: { ...(isCurrent ? { backgroundColor: '#fafcff' } : {}) } }),
        children: childrenCols,
      };
    });

    const currentMonthGroups =
      currentMonthKey && currentMonthKey.length > 0
        ? [CURRENT_MONTH].map((m) => {
            const isCurrent = m === CURRENT_MONTH;
            const childrenCols = monthlyVisibleCols
              .filter((col) => currentMonthKey?.includes(col.key))
              .map((col) => buildMonthChildCol({ col, m, isCurrent, common }));

            return {
              title: <MonthHeaderLabel m={m} isCurrent={isCurrent} monthLabel={common.monthLabel} />,
              key: `group_${m}`,
              onHeaderCell: () => ({ style: { ...(isCurrent ? { backgroundColor: '#fafcff' } : {}) } }),
              children: childrenCols,
            };
          })
        : [];

    const rightItemsCols =
      rightFixedKey && rightFixedKey.length > 0
        ? monthGroups
            .map((item) => ({
              ...item,
              children: item.children.filter((child) => child.fixed === 'right'),
            }))
            .filter((g) => g.children.length > 0)
        : [];

    const monthlyItemsColsWithoutRight = monthGroups.map((item) => ({
      ...item,
      children: item.children.filter((child) => !child.fixed),
    }));

    const operationCol = getOperationColumn(common.renderOperation);

    return [
      ...leftStaticCols,
      ...normalStaticCols,
      ...currentMonthGroups,
      ...monthlyItemsColsWithoutRight,
      ...rightItemsCols,
      operationCol,
    ];
  };

  const columns = useMemo<TableColumnsType<ProposalData>>(() => {
    const targetKeys =
      visibleColumnKeys && visibleColumnKeys.length > 0 ? visibleColumnKeys : PROPOSAL_COLUMNS.map((opt) => opt.key);

    const visibleCols = targetKeys
      .map((key) => PROPOSAL_COLUMNS.find((col) => col.key === key))
      .filter(Boolean) as ColConfig[];

    console.log('visibleCols', visibleCols);
    const hasData = dataSource.length > 0;

    const common: CommonBuilderParams = {
      visibleCols,
      hasData,
      leftFixedKey,
      rightFixedKey,
      currentMonthKey,
      CURRENT_MONTH,
      dataSource,
      renderOperation,
      calcTextWidth,
      monthLabel,
      matchColumnFilter,
    };

    if (layout === 'period') {
      return buildPeriodColumns(common, effectiveMonths);
    } else {
      return buildItemColumns(common, effectiveMonths);
    }
  }, [
    visibleColumnKeys,
    leftFixedKey,
    rightFixedKey,
    currentMonthKey,
    dataSource,
    effectiveMonths,
    layout,
    renderOperation,
    PROPOSAL_COLUMNS,
    CURRENT_MONTH,
    calcTextWidth,
    matchColumnFilter,
    monthLabel,
  ]);
}




React Hook useMemo has missing dependencies: 'buildItemColumns' and 'buildPeriodColumns'. Either include them or remove the dependency array. Outer scope values like 'PROPOSAL_COLUMNS' aren't valid dependencies because mutating them doesn't re-render the component.eslintreact-hooks/exhaustive-deps
const effectiveMonths: string[]
posted @ 2026-10-15 13:36  小兔子09  阅读(2)  评论(0)    收藏  举报