import { Component, EventEmitter, Input, Output, OnInit, OnChanges, SimpleChanges } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { FmodeObject } from 'fmode-ng/parse'; import { ProjectIssueService, ProjectIssue, IssueStatus, IssuePriority, IssueType, IssueCounts } from '../../services/project-issue.service'; @Component({ selector: 'app-project-issues-modal', standalone: true, imports: [CommonModule, FormsModule], templateUrl: './project-issues-modal.component.html', styleUrls: ['./project-issues-modal.component.scss'] }) export class ProjectIssuesModalComponent implements OnInit, OnChanges { @Input() project: FmodeObject | null = null; @Input() currentUser: FmodeObject | null = null; @Input() isVisible: boolean = false; @Output() close = new EventEmitter(); issues: ProjectIssue[] = []; counts: IssueCounts = { total: 0, open: 0, in_progress: 0, resolved: 0, closed: 0 }; filterStatus: IssueStatus[] = []; searchText: string = ''; // 创建表单 creating: boolean = false; newTitle: string = ''; newDescription: string = ''; newPriority: IssuePriority = 'medium'; newType: IssueType = 'task'; newDueDate?: string; newTagsText: string = ''; loading: boolean = false; error: string | null = null; constructor(private issueService: ProjectIssueService) {} ngOnInit() { this.refresh(); } ngOnChanges(changes: SimpleChanges): void { if (changes['isVisible'] && this.isVisible) { this.refresh(); } if (changes['project'] && this.project) { this.refresh(); } } refresh() { if (!this.project?.id) return; try { this.loading = true; // 首次种子数据(仅内存,无副作用) this.issueService.seed(this.project.id); this.issues = this.issueService.listIssues(this.project.id, { status: this.filterStatus, text: this.searchText }); this.counts = this.issueService.getCounts(this.project.id); this.loading = false; } catch (err: any) { this.error = err.message || '加载问题列表失败'; this.loading = false; } } toggleStatusFilter(status: IssueStatus) { const idx = this.filterStatus.indexOf(status); if (idx >= 0) this.filterStatus.splice(idx, 1); else this.filterStatus.push(status); this.refresh(); } onSearchChange() { this.refresh(); } startCreate() { this.creating = true; this.newTitle = ''; this.newDescription = ''; this.newPriority = 'medium'; this.newType = 'task'; this.newDueDate = undefined; this.newTagsText = ''; } cancelCreate() { this.creating = false; } submitCreate() { if (!this.project?.id || !this.currentUser?.id) return; if (!this.newTitle.trim()) return; const tags = this.newTagsText .split(',') .map(t => t.trim()) .filter(Boolean); const due = this.newDueDate ? new Date(this.newDueDate) : undefined; this.issueService.createIssue(this.project.id, { title: this.newTitle.trim(), description: this.newDescription.trim(), priority: this.newPriority, type: this.newType, creatorId: this.currentUser.id, assigneeId: undefined, dueDate: due, tags }); this.creating = false; this.refresh(); } setStatus(issue: ProjectIssue, status: IssueStatus) { if (!this.project?.id) return; this.issueService.setStatus(this.project.id, issue.id, status); this.refresh(); } deleteIssue(issue: ProjectIssue) { if (!this.project?.id) return; this.issueService.deleteIssue(this.project.id, issue.id); this.refresh(); } addComment(issue: ProjectIssue, text: string) { if (!this.project?.id || !this.currentUser?.id) return; const content = text.trim(); if (!content) return; this.issueService.addComment(this.project.id, issue.id, this.currentUser.id, content); this.refresh(); } onClose() { this.close.emit(); } }