diff --git a/ccw/frontend/src/locales/en/project-overview.json b/ccw/frontend/src/locales/en/project-overview.json index 4604900f..549500cf 100644 --- a/ccw/frontend/src/locales/en/project-overview.json +++ b/ccw/frontend/src/locales/en/project-overview.json @@ -45,7 +45,54 @@ "constraints": "Constraints", "qualityRules": "Quality Rules", "learnings": "Session Learnings", - "scope": "Scope" + "scope": "Scope", + "noQualityRules": "No quality rules defined yet. Switch to edit mode to add rules.", + "noLearnings": "No learning summaries yet. Switch to edit mode to add learnings.", + "edit": "Edit", + "save": "Save", + "cancel": "Cancel", + "saving": "Saving...", + "saveSuccess": "Guidelines saved successfully", + "saveError": "Failed to save guidelines", + "conventionCategories": { + "codingStyle": "Coding Style", + "namingPatterns": "Naming Patterns", + "fileStructure": "File Structure", + "documentation": "Documentation" + }, + "constraintCategories": { + "architecture": "Architecture", + "techStack": "Tech Stack", + "performance": "Performance", + "security": "Security" + }, + "qualityRuleFields": { + "rule": "Rule", + "scope": "Scope", + "enforcedBy": "Enforced By", + "addRule": "Add Quality Rule", + "removeRule": "Remove" + }, + "learningFields": { + "insight": "Insight", + "category": "Category", + "sessionId": "Session ID", + "context": "Context", + "date": "Date", + "addLearning": "Add Learning", + "removeLearning": "Remove" + }, + "placeholders": { + "addItem": "Press Enter to add item", + "rule": "Enter quality rule", + "scope": "Enter scope", + "enforcedBy": "Enter enforcement method", + "insight": "Enter insight", + "category": "Enter category", + "sessionId": "Enter session ID", + "context": "Enter context", + "selectDate": "Select date" + } }, "stats": { "title": "Statistics", diff --git a/ccw/frontend/src/locales/zh/project-overview.json b/ccw/frontend/src/locales/zh/project-overview.json index 90f85ade..6777dbad 100644 --- a/ccw/frontend/src/locales/zh/project-overview.json +++ b/ccw/frontend/src/locales/zh/project-overview.json @@ -45,7 +45,54 @@ "constraints": "约束", "qualityRules": "质量规则", "learnings": "学习总结", - "scope": "范围" + "scope": "范围", + "noQualityRules": "暂无质量规则。切换到编辑模式添加规则。", + "noLearnings": "暂无学习总结。切换到编辑模式添加总结。", + "edit": "编辑", + "save": "保存", + "cancel": "取消", + "saving": "正在保存...", + "saveSuccess": "规范已保存成功", + "saveError": "保存规范失败", + "conventionCategories": { + "codingStyle": "编码风格", + "namingPatterns": "命名规范", + "fileStructure": "文件结构", + "documentation": "文档规范" + }, + "constraintCategories": { + "architecture": "架构", + "techStack": "技术栈", + "performance": "性能", + "security": "安全性" + }, + "qualityRuleFields": { + "rule": "规则", + "scope": "范围", + "enforcedBy": "执行方式", + "addRule": "添加质量规则", + "removeRule": "删除" + }, + "learningFields": { + "insight": "见解", + "category": "分类", + "sessionId": "会话ID", + "context": "上下文", + "date": "日期", + "addLearning": "添加学习总结", + "removeLearning": "删除" + }, + "placeholders": { + "addItem": "按Enter添加条目", + "rule": "输入质量规则", + "scope": "输入范围", + "enforcedBy": "输入执行方式", + "insight": "输入见解", + "category": "输入分类", + "sessionId": "输入会话ID", + "context": "输入上下文", + "selectDate": "选择日期" + } }, "stats": { "title": "统计", diff --git a/ccw/frontend/src/pages/ProjectOverviewPage.tsx b/ccw/frontend/src/pages/ProjectOverviewPage.tsx index 4fa95093..efed628d 100644 --- a/ccw/frontend/src/pages/ProjectOverviewPage.tsx +++ b/ccw/frontend/src/pages/ProjectOverviewPage.tsx @@ -24,13 +24,20 @@ import { ShieldAlert, LayoutGrid, GitCommitHorizontal, + Edit, + Save, + X, + Plus, + Trash2, } from 'lucide-react'; -import { useProjectOverview } from '@/hooks/useProjectOverview'; +import { useProjectOverview, useUpdateGuidelines } from '@/hooks/useProjectOverview'; +import { toast } from 'sonner'; import type { KeyComponent, DevelopmentIndexEntry, GuidelineEntry, LearningEntry, + ProjectGuidelines, } from '@/lib/api'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; @@ -61,7 +68,10 @@ function formatDate(dateString: string | undefined): string { export function ProjectOverviewPage() { const { formatMessage } = useIntl(); const { projectOverview, isLoading, error, refetch } = useProjectOverview(); + const { updateGuidelines, isUpdating } = useUpdateGuidelines(); const [devIndexView, setDevIndexView] = React.useState('category'); + const [isEditMode, setIsEditMode] = React.useState(false); + const [editedGuidelines, setEditedGuidelines] = React.useState(null); // Helper function to format date function formatDate(dateString: string | undefined): string { @@ -131,6 +141,45 @@ export function ProjectOverviewPage() { // Calculate statistics const totalFeatures = devIndexCategories.reduce((sum, cat) => sum + devIndexTotals[cat.key], 0); + // Guidelines edit handlers + const handleEditStart = React.useCallback(() => { + const g = projectOverview?.guidelines; + setEditedGuidelines({ + conventions: { + coding_style: [...(g?.conventions?.coding_style || [])], + naming_patterns: [...(g?.conventions?.naming_patterns || [])], + file_structure: [...(g?.conventions?.file_structure || [])], + documentation: [...(g?.conventions?.documentation || [])], + }, + constraints: { + architecture: [...(g?.constraints?.architecture || [])], + tech_stack: [...(g?.constraints?.tech_stack || [])], + performance: [...(g?.constraints?.performance || [])], + security: [...(g?.constraints?.security || [])], + }, + quality_rules: (g?.quality_rules || []).map((r) => ({ ...r })), + learnings: (g?.learnings || []).map((l) => ({ ...l })), + }); + setIsEditMode(true); + }, [projectOverview?.guidelines]); + + const handleEditCancel = React.useCallback(() => { + setIsEditMode(false); + setEditedGuidelines(null); + }, []); + + const handleSave = React.useCallback(async () => { + if (!editedGuidelines) return; + try { + await updateGuidelines(editedGuidelines); + toast.success(formatMessage({ id: 'projectOverview.guidelines.saveSuccess' })); + setIsEditMode(false); + setEditedGuidelines(null); + } catch { + toast.error(formatMessage({ id: 'projectOverview.guidelines.saveError' })); + } + }, [editedGuidelines, updateGuidelines, formatMessage]); + // Render loading state if (isLoading) { return ( @@ -598,139 +647,443 @@ export function ProjectOverviewPage() { {guidelines && ( -

- - {formatMessage({ id: 'projectOverview.guidelines.title' })} -

+
+

+ + {formatMessage({ id: 'projectOverview.guidelines.title' })} +

+
+ {!isEditMode ? ( + + ) : ( + <> + + + + )} +
+
- {/* Conventions */} - {guidelines.conventions && ( -
-

- - {formatMessage({ id: 'projectOverview.guidelines.conventions' })} -

-
- {Object.entries(guidelines.conventions).slice(0, 4).map(([key, items]) => { - const itemList = Array.isArray(items) ? items : []; - if (itemList.length === 0) return null; - return ( -
- {itemList.slice(0, 3).map((item: string, i: number) => ( -
- - {key} - - {item as string} + {!isEditMode ? ( + <> + {/* Read-only Mode - Conventions */} + {guidelines.conventions && ( +
+

+ + {formatMessage({ id: 'projectOverview.guidelines.conventions' })} +

+
+ {Object.entries(guidelines.conventions).map(([key, items]) => { + const itemList = Array.isArray(items) ? items : []; + if (itemList.length === 0) return null; + return ( +
+ {itemList.map((item: string, i: number) => ( +
+ + {key} + + {item} +
+ ))}
- ))} -
- ); - })} -
-
- )} - - {/* Constraints */} - {guidelines.constraints && ( -
-

- - {formatMessage({ id: 'projectOverview.guidelines.constraints' })} -

-
- {Object.entries(guidelines.constraints).slice(0, 4).map(([key, items]) => { - const itemList = Array.isArray(items) ? items : []; - if (itemList.length === 0) return null; - return ( -
- {itemList.slice(0, 3).map((item: string, i: number) => ( -
- - {key} - - {item as string} -
- ))} -
- ); - })} -
-
- )} - - {/* Quality Rules */} - {guidelines.quality_rules && guidelines.quality_rules.length > 0 && ( -
-

- - {formatMessage({ id: 'projectOverview.guidelines.qualityRules' })} -

-
- {guidelines.quality_rules.slice(0, 5).map((rule: GuidelineEntry, i: number) => ( -
-
- {rule.rule} - {rule.enforced_by && ( - - {rule.enforced_by} - - )} -
- - {formatMessage({ id: 'projectOverview.guidelines.scope' })}: {rule.scope} - + ); + })}
- ))} -
-
- )} +
+ )} - {/* Learnings */} - {guidelines.learnings && guidelines.learnings.length > 0 && ( -
-

- - {formatMessage({ id: 'projectOverview.guidelines.learnings' })} -

-
- {guidelines.learnings.slice(0, 5).map((learning: LearningEntry, i: number) => ( -
+

+ + {formatMessage({ id: 'projectOverview.guidelines.constraints' })} +

+
+ {Object.entries(guidelines.constraints).map(([key, items]) => { + const itemList = Array.isArray(items) ? items : []; + if (itemList.length === 0) return null; + return ( +
+ {itemList.map((item: string, i: number) => ( +
+ + {key} + + {item} +
+ ))} +
+ ); + })} +
+
+ )} + + {/* Read-only Mode - Quality Rules */} + {guidelines.quality_rules && ( +
+

+ + {formatMessage({ id: 'projectOverview.guidelines.qualityRules' })} +

+ {guidelines.quality_rules.length > 0 ? ( +
+ {guidelines.quality_rules.map((rule: GuidelineEntry, i: number) => ( +
+
+ {rule.rule} + {rule.enforced_by && ( + + {rule.enforced_by} + + )} +
+ + {formatMessage({ id: 'projectOverview.guidelines.scope' })}: {rule.scope} + +
+ ))} +
+ ) : ( +

+ {formatMessage({ id: 'projectOverview.guidelines.noQualityRules' }) || 'No quality rules defined yet. Switch to edit mode to add rules.'} +

+ )} +
+ )} + + {/* Read-only Mode - Learnings */} + {guidelines.learnings && ( +
+

+ + {formatMessage({ id: 'projectOverview.guidelines.learnings' })} +

+ {guidelines.learnings.length > 0 ? ( +
+ {guidelines.learnings.map((learning: LearningEntry, i: number) => ( +
+
+ {learning.insight} + + {formatDate(learning.date)} + +
+
+ {learning.category && ( + + {learning.category} + + )} + {learning.session_id && ( + + {learning.session_id} + + )} +
+ {learning.context && ( +

{learning.context}

+ )} +
+ ))} +
+ ) : ( +

+ {formatMessage({ id: 'projectOverview.guidelines.noLearnings' }) || 'No learning summaries yet. Switch to edit mode to add learnings.'} +

+ )} +
+ )} + + ) : ( + <> + {/* Edit Mode - Conventions */} +
+

+ + {formatMessage({ id: 'projectOverview.guidelines.conventions' })} +

+
+ {Object.entries({ + coding_style: 'codingStyle', + naming_patterns: 'namingPatterns', + file_structure: 'fileStructure', + documentation: 'documentation', + }).map(([key, labelKey]) => ( +
+ +
+ {((editedGuidelines?.conventions as any)?.[key] || []).map((item: string, idx: number) => ( +
+ {item} + +
+ ))} + { + if (e.key === 'Enter' && e.currentTarget.value.trim()) { + const updated = { ...editedGuidelines }; + (updated.conventions as any)[key] = [...((updated.conventions as any)[key] || []), e.currentTarget.value.trim()]; + setEditedGuidelines(updated); + e.currentTarget.value = ''; + } + }} + /> +
+
+ ))} +
+
+ + {/* Edit Mode - Constraints */} +
+

+ + {formatMessage({ id: 'projectOverview.guidelines.constraints' })} +

+
+ {Object.entries({ + architecture: 'architecture', + tech_stack: 'techStack', + performance: 'performance', + security: 'security', + }).map(([key, labelKey]) => ( +
+ +
+ {((editedGuidelines?.constraints as any)?.[key] || []).map((item: string, idx: number) => ( +
+ {item} + +
+ ))} + { + if (e.key === 'Enter' && e.currentTarget.value.trim()) { + const updated = { ...editedGuidelines }; + (updated.constraints as any)[key] = [...((updated.constraints as any)[key] || []), e.currentTarget.value.trim()]; + setEditedGuidelines(updated); + e.currentTarget.value = ''; + } + }} + /> +
+
+ ))} +
+
+ + {/* Edit Mode - Quality Rules */} +
+
+

+ + {formatMessage({ id: 'projectOverview.guidelines.qualityRules' })} +

+ +
+
+ {(editedGuidelines?.quality_rules || []).map((rule, idx) => ( +
+
+ { + const updated = { ...editedGuidelines }; + updated.quality_rules![idx].rule = e.target.value; + setEditedGuidelines(updated); + }} + /> + +
+
+ { + const updated = { ...editedGuidelines }; + updated.quality_rules![idx].scope = e.target.value; + setEditedGuidelines(updated); + }} + /> + { + const updated = { ...editedGuidelines }; + updated.quality_rules![idx].enforced_by = e.target.value; + setEditedGuidelines(updated); + }} + /> +
-
- {learning.category && ( - - {learning.category} - - )} - {learning.session_id && ( - - {learning.session_id} - - )} -
- {learning.context && ( -

{learning.context}

- )} -
- ))} + ))} +
-
+ + {/* Edit Mode - Learnings */} +
+
+

+ + {formatMessage({ id: 'projectOverview.guidelines.learnings' })} +

+ +
+
+ {(editedGuidelines?.learnings || []).map((learning, idx) => ( +
+
+ { + const updated = { ...editedGuidelines }; + updated.learnings![idx].insight = e.target.value; + setEditedGuidelines(updated); + }} + /> + +
+
+ { + const updated = { ...editedGuidelines }; + updated.learnings![idx].category = e.target.value; + setEditedGuidelines(updated); + }} + /> + { + const updated = { ...editedGuidelines }; + updated.learnings![idx].session_id = e.target.value; + setEditedGuidelines(updated); + }} + /> +
+