关键改动: - 抽取版本列表范围树和筛选规则 - 重构版本列表页的范围选择与优先级筛选 - 配置 Next dev 忽略测试编译输出,避免刷新干扰 Co-Authored-By: Codex GPT-5 <codex@openai.com>
87 lines
2.9 KiB
TypeScript
87 lines
2.9 KiB
TypeScript
import type { Priority, VersionWithContext } from './derive';
|
|
|
|
export type VersionListScope =
|
|
| { type: 'all' }
|
|
| { type: 'product'; productId: string }
|
|
| { type: 'project'; projectId: string };
|
|
|
|
export interface VersionProjectNode {
|
|
projectId: string;
|
|
projectName: string;
|
|
count: number;
|
|
}
|
|
|
|
export interface VersionProductNode {
|
|
productId: string;
|
|
productName: string;
|
|
count: number;
|
|
projects: VersionProjectNode[];
|
|
}
|
|
|
|
export interface VersionListFilter {
|
|
scope: VersionListScope;
|
|
keyword: string;
|
|
priority: Priority | 'all';
|
|
}
|
|
|
|
function matchesKeyword(values: Array<string | undefined | null>, keyword: string): boolean {
|
|
const normalized = keyword.trim().toLowerCase();
|
|
if (!normalized) return true;
|
|
return values.some((value) => value?.toLowerCase().includes(normalized));
|
|
}
|
|
|
|
export function buildVersionScopeTree(versions: VersionWithContext[], keyword: string): VersionProductNode[] {
|
|
const normalized = keyword.trim().toLowerCase();
|
|
const productMap = new Map<string, {
|
|
productId: string;
|
|
productName: string;
|
|
projects: Map<string, VersionProjectNode>;
|
|
}>();
|
|
|
|
for (const version of versions) {
|
|
const productMatches = matchesKeyword([version.productName], normalized);
|
|
const projectMatches = matchesKeyword([version.projectName], normalized);
|
|
const versionMatches = matchesKeyword([version.name], normalized);
|
|
if (normalized && !productMatches && !projectMatches && !versionMatches) continue;
|
|
|
|
let product = productMap.get(version.productId);
|
|
if (!product) {
|
|
product = {
|
|
productId: version.productId,
|
|
productName: version.productName,
|
|
projects: new Map(),
|
|
};
|
|
productMap.set(version.productId, product);
|
|
}
|
|
|
|
const project = product.projects.get(version.projectId) ?? {
|
|
projectId: version.projectId,
|
|
projectName: version.projectName,
|
|
count: 0,
|
|
};
|
|
project.count += 1;
|
|
product.projects.set(version.projectId, project);
|
|
}
|
|
|
|
return Array.from(productMap.values()).map((product) => {
|
|
const projects = Array.from(product.projects.values()).sort((a, b) => a.projectName.localeCompare(b.projectName, 'zh-Hans-CN'));
|
|
return {
|
|
productId: product.productId,
|
|
productName: product.productName,
|
|
count: projects.reduce((sum, project) => sum + project.count, 0),
|
|
projects,
|
|
};
|
|
}).sort((a, b) => a.productName.localeCompare(b.productName, 'zh-Hans-CN'));
|
|
}
|
|
|
|
export function filterVersionsForList(versions: VersionWithContext[], filter: VersionListFilter): VersionWithContext[] {
|
|
const filtered = versions.filter((version) => {
|
|
if (filter.scope.type === 'product' && version.productId !== filter.scope.productId) return false;
|
|
if (filter.scope.type === 'project' && version.projectId !== filter.scope.projectId) return false;
|
|
if (filter.priority !== 'all' && (version.priority ?? 'P2') !== filter.priority) return false;
|
|
return matchesKeyword([version.name], filter.keyword);
|
|
});
|
|
|
|
return filtered;
|
|
}
|