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[]

浙公网安备 33010602011771号