fix(data): 清理旧数据并保护服务端写入
This commit is contained in:
@@ -11,7 +11,7 @@ import { flattenProjects, flattenVersions } from '@/lib/derive';
|
||||
import { REQ_STATUS_LABEL, REQ_STATUS_COLOR, SOURCE_TYPE_LABEL } from '@/lib/requirement';
|
||||
import type { Requirement, RequirementStatus, SourceType } from '@/lib/requirement';
|
||||
import { deriveReqDevStatus, canEditRequirement, canCloseRequirement, REQ_DEV_STATUS_LABEL, REQ_DEV_STATUS_COLOR } from '@/lib/linkage-engine';
|
||||
import { buildRequirementScopeTree, filterRequirementsByScope, type RequirementProductScopeNode, type RequirementScopeSelection } from '@/lib/requirement-scope';
|
||||
import { buildRequirementScopeTree, filterRequirementScopeTreeByKeyword, filterRequirementsByScope, type RequirementProductScopeNode, type RequirementScopeSelection } from '@/lib/requirement-scope';
|
||||
import { sortRequirementsByCreatedAt, type RequirementDateSort } from '@/lib/requirement-sort';
|
||||
import { Pagination, usePagination } from '@/components/Pagination';
|
||||
import { RequirementModal } from '@/components/requirement/RequirementModal';
|
||||
@@ -173,6 +173,7 @@ function RequirementsPageContent() {
|
||||
const allVersions = useMemo(() => flattenVersions(overview), [overview]);
|
||||
|
||||
const [selectedScope, setSelectedScope] = useState<RequirementScopeSelection>({ type: 'all' });
|
||||
const [scopeSearch, setScopeSearch] = useState('');
|
||||
const [search, setSearch] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('all');
|
||||
const [priorityFilter, setPriorityFilter] = useState('all');
|
||||
@@ -202,6 +203,7 @@ function RequirementsPageContent() {
|
||||
}, [selectedScopeKey]);
|
||||
|
||||
const scopeTree = useMemo(() => buildRequirementScopeTree(overview, requirements), [overview, requirements]);
|
||||
const searchedScopeTree = useMemo(() => filterRequirementScopeTreeByKeyword(scopeTree, scopeSearch), [scopeTree, scopeSearch]);
|
||||
const scopedRequirements = useMemo(() => filterRequirementsByScope(requirements, selectedScope), [requirements, selectedScope]);
|
||||
const scopedVersions = useMemo(() => {
|
||||
if (selectedScope.type === 'product') return allVersions.filter((v) => v.productId === selectedScope.productId);
|
||||
@@ -303,6 +305,18 @@ function RequirementsPageContent() {
|
||||
{requirements.length}
|
||||
</span>
|
||||
</div>
|
||||
<div className="shrink-0 border-b border-[var(--line)] px-3 py-3">
|
||||
<div className="relative">
|
||||
<Search className="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-[var(--ink-muted)]" strokeWidth={2} />
|
||||
<input
|
||||
value={scopeSearch}
|
||||
onChange={(event) => setScopeSearch(event.target.value)}
|
||||
placeholder="搜索产品或项目"
|
||||
aria-label="搜索产品或项目"
|
||||
className="h-8 w-full rounded-lg border border-[var(--line)] bg-[var(--bg)] pl-8 pr-3 text-[12px] text-[var(--ink)] placeholder:text-[var(--ink-muted)] focus:border-[var(--accent)] focus:outline-none focus:ring-2 focus:ring-[var(--accent-ring)]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto p-2">
|
||||
<button
|
||||
onClick={() => setSelectedScope({ type: 'all' })}
|
||||
@@ -316,7 +330,7 @@ function RequirementsPageContent() {
|
||||
<span className="flex-1 text-left">全部需求</span>
|
||||
<ScopeCount count={requirements.length} />
|
||||
</button>
|
||||
{scopeTree.map((product) => (
|
||||
{searchedScopeTree.map((product) => (
|
||||
<RequirementProductNode
|
||||
key={product.id}
|
||||
product={product}
|
||||
@@ -324,6 +338,11 @@ function RequirementsPageContent() {
|
||||
onSelect={setSelectedScope}
|
||||
/>
|
||||
))}
|
||||
{searchedScopeTree.length === 0 && scopeSearch.trim() && (
|
||||
<div className="mt-3 rounded-lg border border-dashed border-[var(--line)] px-3 py-4 text-center text-[12px] text-[var(--ink-muted)]">
|
||||
没有匹配的产品或项目
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user