ideaco 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/.dockerignore +33 -0
  2. package/.nvmrc +1 -0
  3. package/ARCHITECTURE.md +394 -0
  4. package/Dockerfile +50 -0
  5. package/LICENSE +29 -0
  6. package/README.md +206 -0
  7. package/bin/i18n.js +46 -0
  8. package/bin/ideaco.js +494 -0
  9. package/deploy.sh +15 -0
  10. package/docker-compose.yml +30 -0
  11. package/electron/main.cjs +986 -0
  12. package/electron/preload.cjs +14 -0
  13. package/electron/web-backends.cjs +854 -0
  14. package/jsconfig.json +8 -0
  15. package/next.config.mjs +34 -0
  16. package/package.json +134 -0
  17. package/postcss.config.mjs +6 -0
  18. package/public/demo/dashboard.png +0 -0
  19. package/public/demo/employee.png +0 -0
  20. package/public/demo/messages.png +0 -0
  21. package/public/demo/office.png +0 -0
  22. package/public/demo/requirement.png +0 -0
  23. package/public/logo.jpeg +0 -0
  24. package/public/logo.png +0 -0
  25. package/scripts/prepare-electron.js +67 -0
  26. package/scripts/release.js +76 -0
  27. package/src/app/api/agents/[agentId]/chat/route.js +70 -0
  28. package/src/app/api/agents/[agentId]/conversations/route.js +35 -0
  29. package/src/app/api/agents/[agentId]/route.js +106 -0
  30. package/src/app/api/avatar/route.js +104 -0
  31. package/src/app/api/browse-dir/route.js +44 -0
  32. package/src/app/api/chat/route.js +265 -0
  33. package/src/app/api/company/factory-reset/route.js +43 -0
  34. package/src/app/api/company/route.js +82 -0
  35. package/src/app/api/departments/[deptId]/agents/[agentId]/dismiss/route.js +19 -0
  36. package/src/app/api/departments/route.js +92 -0
  37. package/src/app/api/group-chat-loop/events/route.js +70 -0
  38. package/src/app/api/group-chat-loop/route.js +94 -0
  39. package/src/app/api/mailbox/route.js +100 -0
  40. package/src/app/api/messages/route.js +14 -0
  41. package/src/app/api/providers/[id]/configure/route.js +21 -0
  42. package/src/app/api/providers/[id]/refresh-cookie/route.js +38 -0
  43. package/src/app/api/providers/[id]/test-cookie/route.js +28 -0
  44. package/src/app/api/providers/route.js +11 -0
  45. package/src/app/api/requirements/route.js +242 -0
  46. package/src/app/api/secretary/route.js +65 -0
  47. package/src/app/api/system/cli-backends/route.js +91 -0
  48. package/src/app/api/system/cron/route.js +110 -0
  49. package/src/app/api/system/knowledge/route.js +104 -0
  50. package/src/app/api/system/plugins/route.js +40 -0
  51. package/src/app/api/system/skills/route.js +46 -0
  52. package/src/app/api/system/status/route.js +46 -0
  53. package/src/app/api/talent-market/[profileId]/recall/route.js +22 -0
  54. package/src/app/api/talent-market/[profileId]/route.js +17 -0
  55. package/src/app/api/talent-market/route.js +26 -0
  56. package/src/app/api/teams/route.js +773 -0
  57. package/src/app/api/ws-files/[departmentId]/file/route.js +27 -0
  58. package/src/app/api/ws-files/[departmentId]/files/route.js +22 -0
  59. package/src/app/globals.css +130 -0
  60. package/src/app/layout.jsx +40 -0
  61. package/src/app/page.jsx +97 -0
  62. package/src/components/AgentChatModal.jsx +164 -0
  63. package/src/components/AgentDetailModal.jsx +425 -0
  64. package/src/components/AgentSpyModal.jsx +481 -0
  65. package/src/components/AvatarGrid.jsx +29 -0
  66. package/src/components/BossProfileModal.jsx +162 -0
  67. package/src/components/CachedAvatar.jsx +77 -0
  68. package/src/components/ChatPanel.jsx +219 -0
  69. package/src/components/ChatShared.jsx +255 -0
  70. package/src/components/DepartmentDetail.jsx +842 -0
  71. package/src/components/DepartmentView.jsx +367 -0
  72. package/src/components/FileReference.jsx +260 -0
  73. package/src/components/FilesView.jsx +465 -0
  74. package/src/components/GroupChatView.jsx +799 -0
  75. package/src/components/Mailbox.jsx +926 -0
  76. package/src/components/MessagesView.jsx +112 -0
  77. package/src/components/OnboardingGuide.jsx +209 -0
  78. package/src/components/OrgTree.jsx +151 -0
  79. package/src/components/Overview.jsx +391 -0
  80. package/src/components/PixelOffice.jsx +2281 -0
  81. package/src/components/ProviderGrid.jsx +551 -0
  82. package/src/components/ProvidersBoard.jsx +16 -0
  83. package/src/components/RequirementDetail.jsx +1279 -0
  84. package/src/components/RequirementsBoard.jsx +187 -0
  85. package/src/components/SecretarySettings.jsx +295 -0
  86. package/src/components/SetupWizard.jsx +388 -0
  87. package/src/components/Sidebar.jsx +169 -0
  88. package/src/components/SystemMonitor.jsx +808 -0
  89. package/src/components/TalentMarket.jsx +183 -0
  90. package/src/components/TeamDetail.jsx +697 -0
  91. package/src/core/agent/base-agent.js +104 -0
  92. package/src/core/agent/chat-store.js +602 -0
  93. package/src/core/agent/cli-agent/backends/claude-code/README.md +52 -0
  94. package/src/core/agent/cli-agent/backends/claude-code/config.js +27 -0
  95. package/src/core/agent/cli-agent/backends/codebuddy/README.md +236 -0
  96. package/src/core/agent/cli-agent/backends/codebuddy/config.js +27 -0
  97. package/src/core/agent/cli-agent/backends/codex/README.md +51 -0
  98. package/src/core/agent/cli-agent/backends/codex/config.js +27 -0
  99. package/src/core/agent/cli-agent/backends/index.js +27 -0
  100. package/src/core/agent/cli-agent/backends/registry.js +580 -0
  101. package/src/core/agent/cli-agent/index.js +154 -0
  102. package/src/core/agent/index.js +60 -0
  103. package/src/core/agent/llm-agent/client.js +320 -0
  104. package/src/core/agent/llm-agent/index.js +97 -0
  105. package/src/core/agent/message-bus.js +211 -0
  106. package/src/core/agent/session.js +608 -0
  107. package/src/core/agent/tools.js +596 -0
  108. package/src/core/agent/web-agent/backends/base-backend.js +180 -0
  109. package/src/core/agent/web-agent/backends/chatgpt/client.js +146 -0
  110. package/src/core/agent/web-agent/backends/chatgpt/config.js +148 -0
  111. package/src/core/agent/web-agent/backends/chatgpt/dom-scripts.js +303 -0
  112. package/src/core/agent/web-agent/backends/index.js +91 -0
  113. package/src/core/agent/web-agent/index.js +278 -0
  114. package/src/core/agent/web-agent/web-client.js +407 -0
  115. package/src/core/employee/base-employee.js +1088 -0
  116. package/src/core/employee/index.js +35 -0
  117. package/src/core/employee/knowledge.js +327 -0
  118. package/src/core/employee/lifecycle.js +990 -0
  119. package/src/core/employee/memory/index.js +642 -0
  120. package/src/core/employee/memory/store.js +143 -0
  121. package/src/core/employee/performance.js +224 -0
  122. package/src/core/employee/secretary.js +625 -0
  123. package/src/core/employee/skills.js +398 -0
  124. package/src/core/index.js +38 -0
  125. package/src/core/organization/company.js +2600 -0
  126. package/src/core/organization/department.js +737 -0
  127. package/src/core/organization/group-chat-loop.js +264 -0
  128. package/src/core/organization/index.js +8 -0
  129. package/src/core/organization/persistence.js +111 -0
  130. package/src/core/organization/team.js +267 -0
  131. package/src/core/organization/workforce/hr.js +377 -0
  132. package/src/core/organization/workforce/providers.js +468 -0
  133. package/src/core/organization/workforce/role-archetypes.js +805 -0
  134. package/src/core/organization/workforce/talent-market.js +205 -0
  135. package/src/core/prompts.js +532 -0
  136. package/src/core/requirement.js +1789 -0
  137. package/src/core/system/audit.js +483 -0
  138. package/src/core/system/cron.js +449 -0
  139. package/src/core/system/index.js +7 -0
  140. package/src/core/system/plugin.js +2183 -0
  141. package/src/core/utils/json-parse.js +188 -0
  142. package/src/core/workspace.js +239 -0
  143. package/src/lib/api-i18n.js +211 -0
  144. package/src/lib/avatar.js +268 -0
  145. package/src/lib/client-store.js +1025 -0
  146. package/src/lib/config-validator.js +483 -0
  147. package/src/lib/format-time.js +22 -0
  148. package/src/lib/hooks.js +414 -0
  149. package/src/lib/i18n.js +134 -0
  150. package/src/lib/paths.js +23 -0
  151. package/src/lib/store.js +72 -0
  152. package/src/locales/de.js +393 -0
  153. package/src/locales/en.js +1054 -0
  154. package/src/locales/es.js +393 -0
  155. package/src/locales/fr.js +393 -0
  156. package/src/locales/ja.js +501 -0
  157. package/src/locales/ko.js +513 -0
  158. package/src/locales/zh.js +828 -0
  159. package/tailwind.config.mjs +11 -0
@@ -0,0 +1,391 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import { useStore } from '@/lib/client-store';
5
+ import { useI18n } from '@/lib/i18n';
6
+ import SystemMonitor from './SystemMonitor';
7
+ import CachedAvatar from './CachedAvatar';
8
+
9
+ export default function Overview() {
10
+ const { company, planDepartment, confirmPlan, pendingPlan, setPendingPlan, loading, setActiveTab, fetchRequirements, navigateToRequirement, fetchTeams, navigateToTeam } = useStore();
11
+ const { t } = useI18n();
12
+ const [activeSubTab, setActiveSubTab] = useState('dashboard');
13
+ const [showCreate, setShowCreate] = useState(false);
14
+ const [deptName, setDeptName] = useState('');
15
+ const [deptMission, setDeptMission] = useState('');
16
+ const [requirements, setRequirements] = useState([]);
17
+ const [teams, setTeams] = useState([]);
18
+ const [activeReqId, setActiveReqId] = useState(null); // unused, kept for compat
19
+
20
+ if (!company) return null;
21
+
22
+ // Load requirements list and teams
23
+ useEffect(() => {
24
+ fetchRequirements().then(setRequirements);
25
+ fetchTeams().then(setTeams);
26
+ }, [company]);
27
+
28
+ // Step 1: Generate recruitment plan
29
+ const handlePlan = async () => {
30
+ if (!deptName || !deptMission) return;
31
+ try {
32
+ await planDepartment(deptName, deptMission);
33
+ } catch (e) { /* handled */ }
34
+ };
35
+
36
+ // Step 2: Confirm plan
37
+ const handleConfirm = async () => {
38
+ if (!pendingPlan?.planId) return;
39
+ try {
40
+ await confirmPlan(pendingPlan.planId);
41
+ setShowCreate(false);
42
+ setDeptName('');
43
+ setDeptMission('');
44
+ } catch (e) { /* handled */ }
45
+ };
46
+
47
+ const deptCount = company.departments?.length || 0;
48
+ const agentCount = company.departments?.reduce((s, d) => s + d.members.length, 0) || 0;
49
+ const enabledProviders = Object.values(company.providerDashboard || {}).reduce((s, c) => s + c.enabled, 0);
50
+ const talentCount = company.talentMarket?.length || 0;
51
+ const budget = company.budget || {};
52
+
53
+ const SUB_TABS = [
54
+ { id: 'dashboard', label: t('sidebar.nav.overview'), icon: '📊' },
55
+ { id: 'system-settings', label: t('sidebar.nav.systemSettings'), icon: '⚙️' },
56
+ ];
57
+
58
+ // 如果切换到系统设置,直接渲染 SystemMonitor
59
+ if (activeSubTab === 'system-settings') {
60
+ return (
61
+ <div className="p-6 space-y-6 animate-fade-in">
62
+ <div className="flex items-center justify-between">
63
+ <div>
64
+ <h1 className="text-2xl font-bold">{t('overview.title')}</h1>
65
+ <p className="text-sm text-[var(--muted)] mt-1">{t('overview.subtitle')}</p>
66
+ </div>
67
+ </div>
68
+ {/* Sub Tabs */}
69
+ <div className="flex gap-2">
70
+ {SUB_TABS.map(tab => (
71
+ <button
72
+ key={tab.id}
73
+ onClick={() => setActiveSubTab(tab.id)}
74
+ className={`px-4 py-2 rounded-lg text-sm flex items-center gap-2 transition-all ${
75
+ activeSubTab === tab.id
76
+ ? 'bg-[var(--accent)] text-white'
77
+ : 'bg-white/5 text-[var(--muted)] hover:bg-white/10'
78
+ }`}
79
+ >
80
+ <span>{tab.icon}</span>
81
+ <span>{tab.label}</span>
82
+ </button>
83
+ ))}
84
+ </div>
85
+ <SystemMonitor embedded />
86
+ </div>
87
+ );
88
+ }
89
+
90
+ return (
91
+ <div className="p-6 space-y-6 animate-fade-in">
92
+ <div>
93
+ <h1 className="text-2xl font-bold">{t('overview.title')}</h1>
94
+ <p className="text-sm text-[var(--muted)] mt-1">{t('overview.subtitle')}</p>
95
+ </div>
96
+
97
+ {/* Sub Tabs */}
98
+ <div className="flex gap-2">
99
+ {SUB_TABS.map(tab => (
100
+ <button
101
+ key={tab.id}
102
+ onClick={() => setActiveSubTab(tab.id)}
103
+ className={`px-4 py-2 rounded-lg text-sm flex items-center gap-2 transition-all ${
104
+ activeSubTab === tab.id
105
+ ? 'bg-[var(--accent)] text-white'
106
+ : 'bg-white/5 text-[var(--muted)] hover:bg-white/10'
107
+ }`}
108
+ >
109
+ <span>{tab.icon}</span>
110
+ <span>{tab.label}</span>
111
+ </button>
112
+ ))}
113
+ </div>
114
+
115
+ {/* Stats cards */}
116
+ <div className="grid grid-cols-5 gap-4">
117
+ {[
118
+ { label: t('overview.stats.departments'), value: deptCount, icon: '🏭', color: 'blue' },
119
+ { label: t('overview.stats.workers'), value: agentCount, icon: '🤖', color: 'green' },
120
+ { label: t('overview.stats.providers'), value: enabledProviders, icon: '⚡', color: 'purple' },
121
+ { label: t('overview.stats.talents'), value: talentCount, icon: '🏪', color: 'yellow' },
122
+ { label: t('overview.stats.burned'), value: `$${(budget.totalCost || 0).toFixed(4)}`, icon: '🔥', color: 'red', isText: true },
123
+ ].map((stat) => (
124
+ <div key={stat.label} className="card">
125
+ <div className="flex items-center justify-between">
126
+ <span className="text-2xl">{stat.icon}</span>
127
+ <span className={`${stat.isText ? 'text-xl' : 'text-3xl'} font-bold text-${stat.color}-400`}>{stat.value}</span>
128
+ </div>
129
+ <div className="text-sm text-[var(--muted)] mt-2">{stat.label}</div>
130
+ </div>
131
+ ))}
132
+ </div>
133
+
134
+ {/* Budget management */}
135
+ <div>
136
+ <h2 className="text-lg font-semibold mb-3">{t('overview.budget.title')}</h2>
137
+ <div className="grid grid-cols-3 gap-4">
138
+ <div className="card">
139
+ <div className="text-sm text-[var(--muted)] mb-2">{t('overview.budget.totalBurn')}</div>
140
+ <div className="text-2xl font-bold text-red-400">${(budget.totalCost || 0).toFixed(4)}</div>
141
+ <div className="text-xs text-[var(--muted)] mt-1">Token: {(budget.totalTokens || 0).toLocaleString()}</div>
142
+ <div className="flex gap-4 mt-3 text-xs">
143
+ <div><span className="text-[var(--muted)]">{t('overview.budget.secretary') + ': '}</span><span className="text-blue-400">{(budget.secretaryUsage?.totalTokens || 0).toLocaleString()}</span></div>
144
+ <div><span className="text-[var(--muted)]">{t('overview.budget.hr') + ': '}</span><span className="text-purple-400">{(budget.hrUsage?.totalTokens || 0).toLocaleString()}</span></div>
145
+ </div>
146
+ </div>
147
+ {company.departments?.slice(0, 2).map((dept) => (
148
+ <div key={dept.id} className="card">
149
+ <div className="text-sm text-[var(--muted)] mb-2">{dept.name}</div>
150
+ <div className="text-2xl font-bold text-blue-400">${(dept.tokenUsage?.totalCost || 0).toFixed(4)}</div>
151
+ <div className="text-xs text-[var(--muted)] mt-1">Token: {(dept.tokenUsage?.totalTokens || 0).toLocaleString()}</div>
152
+ <div className="mt-3 space-y-1">
153
+ {dept.members.slice(0, 3).map((m) => (
154
+ <div key={m.id} className="flex items-center justify-between text-xs">
155
+ <div className="flex items-center gap-1">
156
+ <CachedAvatar src={m.avatar} alt="" className="w-4 h-4 rounded-full" />
157
+ <span className="text-[var(--muted)]">{m.name}</span>
158
+ </div>
159
+ <span className="text-green-400">${(m.tokenUsage?.totalCost || 0).toFixed(4)}</span>
160
+ </div>
161
+ ))}
162
+ </div>
163
+ </div>
164
+ ))}
165
+ </div>
166
+ </div>
167
+
168
+ {/* Requirements management */}
169
+ {/* Create department modal (two-step flow) */}
170
+ {showCreate && (
171
+ <div className="fixed inset-0 z-50 bg-black/70 flex items-center justify-center !m-0" onClick={() => { setShowCreate(false); setPendingPlan(null); }}>
172
+ <div className="card max-w-lg w-full mx-4 space-y-4 max-h-[80vh] overflow-auto" onClick={e => e.stopPropagation()}>
173
+ {!pendingPlan ? (
174
+ <>
175
+ <h3 className="text-lg font-semibold">{t('overview.createDept.title')}</h3>
176
+ <p className="text-sm text-[var(--muted)]">{t('overview.createDept.desc')}</p>
177
+ <div>
178
+ <label className="block text-sm mb-1 text-[var(--muted)]">{t('overview.createDept.nameLabel')}</label>
179
+ <input className="input w-full" placeholder={t('overview.createDept.namePlaceholder')} value={deptName} onChange={e => setDeptName(e.target.value)} />
180
+ </div>
181
+ <div>
182
+ <label className="block text-sm mb-1 text-[var(--muted)]">{t('overview.createDept.missionLabel')}</label>
183
+ <textarea className="input w-full h-24 resize-none" placeholder={t('overview.createDept.missionPlaceholder')} value={deptMission} onChange={e => setDeptMission(e.target.value)} />
184
+ </div>
185
+ <div className="flex gap-2 pt-2">
186
+ <button className="btn-secondary flex-1" onClick={() => setShowCreate(false)}>{t('overview.createDept.cancelBtn')}</button>
187
+ <button className="btn-primary flex-1" disabled={!deptName || !deptMission || loading} onClick={handlePlan}>
188
+ {loading ? t('overview.createDept.planning') : t('overview.createDept.planBtn')}
189
+ </button>
190
+ </div>
191
+ </>
192
+ ) : (
193
+ <>
194
+ <h3 className="text-lg font-semibold">{t('overview.planReview.title')}</h3>
195
+ <p className="text-sm text-[var(--muted)]">
196
+ {t('overview.planReview.desc', { dept: pendingPlan.departmentName })}
197
+ </p>
198
+
199
+ {/* Secretary analysis reasoning */}
200
+ {pendingPlan.reasoning && (
201
+ <div className="bg-blue-900/10 border border-blue-500/20 rounded-lg p-3">
202
+ <div className="text-xs font-medium text-blue-400 mb-1">{t('overview.planReview.analysis')}</div>
203
+ <div className="text-sm text-[var(--muted)]">{pendingPlan.reasoning}</div>
204
+ </div>
205
+ )}
206
+
207
+ {/* Plan display */}
208
+ <div className="bg-[var(--background)] border border-[var(--border)] rounded-lg p-3 space-y-2">
209
+ <div className="text-xs text-[var(--muted)]">{t('overview.planReview.mission')}: {pendingPlan.mission}</div>
210
+ <div className="text-xs text-[var(--muted)] mb-2">{t('overview.planReview.teamSize', { n: pendingPlan.members?.length || 0 })}</div>
211
+
212
+ {pendingPlan.members?.map((m, i) => (
213
+ <div key={i} className={`flex items-center gap-3 p-2 rounded-lg ${m.isLeader ? 'bg-yellow-900/10 border border-yellow-500/20' : 'bg-white/5'}`}>
214
+ <div className="w-8 h-8 bg-gradient-to-br from-indigo-600 to-blue-700 rounded-full flex items-center justify-center text-xs">
215
+ {m.isLeader ? '👔' : '🤖'}
216
+ </div>
217
+ <div className="flex-1">
218
+ <div className="text-sm font-medium">{m.name}</div>
219
+ <div className="text-xs text-[var(--muted)]">{m.title}</div>
220
+ {m.providerName && <div className="text-[10px] text-purple-400/80 mt-0.5">⚡ {m.providerName}</div>}
221
+ {m.reason && <div className="text-[10px] text-blue-400/70 mt-0.5">💡 {m.reason}</div>}
222
+ </div>
223
+ <div className="text-right">
224
+ {m.isLeader && <span className="text-[10px] bg-yellow-900/30 text-yellow-400 px-1.5 py-0.5 rounded">{t('overview.planReview.leader')}</span>}
225
+ {m.reportsTo && <span className="text-[10px] text-[var(--muted)]">→ {m.reportsTo}</span>}
226
+ </div>
227
+ </div>
228
+ ))}
229
+ </div>
230
+
231
+ <div className="flex gap-2 pt-2">
232
+ <button className="btn-secondary flex-1" onClick={() => setPendingPlan(null)}>{t('overview.planReview.rejectBtn')}</button>
233
+ <button className="btn-primary flex-1" disabled={loading} onClick={handleConfirm}>
234
+ {loading ? t('overview.planReview.hiring') : t('overview.planReview.approveBtn')}
235
+ </button>
236
+ </div>
237
+ </>
238
+ )}
239
+ </div>
240
+ </div>
241
+ )}
242
+
243
+ {/* Requirements management */}
244
+ {(company.requirements?.length > 0 || requirements.length > 0) && (
245
+ <div>
246
+ <div className="flex items-center justify-between mb-3">
247
+ <h2 className="text-lg font-semibold">{t('requirements.title')}</h2>
248
+ <span className="text-xs text-[var(--muted)]">{t('overview.requirements.count', { n: (company.requirements || requirements).length })}</span>
249
+ </div>
250
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
251
+ {(company.requirements || requirements).map((req) => {
252
+ const statusCfg = {
253
+ pending: { label: t('requirements.status.pending'), color: 'text-gray-400', bg: 'bg-gray-900/30', icon: '⏳' },
254
+ planning: { label: t('requirements.status.planning'), color: 'text-blue-400', bg: 'bg-blue-900/30', icon: '📝' },
255
+ in_progress: { label: t('requirements.status.in_progress'), color: 'text-yellow-400', bg: 'bg-yellow-900/30', icon: '⚙️' },
256
+ pending_approval: { label: t('requirements.status.pending_approval'), color: 'text-orange-400', bg: 'bg-orange-900/30', icon: '🔍' },
257
+ completed: { label: t('requirements.stats.completed'), color: 'text-green-400', bg: 'bg-green-900/30', icon: '✅' },
258
+ failed: { label: t('requirements.status.failed'), color: 'text-red-400', bg: 'bg-red-900/30', icon: '❌' },
259
+ };
260
+ const st = statusCfg[req.status] || statusCfg.pending;
261
+
262
+ return (
263
+ <div
264
+ key={req.id}
265
+ className="card cursor-pointer hover:border-[var(--accent)]/30"
266
+ onClick={() => navigateToRequirement(req.id)}
267
+ >
268
+ <div className="flex items-start justify-between mb-2">
269
+ <div className="flex items-center gap-2">
270
+ <span>{st.icon}</span>
271
+ <span className="font-medium text-sm truncate">{req.title}</span>
272
+ </div>
273
+ <span className={`text-[10px] px-1.5 py-0.5 rounded ${st.bg} ${st.color} shrink-0`}>{st.label}</span>
274
+ </div>
275
+ <p className="text-xs text-[var(--muted)] line-clamp-2 mb-2">{req.description}</p>
276
+ <div className="flex items-center justify-between text-[10px] text-[var(--muted)]">
277
+ <span>🏢 {req.departmentName}</span>
278
+ <div className="flex items-center gap-2">
279
+ {req.workflow && <span>📊 {req.workflow.completedCount || 0}/{req.workflow.nodeCount || 0}</span>}
280
+ {req.chatCount > 0 && <span>💬 {req.chatCount}</span>}
281
+ {req.outputCount > 0 && <span>📦 {req.outputCount}</span>}
282
+ </div>
283
+ </div>
284
+ </div>
285
+ );
286
+ })}
287
+ </div>
288
+ </div>
289
+ )}
290
+
291
+ {/* Requirement detail is now a standalone page, no longer using modal */}
292
+
293
+ {/* Teams */}
294
+ {teams.length > 0 && (
295
+ <div>
296
+ <div className="flex items-center justify-between mb-3">
297
+ <h2 className="text-lg font-semibold">{t('overview.teams.title')}</h2>
298
+ <span className="text-xs text-[var(--muted)]">{t('overview.teams.count', { n: teams.length })}</span>
299
+ </div>
300
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
301
+ {teams.map((team) => {
302
+ const statusCfg = {
303
+ active: { color: 'text-green-400', bg: 'bg-green-900/30', icon: '🟢' },
304
+ archived: { color: 'text-gray-400', bg: 'bg-gray-900/30', icon: '📦' },
305
+ };
306
+ const st = statusCfg[team.status] || statusCfg.active;
307
+
308
+ return (
309
+ <div
310
+ key={team.id}
311
+ className="card cursor-pointer hover:border-[var(--accent)]/30 transition-all"
312
+ onClick={() => navigateToTeam(team.id)}
313
+ >
314
+ <div className="flex items-start justify-between mb-2">
315
+ <div className="flex items-center gap-2 min-w-0">
316
+ <span className="text-lg shrink-0">👥</span>
317
+ <span className="font-medium text-sm truncate">{team.name}</span>
318
+ </div>
319
+ <span className={`text-[10px] px-1.5 py-0.5 rounded ${st.bg} ${st.color} shrink-0`}>{st.icon} {team.status}</span>
320
+ </div>
321
+ <p className="text-xs text-[var(--muted)] line-clamp-2 mb-3">{team.description || t('team.noDescription')}</p>
322
+ <div className="flex items-center justify-between text-[10px] text-[var(--muted)]">
323
+ <div className="flex items-center gap-3">
324
+ <span>🏢 {team.departmentName}</span>
325
+ <span>👤 {t('overview.teams.members', { n: team.memberIds?.length || 0 })}</span>
326
+ {team.sprintCount > 0 && <span>🔄 {t('overview.teams.sprints', { n: team.sprintCount })}</span>}
327
+ </div>
328
+ </div>
329
+ {team.skills?.length > 0 && (
330
+ <div className="flex flex-wrap gap-1 mt-2">
331
+ {team.skills.slice(0, 4).map((s, i) => (
332
+ <span key={i} className="text-[10px] px-1.5 py-0.5 rounded bg-blue-900/20 text-blue-400">{s}</span>
333
+ ))}
334
+ {team.skills.length > 4 && <span className="text-[10px] text-[var(--muted)]">+{team.skills.length - 4}</span>}
335
+ </div>
336
+ )}
337
+ {team.leaderName && (
338
+ <div className="mt-2 text-[10px] text-[var(--muted)]">
339
+ 👔 {team.leaderName}
340
+ </div>
341
+ )}
342
+ </div>
343
+ );
344
+ })}
345
+ </div>
346
+ </div>
347
+ )}
348
+
349
+ {/* Department list */}
350
+ <div>
351
+ <h2 className="text-lg font-semibold mb-3">{t('overview.departments.title')}</h2>
352
+ {deptCount === 0 ? (
353
+ <div className="card text-center py-8 text-[var(--muted)]">
354
+ <div className="text-4xl mb-3">🏗️</div>
355
+ <p>{t('overview.departments.empty')}</p>
356
+ </div>
357
+ ) : (
358
+ <div className="grid grid-cols-2 gap-4">
359
+ {company.departments.map((dept) => (
360
+ <div key={dept.id} className="card cursor-pointer" onClick={() => setActiveTab('departments')}>
361
+ <div className="flex items-center justify-between mb-3">
362
+ <h3 className="font-semibold">{dept.name}</h3>
363
+ <div className="flex items-center gap-2">
364
+ <span className="text-xs text-red-400">🔥 ${(dept.tokenUsage?.totalCost || 0).toFixed(4)}</span>
365
+ <span className={`text-xs px-2 py-0.5 rounded-full ${
366
+ dept.status === 'completed' ? 'bg-green-900/30 text-green-400' :
367
+ dept.status === 'active' ? 'bg-yellow-900/30 text-yellow-400' :
368
+ 'bg-blue-900/30 text-blue-400'
369
+ }`}>{dept.status}</span>
370
+ </div>
371
+ </div>
372
+ <p className="text-xs text-[var(--muted)] mb-3 line-clamp-2">{dept.mission}</p>
373
+ <div className="flex items-center gap-2">
374
+ <div className="flex -space-x-2">
375
+ {dept.members.slice(0, 5).map((m) => (
376
+ <CachedAvatar key={m.id} src={m.avatar} alt={m.name} title={`${m.name} (${m.role})`} className="w-7 h-7 rounded-full border-2 border-[var(--card)] bg-[var(--border)]" />
377
+ ))}
378
+ </div>
379
+ <span className="text-xs text-[var(--muted)]">{t('overview.departments.workers', { n: dept.members.length })}</span>
380
+ </div>
381
+ </div>
382
+ ))}
383
+ </div>
384
+ )}
385
+ </div>
386
+
387
+
388
+
389
+ </div>
390
+ );
391
+ }