table

 const columns = useMemo<TableColumnsType<ProposalData>>(() => {
    const visibleCols = PROPOSAL_COLUMNS.filter((col) => visibleColumnKeys.includes(col.key));

    // 左固定の基準インデックス(visibleCols 上の位置)。左固定は静的列のみ対象。
    // モーダル側で妥当性を検証済みのため、ここでは指定列の位置をそのまま採用する。
    // const leftFixedCol = leftFixedKey && leftFixedKey.length > 0 ? PROPOSAL_COLUMNS.find((c) => c.key === leftFixedKey) : undefined;
    // const leftIndex = leftFixedCol && !leftFixedCol.monthly ? visibleCols.findIndex((col) => col.key === leftFixedKey) : -1;

    const hasData = dataSource.length > 0;
    let months = effectiveMonths;

    // 静的列の描画定義を作る共通関数
    const buildStaticCol = (col: (typeof visibleCols)[number], fixed: 'left' | 'right' | undefined) => {
      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]),
            }
          : {}),
      };
    };

    // ============ 期間軸表示:上=月、下=各項目(月を主軸に列を展開) ============
    if (layout === 'period') {
      const staticVisibleCols = visibleCols.filter((col) => !col.monthly);
      const monthlyVisibleCols = visibleCols.filter((col) => col.monthly);

      // leftFixedKey が静的列の場合、その静的列までを左固定にする。
      // const staticLeftIndex =
      //   leftFixedKey && !PROPOSAL_COLUMNS.find((c) => c.key === leftFixedKey)?.monthly
      //     ? staticVisibleCols.findIndex((c) => c.key === leftFixedKey)
      //     : -1;

      // 静的列を「左固定・通常」に振り分け
      const leftStaticCols: TableColumnsType<ProposalData> = [];
      const normalStaticCols: TableColumnsType<ProposalData> = [];
      staticVisibleCols.forEach((col) => {
        if (leftFixedKey?.includes(col.key)) {
          leftStaticCols.push(buildStaticCol(col, 'left'));
        } else {
          normalStaticCols.push(buildStaticCol(col, undefined));
        }
      });

      // 月ごとにグループ化:親=月、子=各月次項目

      const monthGroups = months.map((m) => {
        const isCurrent = m === CURRENT_MONTH;
        const childrenCols = monthlyVisibleCols
          .filter((col) => !currentMonthKey?.includes(col.key))
          .map((col) => {
            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;

            const isRightFixed = !!rightFixedKey?.includes(col.key);

            return {
              title: col.title,
              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,
            };
          });
        return {
          title: isCurrent ? (
            <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>
          ) : (
            monthLabel(m)
          ),
          key: `group_${m}`,
          onHeaderCell: () => ({ style: { ...(isCurrent ? { backgroundColor: '#fafcff' } : {}) } }),
          children: childrenCols,
        };
      });

      // 当月
      const currentMonth = ['202610'];
      // 当月的一定不在右侧固定列里面
      const currentMonthGroups =
        currentMonthKey && currentMonthKey.length > 0
          ? currentMonth.map((m) => {
              const isCurrent = m === CURRENT_MONTH;
              const childrenCols = monthlyVisibleCols
                .filter((col) => currentMonthKey?.includes(col.key))
                .map((col) => {
                  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;

                  const isRightFixed = !!rightFixedKey?.includes(col.key);

                  return {
                    title: col.title,
                    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,
                  };
                });
              return {
                title: isCurrent ? (
                  <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>
                ) : (
                  monthLabel(m)
                ),
                key: `group_${m}`,
                onHeaderCell: () => ({ style: { ...(isCurrent ? { backgroundColor: '#fafcff' } : {}) } }),
                children: childrenCols,
              };
            })
          : [];

      const operationColumn = {
        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,
      };
      console.log('monthGroups', monthGroups);
      // 过滤出右侧的列
      const rightItemsCols =
        rightFixedKey && rightFixedKey.length > 0
          ? monthGroups.map((item) => {
              return {
                ...item,
                fixed: 'right',
                children: item.children.filter((child) => child.fixed === 'right'),
              };
            })
          : [];
      const monthlyItemsColsWithoutRight = monthGroups.map((item) => {
        return {
          ...item,
          children: item.children.filter((child) => !child.fixed),
        };
      });
      console.log('rightItemsCols', rightItemsCols);
      // 左固定 → 通常静的 → 当月→ 月グループ → 操作列(右固定)の順で連続させる
      return [
        ...leftStaticCols,
        ...normalStaticCols,
        ...currentMonthGroups,
        ...monthlyItemsColsWithoutRight,
        ...rightItemsCols,
        operationColumn,
      ] as TableColumnsType<ProposalData>;
    }

    // ============ 項目軸表示:行=要員、月次列は月ごとにサブ列展開 ============
    // 【列順を期間軸と統一】静的項目を先、月次項目を後に並べる。
    // 固定列は静的項目のみ。右固定の静的列は右端(操作列の前)へ寄せて連続させる。
    const leftItemCols: TableColumnsType<ProposalData> = [];
    const normalStaticItemCols: TableColumnsType<ProposalData> = [];
    const monthlyItemCols: TableColumnsType<ProposalData> = [];

    visibleCols.forEach((col) => {
      if (currentMonthKey?.includes(col.key)) {
        months = [CURRENT_MONTH];
      } else {
        months = effectiveMonths;
      }
      // --- 月次項目:選択された月ごとにサブ列を展開(固定しない・通常列扱い) ---
      if (col.monthly) {
        const headerWidth = calcTextWidth(col.title);
        monthlyItemCols.push({
          title: col.title,
          key: col.key,
          fixed: rightFixedKey?.includes(col.key) ? 'right' : undefined,
          children: months.map((m) => {
            const monthKey = `${col.key}__${m}`;
            const isCurrent = m === CURRENT_MONTH;
            const isNumeric = col.numeric;
            const dataWidth = dataSource.reduce(
              (max, row) => Math.max(max, calcTextWidth(String(row.monthly?.[monthKey] ?? ''))),
              0
            );
            const colWidth = Math.max(headerWidth, calcTextWidth(monthLabel(m)) + (isCurrent ? 44 : 0), dataWidth) + 40;
            return {
              title: isCurrent ? (
                <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>
              ) : (
                monthLabel(m)
              ),
              key: monthKey,
              dataIndex: ['monthly', monthKey],
              width: colWidth,
              fixed: rightFixedKey?.includes(col.key) ? 'right' : 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] ?? '',
              // 全ページ共通:localeCompareJa(数字感知)で統一
              sorter: hasData
                ? (a: ProposalData, b: ProposalData) =>
                    localeCompareJa(String(a.monthly?.[monthKey] ?? ''), String(b.monthly?.[monthKey] ?? ''))
                : false,
            };
          }),
        });
        return;
      }

      // --- 静的項目:左固定 / 通常 に振り分け ---
      // if (leftIndex >= 0 && idx <= leftIndex) {
      //   leftItemCols.push(buildStaticCol(col, 'left'));
      // } else {
      //   normalStaticItemCols.push(buildStaticCol(col, undefined));
      // }
      if (leftFixedKey?.includes(col.key)) {
        leftItemCols.push(buildStaticCol(col, 'left'));
      } else {
        normalStaticItemCols.push(buildStaticCol(col, undefined));
      }
    });

    // 操作列(右固定):共通の useOperationColumn を使用
    const operationColumn = {
      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,
    };

    console.log('鱼分monthlyItemCols', monthlyItemCols);
    // 过滤出当月的列
    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') ?? [];

    console.log('当月列', currentMonthCols);
    console.log('当月列', totalMonthCols);

    // 期間軸と列順を統一:左固定 → 通常静的 → 当月→ 月次項目 → 右固定 →操作列(右固定)
    return [
      ...leftItemCols,
      ...normalStaticItemCols,
      ...currentMonthCols,
      ...totalMonthCols,
      ...rightItemsCols,
      operationColumn,
    ];
  }, [
    visibleColumnKeys,
    leftFixedKey,
    rightFixedKey,
    currentMonthKey,
    dataSource,
    effectiveMonths,
    layout,
    renderOperation,
  ]);
posted @ 2026-10-15 09:23  小兔子09  阅读(5)  评论(0)    收藏  举报