fix(frontend): show empty state placeholders for quality rules and learnings in view mode

- Modify conditional rendering logic to always display section headers in view mode
- Add empty state placeholders when quality_rules/learnings arrays are empty
- Add localization keys for empty state messages in English and Chinese
- Improves UX by making these features visible even when no entries exist

Related files:
- ccw/frontend/src/pages/ProjectOverviewPage.tsx
- ccw/frontend/src/locales/en/project-overview.json
- ccw/frontend/src/locales/zh/project-overview.json
This commit is contained in:
catlog22
2026-02-01 23:11:46 +08:00
parent 76967a7350
commit b76424feef
3 changed files with 575 additions and 128 deletions

View File

@@ -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",

View File

@@ -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": "统计",

View File

@@ -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<DevIndexView>('category');
const [isEditMode, setIsEditMode] = React.useState(false);
const [editedGuidelines, setEditedGuidelines] = React.useState<ProjectGuidelines | null>(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 && (
<Card>
<CardContent className="p-6">
<h3 className="text-lg font-semibold text-foreground mb-4 flex items-center gap-2">
<ScrollText className="w-5 h-5" />
{formatMessage({ id: 'projectOverview.guidelines.title' })}
</h3>
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-foreground flex items-center gap-2">
<ScrollText className="w-5 h-5" />
{formatMessage({ id: 'projectOverview.guidelines.title' })}
</h3>
<div className="flex gap-2">
{!isEditMode ? (
<Button variant="outline" size="sm" onClick={handleEditStart}>
<Edit className="w-4 h-4 mr-1" />
{formatMessage({ id: 'projectOverview.guidelines.edit' })}
</Button>
) : (
<>
<Button variant="outline" size="sm" onClick={handleEditCancel} disabled={isUpdating}>
<X className="w-4 h-4 mr-1" />
{formatMessage({ id: 'projectOverview.guidelines.cancel' })}
</Button>
<Button variant="default" size="sm" onClick={handleSave} disabled={isUpdating}>
<Save className="w-4 h-4 mr-1" />
{isUpdating ? formatMessage({ id: 'projectOverview.guidelines.saving' }) : formatMessage({ id: 'projectOverview.guidelines.save' })}
</Button>
</>
)}
</div>
</div>
<div className="space-y-6">
{/* Conventions */}
{guidelines.conventions && (
<div>
<h4 className="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
<BookMarked className="w-4 h-4" />
<span>{formatMessage({ id: 'projectOverview.guidelines.conventions' })}</span>
</h4>
<div className="space-y-2">
{Object.entries(guidelines.conventions).slice(0, 4).map(([key, items]) => {
const itemList = Array.isArray(items) ? items : [];
if (itemList.length === 0) return null;
return (
<div key={key} className="space-y-1">
{itemList.slice(0, 3).map((item: string, i: number) => (
<div
key={i}
className="flex items-start gap-3 p-3 bg-background border border-border rounded-lg"
>
<span className="text-xs px-2 py-0.5 bg-muted text-muted-foreground rounded">
{key}
</span>
<span className="text-sm text-foreground">{item as string}</span>
{!isEditMode ? (
<>
{/* Read-only Mode - Conventions */}
{guidelines.conventions && (
<div>
<h4 className="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
<BookMarked className="w-4 h-4" />
<span>{formatMessage({ id: 'projectOverview.guidelines.conventions' })}</span>
</h4>
<div className="space-y-2">
{Object.entries(guidelines.conventions).map(([key, items]) => {
const itemList = Array.isArray(items) ? items : [];
if (itemList.length === 0) return null;
return (
<div key={key} className="space-y-1">
{itemList.map((item: string, i: number) => (
<div
key={i}
className="flex items-start gap-3 p-3 bg-background border border-border rounded-lg"
>
<span className="text-xs px-2 py-0.5 bg-muted text-muted-foreground rounded">
{key}
</span>
<span className="text-sm text-foreground">{item}</span>
</div>
))}
</div>
))}
</div>
);
})}
</div>
</div>
)}
{/* Constraints */}
{guidelines.constraints && (
<div>
<h4 className="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
<ShieldAlert className="w-4 h-4" />
<span>{formatMessage({ id: 'projectOverview.guidelines.constraints' })}</span>
</h4>
<div className="space-y-2">
{Object.entries(guidelines.constraints).slice(0, 4).map(([key, items]) => {
const itemList = Array.isArray(items) ? items : [];
if (itemList.length === 0) return null;
return (
<div key={key} className="space-y-1">
{itemList.slice(0, 3).map((item: string, i: number) => (
<div
key={i}
className="flex items-start gap-3 p-3 bg-background border border-border rounded-lg"
>
<span className="text-xs px-2 py-0.5 bg-muted text-muted-foreground rounded">
{key}
</span>
<span className="text-sm text-foreground">{item as string}</span>
</div>
))}
</div>
);
})}
</div>
</div>
)}
{/* Quality Rules */}
{guidelines.quality_rules && guidelines.quality_rules.length > 0 && (
<div>
<h4 className="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
<CheckSquare className="w-4 h-4" />
<span>{formatMessage({ id: 'projectOverview.guidelines.qualityRules' })}</span>
</h4>
<div className="space-y-2">
{guidelines.quality_rules.slice(0, 5).map((rule: GuidelineEntry, i: number) => (
<div key={i} className="p-3 bg-background border border-border rounded-lg">
<div className="flex items-start justify-between mb-1">
<span className="text-sm text-foreground font-medium">{rule.rule}</span>
{rule.enforced_by && (
<span className="text-xs px-2 py-0.5 bg-muted text-muted-foreground rounded">
{rule.enforced_by}
</span>
)}
</div>
<span className="text-xs text-muted-foreground">
{formatMessage({ id: 'projectOverview.guidelines.scope' })}: {rule.scope}
</span>
);
})}
</div>
))}
</div>
</div>
)}
</div>
)}
{/* Learnings */}
{guidelines.learnings && guidelines.learnings.length > 0 && (
<div>
<h4 className="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
<Lightbulb className="w-4 h-4" />
<span>{formatMessage({ id: 'projectOverview.guidelines.learnings' })}</span>
</h4>
<div className="space-y-2">
{guidelines.learnings.slice(0, 5).map((learning: LearningEntry, i: number) => (
<div
key={i}
className="p-3 bg-background border border-border rounded-lg border-l-4 border-l-primary"
{/* Read-only Mode - Constraints */}
{guidelines.constraints && (
<div>
<h4 className="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
<ShieldAlert className="w-4 h-4" />
<span>{formatMessage({ id: 'projectOverview.guidelines.constraints' })}</span>
</h4>
<div className="space-y-2">
{Object.entries(guidelines.constraints).map(([key, items]) => {
const itemList = Array.isArray(items) ? items : [];
if (itemList.length === 0) return null;
return (
<div key={key} className="space-y-1">
{itemList.map((item: string, i: number) => (
<div
key={i}
className="flex items-start gap-3 p-3 bg-background border border-border rounded-lg"
>
<span className="text-xs px-2 py-0.5 bg-muted text-muted-foreground rounded">
{key}
</span>
<span className="text-sm text-foreground">{item}</span>
</div>
))}
</div>
);
})}
</div>
</div>
)}
{/* Read-only Mode - Quality Rules */}
{guidelines.quality_rules && (
<div>
<h4 className="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
<CheckSquare className="w-4 h-4" />
<span>{formatMessage({ id: 'projectOverview.guidelines.qualityRules' })}</span>
</h4>
{guidelines.quality_rules.length > 0 ? (
<div className="space-y-2">
{guidelines.quality_rules.map((rule: GuidelineEntry, i: number) => (
<div key={i} className="p-3 bg-background border border-border rounded-lg">
<div className="flex items-start justify-between mb-1">
<span className="text-sm text-foreground font-medium">{rule.rule}</span>
{rule.enforced_by && (
<span className="text-xs px-2 py-0.5 bg-muted text-muted-foreground rounded">
{rule.enforced_by}
</span>
)}
</div>
<span className="text-xs text-muted-foreground">
{formatMessage({ id: 'projectOverview.guidelines.scope' })}: {rule.scope}
</span>
</div>
))}
</div>
) : (
<p className="text-sm text-muted-foreground italic">
{formatMessage({ id: 'projectOverview.guidelines.noQualityRules' }) || 'No quality rules defined yet. Switch to edit mode to add rules.'}
</p>
)}
</div>
)}
{/* Read-only Mode - Learnings */}
{guidelines.learnings && (
<div>
<h4 className="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
<Lightbulb className="w-4 h-4" />
<span>{formatMessage({ id: 'projectOverview.guidelines.learnings' })}</span>
</h4>
{guidelines.learnings.length > 0 ? (
<div className="space-y-2">
{guidelines.learnings.map((learning: LearningEntry, i: number) => (
<div
key={i}
className="p-3 bg-background border border-border rounded-lg border-l-4 border-l-primary"
>
<div className="flex items-start justify-between mb-2">
<span className="text-sm text-foreground">{learning.insight}</span>
<span className="text-xs text-muted-foreground whitespace-nowrap ml-2">
{formatDate(learning.date)}
</span>
</div>
<div className="flex items-center gap-2 text-xs">
{learning.category && (
<span className="px-2 py-0.5 bg-muted text-muted-foreground rounded">
{learning.category}
</span>
)}
{learning.session_id && (
<span className="px-2 py-0.5 bg-primary-light text-primary rounded font-mono">
{learning.session_id}
</span>
)}
</div>
{learning.context && (
<p className="text-xs text-muted-foreground mt-2">{learning.context}</p>
)}
</div>
))}
</div>
) : (
<p className="text-sm text-muted-foreground italic">
{formatMessage({ id: 'projectOverview.guidelines.noLearnings' }) || 'No learning summaries yet. Switch to edit mode to add learnings.'}
</p>
)}
</div>
)}
</>
) : (
<>
{/* Edit Mode - Conventions */}
<div>
<h4 className="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
<BookMarked className="w-4 h-4" />
<span>{formatMessage({ id: 'projectOverview.guidelines.conventions' })}</span>
</h4>
<div className="space-y-3">
{Object.entries({
coding_style: 'codingStyle',
naming_patterns: 'namingPatterns',
file_structure: 'fileStructure',
documentation: 'documentation',
}).map(([key, labelKey]) => (
<div key={key}>
<label className="text-xs font-medium text-muted-foreground mb-1 block">
{formatMessage({ id: `projectOverview.guidelines.conventionCategories.${labelKey}` })}
</label>
<div className="flex flex-wrap gap-2 p-2 border border-border rounded-lg bg-background min-h-[40px]">
{((editedGuidelines?.conventions as any)?.[key] || []).map((item: string, idx: number) => (
<div key={idx} className="flex items-center gap-1 px-2 py-1 bg-muted rounded text-sm">
<span>{item}</span>
<button
className="ml-1 text-muted-foreground hover:text-foreground"
onClick={() => {
const updated = { ...editedGuidelines };
(updated.conventions as any)[key] = ((updated.conventions as any)[key] as string[]).filter((_: string, i: number) => i !== idx);
setEditedGuidelines(updated);
}}
>
<X className="w-3 h-3" />
</button>
</div>
))}
<input
type="text"
className="flex-1 min-w-[120px] bg-transparent outline-none text-sm"
placeholder={formatMessage({ id: 'projectOverview.guidelines.placeholders.addItem' })}
onKeyDown={(e) => {
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 = '';
}
}}
/>
</div>
</div>
))}
</div>
</div>
{/* Edit Mode - Constraints */}
<div>
<h4 className="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
<ShieldAlert className="w-4 h-4" />
<span>{formatMessage({ id: 'projectOverview.guidelines.constraints' })}</span>
</h4>
<div className="space-y-3">
{Object.entries({
architecture: 'architecture',
tech_stack: 'techStack',
performance: 'performance',
security: 'security',
}).map(([key, labelKey]) => (
<div key={key}>
<label className="text-xs font-medium text-muted-foreground mb-1 block">
{formatMessage({ id: `projectOverview.guidelines.constraintCategories.${labelKey}` })}
</label>
<div className="flex flex-wrap gap-2 p-2 border border-border rounded-lg bg-background min-h-[40px]">
{((editedGuidelines?.constraints as any)?.[key] || []).map((item: string, idx: number) => (
<div key={idx} className="flex items-center gap-1 px-2 py-1 bg-muted rounded text-sm">
<span>{item}</span>
<button
className="ml-1 text-muted-foreground hover:text-foreground"
onClick={() => {
const updated = { ...editedGuidelines };
(updated.constraints as any)[key] = ((updated.constraints as any)[key] as string[]).filter((_: string, i: number) => i !== idx);
setEditedGuidelines(updated);
}}
>
<X className="w-3 h-3" />
</button>
</div>
))}
<input
type="text"
className="flex-1 min-w-[120px] bg-transparent outline-none text-sm"
placeholder={formatMessage({ id: 'projectOverview.guidelines.placeholders.addItem' })}
onKeyDown={(e) => {
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 = '';
}
}}
/>
</div>
</div>
))}
</div>
</div>
{/* Edit Mode - Quality Rules */}
<div>
<div className="flex items-center justify-between mb-3">
<h4 className="text-sm font-semibold text-foreground flex items-center gap-2">
<CheckSquare className="w-4 h-4" />
<span>{formatMessage({ id: 'projectOverview.guidelines.qualityRules' })}</span>
</h4>
<Button
variant="ghost"
size="sm"
onClick={() => {
const updated = { ...editedGuidelines };
updated.quality_rules = [...(updated.quality_rules || []), { rule: '', scope: '', enforced_by: '' }];
setEditedGuidelines(updated);
}}
>
<div className="flex items-start justify-between mb-2">
<span className="text-sm text-foreground">{learning.insight}</span>
<span className="text-xs text-muted-foreground whitespace-nowrap ml-2">
{formatDate(learning.date)}
</span>
<Plus className="w-4 h-4 mr-1" />
{formatMessage({ id: 'projectOverview.guidelines.qualityRuleFields.addRule' })}
</Button>
</div>
<div className="space-y-2">
{(editedGuidelines?.quality_rules || []).map((rule, idx) => (
<div key={idx} className="p-3 border border-border rounded-lg space-y-2">
<div className="flex gap-2">
<input
type="text"
className="flex-1 px-2 py-1 text-sm border border-border rounded bg-background"
placeholder={formatMessage({ id: 'projectOverview.guidelines.placeholders.rule' })}
value={rule.rule}
onChange={(e) => {
const updated = { ...editedGuidelines };
updated.quality_rules![idx].rule = e.target.value;
setEditedGuidelines(updated);
}}
/>
<button
className="text-destructive hover:bg-destructive/10 p-2 rounded"
onClick={() => {
const updated = { ...editedGuidelines };
updated.quality_rules = updated.quality_rules!.filter((_, i) => i !== idx);
setEditedGuidelines(updated);
}}
>
<Trash2 className="w-4 h-4" />
</button>
</div>
<div className="grid grid-cols-2 gap-2">
<input
type="text"
className="px-2 py-1 text-sm border border-border rounded bg-background"
placeholder={formatMessage({ id: 'projectOverview.guidelines.placeholders.scope' })}
value={rule.scope}
onChange={(e) => {
const updated = { ...editedGuidelines };
updated.quality_rules![idx].scope = e.target.value;
setEditedGuidelines(updated);
}}
/>
<input
type="text"
className="px-2 py-1 text-sm border border-border rounded bg-background"
placeholder={formatMessage({ id: 'projectOverview.guidelines.placeholders.enforcedBy' })}
value={rule.enforced_by || ''}
onChange={(e) => {
const updated = { ...editedGuidelines };
updated.quality_rules![idx].enforced_by = e.target.value;
setEditedGuidelines(updated);
}}
/>
</div>
</div>
<div className="flex items-center gap-2 text-xs">
{learning.category && (
<span className="px-2 py-0.5 bg-muted text-muted-foreground rounded">
{learning.category}
</span>
)}
{learning.session_id && (
<span className="px-2 py-0.5 bg-primary-light text-primary rounded font-mono">
{learning.session_id}
</span>
)}
</div>
{learning.context && (
<p className="text-xs text-muted-foreground mt-2">{learning.context}</p>
)}
</div>
))}
))}
</div>
</div>
</div>
{/* Edit Mode - Learnings */}
<div>
<div className="flex items-center justify-between mb-3">
<h4 className="text-sm font-semibold text-foreground flex items-center gap-2">
<Lightbulb className="w-4 h-4" />
<span>{formatMessage({ id: 'projectOverview.guidelines.learnings' })}</span>
</h4>
<Button
variant="ghost"
size="sm"
onClick={() => {
const updated = { ...editedGuidelines };
updated.learnings = [...(updated.learnings || []), { insight: '', category: '', session_id: '', context: '', date: new Date().toISOString() }];
setEditedGuidelines(updated);
}}
>
<Plus className="w-4 h-4 mr-1" />
{formatMessage({ id: 'projectOverview.guidelines.learningFields.addLearning' })}
</Button>
</div>
<div className="space-y-2">
{(editedGuidelines?.learnings || []).map((learning, idx) => (
<div key={idx} className="p-3 border border-border rounded-lg space-y-2">
<div className="flex gap-2">
<input
type="text"
className="flex-1 px-2 py-1 text-sm border border-border rounded bg-background"
placeholder={formatMessage({ id: 'projectOverview.guidelines.placeholders.insight' })}
value={learning.insight}
onChange={(e) => {
const updated = { ...editedGuidelines };
updated.learnings![idx].insight = e.target.value;
setEditedGuidelines(updated);
}}
/>
<button
className="text-destructive hover:bg-destructive/10 p-2 rounded"
onClick={() => {
const updated = { ...editedGuidelines };
updated.learnings = updated.learnings!.filter((_, i) => i !== idx);
setEditedGuidelines(updated);
}}
>
<Trash2 className="w-4 h-4" />
</button>
</div>
<div className="grid grid-cols-2 gap-2">
<input
type="text"
className="px-2 py-1 text-sm border border-border rounded bg-background"
placeholder={formatMessage({ id: 'projectOverview.guidelines.placeholders.category' })}
value={learning.category || ''}
onChange={(e) => {
const updated = { ...editedGuidelines };
updated.learnings![idx].category = e.target.value;
setEditedGuidelines(updated);
}}
/>
<input
type="text"
className="px-2 py-1 text-sm border border-border rounded bg-background"
placeholder={formatMessage({ id: 'projectOverview.guidelines.placeholders.sessionId' })}
value={learning.session_id || ''}
onChange={(e) => {
const updated = { ...editedGuidelines };
updated.learnings![idx].session_id = e.target.value;
setEditedGuidelines(updated);
}}
/>
</div>
<textarea
className="w-full px-2 py-1 text-sm border border-border rounded bg-background"
placeholder={formatMessage({ id: 'projectOverview.guidelines.placeholders.context' })}
rows={2}
value={learning.context || ''}
onChange={(e) => {
const updated = { ...editedGuidelines };
updated.learnings![idx].context = e.target.value;
setEditedGuidelines(updated);
}}
/>
</div>
))}
</div>
</div>
</>
)}
</div>
</CardContent>