@hed-hog/operations 0.0.3 → 0.0.285

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 (108) hide show
  1. package/README.md +122 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/operations-data.controller.d.ts +139 -0
  7. package/dist/operations-data.controller.d.ts.map +1 -0
  8. package/dist/operations-data.controller.js +113 -0
  9. package/dist/operations-data.controller.js.map +1 -0
  10. package/dist/operations-growth.controller.d.ts +48 -0
  11. package/dist/operations-growth.controller.d.ts.map +1 -0
  12. package/dist/operations-growth.controller.js +90 -0
  13. package/dist/operations-growth.controller.js.map +1 -0
  14. package/dist/operations.module.d.ts.map +1 -1
  15. package/dist/operations.module.js +10 -4
  16. package/dist/operations.module.js.map +1 -1
  17. package/dist/operations.service.d.ts +178 -0
  18. package/dist/operations.service.d.ts.map +1 -0
  19. package/dist/operations.service.js +134 -0
  20. package/dist/operations.service.js.map +1 -0
  21. package/hedhog/data/menu.yaml +251 -132
  22. package/hedhog/data/operations_career_level.yaml +102 -0
  23. package/hedhog/data/operations_career_track.yaml +8 -0
  24. package/hedhog/data/operations_certification.yaml +38 -0
  25. package/hedhog/data/operations_evaluation_cycle.yaml +18 -0
  26. package/hedhog/data/operations_performance_criterion.yaml +48 -0
  27. package/hedhog/data/role.yaml +14 -7
  28. package/hedhog/data/route.yaml +143 -80
  29. package/hedhog/frontend/app/_components/allocation-calendar.tsx.ejs +56 -56
  30. package/hedhog/frontend/app/_components/kanban-board.tsx.ejs +83 -83
  31. package/hedhog/frontend/app/_components/operations-header.tsx.ejs +29 -29
  32. package/hedhog/frontend/app/_components/section-card.tsx.ejs +32 -32
  33. package/hedhog/frontend/app/_components/status-badge.tsx.ejs +15 -15
  34. package/hedhog/frontend/app/_components/timesheet-entry-dialog.tsx.ejs +142 -142
  35. package/hedhog/frontend/app/_lib/hooks/use-operations-data.ts.ejs +41 -41
  36. package/hedhog/frontend/app/_lib/hooks/use-operations-growth-data.ts.ejs +63 -0
  37. package/hedhog/frontend/app/_lib/mocks/allocations.mock.ts.ejs +74 -74
  38. package/hedhog/frontend/app/_lib/mocks/contracts.mock.ts.ejs +74 -74
  39. package/hedhog/frontend/app/_lib/mocks/operations-growth.mock.ts.ejs +824 -0
  40. package/hedhog/frontend/app/_lib/mocks/projects.mock.ts.ejs +60 -60
  41. package/hedhog/frontend/app/_lib/mocks/tasks.mock.ts.ejs +88 -88
  42. package/hedhog/frontend/app/_lib/mocks/timesheets.mock.ts.ejs +84 -84
  43. package/hedhog/frontend/app/_lib/mocks/users.mock.ts.ejs +67 -67
  44. package/hedhog/frontend/app/_lib/services/contracts.service.ts.ejs +10 -10
  45. package/hedhog/frontend/app/_lib/services/operations-growth.service.ts.ejs +31 -0
  46. package/hedhog/frontend/app/_lib/services/projects.service.ts.ejs +10 -10
  47. package/hedhog/frontend/app/_lib/services/tasks.service.ts.ejs +10 -10
  48. package/hedhog/frontend/app/_lib/services/timesheets.service.ts.ejs +10 -10
  49. package/hedhog/frontend/app/_lib/types/operations-growth.ts.ejs +209 -0
  50. package/hedhog/frontend/app/_lib/types/operations.ts.ejs +95 -95
  51. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +25 -25
  52. package/hedhog/frontend/app/_lib/utils/growth.ts.ejs +62 -0
  53. package/hedhog/frontend/app/_lib/utils/metrics.ts.ejs +103 -103
  54. package/hedhog/frontend/app/_lib/utils/status.ts.ejs +80 -80
  55. package/hedhog/frontend/app/allocations/page.tsx.ejs +154 -99
  56. package/hedhog/frontend/app/approvals/page.tsx.ejs +147 -147
  57. package/hedhog/frontend/app/career/page.tsx.ejs +143 -0
  58. package/hedhog/frontend/app/certifications/page.tsx.ejs +201 -0
  59. package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +108 -108
  60. package/hedhog/frontend/app/contracts/page.tsx.ejs +180 -124
  61. package/hedhog/frontend/app/evaluations/page.tsx.ejs +277 -0
  62. package/hedhog/frontend/app/goals/page.tsx.ejs +170 -0
  63. package/hedhog/frontend/app/growth/page.tsx.ejs +288 -0
  64. package/hedhog/frontend/app/layout.tsx.ejs +9 -9
  65. package/hedhog/frontend/app/manager/page.tsx.ejs +175 -0
  66. package/hedhog/frontend/app/page.tsx.ejs +177 -177
  67. package/hedhog/frontend/app/projects/[id]/page.tsx.ejs +186 -186
  68. package/hedhog/frontend/app/projects/page.tsx.ejs +111 -111
  69. package/hedhog/frontend/app/rewards/page.tsx.ejs +195 -0
  70. package/hedhog/frontend/app/tasks/page.tsx.ejs +47 -47
  71. package/hedhog/frontend/app/timesheets/page.tsx.ejs +126 -126
  72. package/hedhog/frontend/messages/en.json +152 -142
  73. package/hedhog/frontend/messages/pt.json +152 -142
  74. package/hedhog/table/operations_allocation.yaml +52 -0
  75. package/hedhog/table/operations_calibration_item.yaml +61 -0
  76. package/hedhog/table/operations_calibration_session.yaml +25 -0
  77. package/hedhog/table/operations_career_level.yaml +75 -0
  78. package/hedhog/table/operations_career_track.yaml +21 -0
  79. package/hedhog/table/operations_certification.yaml +48 -0
  80. package/hedhog/table/operations_contract.yaml +57 -0
  81. package/hedhog/table/operations_employee.yaml +64 -0
  82. package/hedhog/table/operations_employee_certification.yaml +43 -0
  83. package/hedhog/table/operations_employee_connect.yaml +61 -0
  84. package/hedhog/table/operations_employee_evaluation.yaml +113 -0
  85. package/hedhog/table/operations_employee_evaluation_item.yaml +39 -0
  86. package/hedhog/table/operations_employee_profile.yaml +80 -0
  87. package/hedhog/table/operations_employee_skill_matrix.yaml +30 -0
  88. package/hedhog/table/operations_evaluation_cycle.yaml +31 -0
  89. package/hedhog/table/operations_goal.yaml +67 -0
  90. package/hedhog/table/operations_goal_progress.yaml +31 -0
  91. package/hedhog/table/operations_performance_criterion.yaml +29 -0
  92. package/hedhog/table/operations_project.yaml +66 -0
  93. package/hedhog/table/operations_promotion_readiness.yaml +49 -0
  94. package/hedhog/table/operations_promotion_recommendation.yaml +63 -0
  95. package/hedhog/table/operations_public_recognition.yaml +46 -0
  96. package/hedhog/table/operations_reward.yaml +100 -0
  97. package/hedhog/table/operations_score_event.yaml +81 -0
  98. package/hedhog/table/operations_task.yaml +60 -0
  99. package/hedhog/table/operations_timesheet.yaml +49 -0
  100. package/hedhog/table/operations_timesheet_entry.yaml +51 -0
  101. package/package.json +4 -4
  102. package/src/index.ts +2 -1
  103. package/src/language/en.json +8 -8
  104. package/src/language/pt.json +8 -8
  105. package/src/operations-data.controller.ts +54 -0
  106. package/src/operations-growth.controller.ts +44 -0
  107. package/src/operations.module.ts +21 -15
  108. package/src/operations.service.ts +137 -0
@@ -0,0 +1,288 @@
1
+ 'use client';
2
+
3
+ import { EmptyState, Page, SearchBar } from '@/components/entity-list';
4
+ import { KpiCard } from '@/components/ui/kpi-card';
5
+ import { Badge } from '@/components/ui/badge';
6
+ import { Progress } from '@/components/ui/progress';
7
+ import {
8
+ Line,
9
+ LineChart,
10
+ ResponsiveContainer,
11
+ Tooltip,
12
+ XAxis,
13
+ YAxis,
14
+ } from 'recharts';
15
+ import { Award, ChevronRight, Flag, Sparkles, TrendingUp } from 'lucide-react';
16
+ import { useTranslations } from 'next-intl';
17
+ import { useMemo, useState } from 'react';
18
+ import { OperationsHeader } from '../_components/operations-header';
19
+ import { SectionCard } from '../_components/section-card';
20
+ import { StatusBadge } from '../_components/status-badge';
21
+ import { useOperationsGrowthData } from '../_lib/hooks/use-operations-growth-data';
22
+ import { formatCurrency, formatDate } from '../_lib/utils/format';
23
+ import {
24
+ getGrowthPromotionBadgeClasses,
25
+ humanizeGrowthStatus,
26
+ } from '../_lib/utils/growth';
27
+
28
+ export default function OperationsGrowthDashboardPage() {
29
+ const t = useTranslations('operations.GrowthDashboardPage');
30
+ const {
31
+ users,
32
+ profiles,
33
+ careerTracks,
34
+ careerLevels,
35
+ goals,
36
+ employeeCertifications,
37
+ certificationCatalog,
38
+ recognitions,
39
+ scoreEvents,
40
+ } = useOperationsGrowthData();
41
+ const [searchInput, setSearchInput] = useState('');
42
+ const [search, setSearch] = useState('');
43
+ const [trackFilter, setTrackFilter] = useState('all');
44
+
45
+ const filteredProfiles = useMemo(
46
+ () =>
47
+ profiles.filter((profile) => {
48
+ const user = users.find((item) => item.id === profile.employeeId);
49
+ const track = careerTracks.find((item) => item.id === profile.trackId);
50
+ const matchesSearch = `${user?.name ?? ''} ${profile.story}`
51
+ .toLowerCase()
52
+ .includes(search.toLowerCase());
53
+ const matchesTrack =
54
+ trackFilter === 'all' || profile.trackId === trackFilter;
55
+
56
+ return matchesSearch && matchesTrack;
57
+ }),
58
+ [careerTracks, profiles, search, trackFilter, users]
59
+ );
60
+
61
+ const selectedProfile = filteredProfiles[0];
62
+
63
+ if (!selectedProfile) {
64
+ return (
65
+ <Page>
66
+ <OperationsHeader
67
+ title={t('title')}
68
+ description={t('description')}
69
+ current={t('breadcrumb')}
70
+ />
71
+ <EmptyState
72
+ icon={<TrendingUp className="h-12 w-12" />}
73
+ title={t('emptyState.title')}
74
+ description={t('emptyState.description')}
75
+ actionLabel={t('emptyState.action')}
76
+ onAction={() => {
77
+ setSearch('');
78
+ setSearchInput('');
79
+ setTrackFilter('all');
80
+ }}
81
+ />
82
+ </Page>
83
+ );
84
+ }
85
+
86
+ const employee = users.find((item) => item.id === selectedProfile.employeeId);
87
+ const currentLevel = careerLevels.find(
88
+ (item) => item.id === selectedProfile.currentLevelId
89
+ );
90
+ const nextLevel = careerLevels.find(
91
+ (item) => item.id === selectedProfile.nextLevelId
92
+ );
93
+ const track = careerTracks.find((item) => item.id === selectedProfile.trackId);
94
+ const employeeGoals = goals.filter(
95
+ (item) => item.employeeId === selectedProfile.employeeId
96
+ );
97
+ const employeeRecognitions = recognitions.filter(
98
+ (item) => item.employeeId === selectedProfile.employeeId
99
+ );
100
+ const employeeCerts = employeeCertifications.filter(
101
+ (item) => item.employeeId === selectedProfile.employeeId
102
+ );
103
+ const scoreHistory = scoreEvents
104
+ .filter((item) => item.employeeId === selectedProfile.employeeId)
105
+ .map((item) => ({ date: formatDate(item.referenceDate), score: item.scoreDelta }));
106
+
107
+ return (
108
+ <Page>
109
+ <OperationsHeader
110
+ title={t('title')}
111
+ description={t('description')}
112
+ current={t('breadcrumb')}
113
+ />
114
+
115
+ <SearchBar
116
+ className="mb-6"
117
+ searchQuery={searchInput}
118
+ onSearchChange={setSearchInput}
119
+ onSearch={() => setSearch(searchInput)}
120
+ placeholder={t('searchPlaceholder')}
121
+ controls={[
122
+ {
123
+ id: 'track-filter',
124
+ type: 'select',
125
+ value: trackFilter,
126
+ onChange: setTrackFilter,
127
+ placeholder: t('filters.allTracks'),
128
+ options: [
129
+ { value: 'all', label: t('filters.allTracks') },
130
+ ...careerTracks.map((item) => ({ value: item.id, label: item.name })),
131
+ ],
132
+ },
133
+ ]}
134
+ />
135
+
136
+ <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
137
+ <KpiCard title={t('cards.score')} value={selectedProfile.currentScore} icon={TrendingUp} />
138
+ <KpiCard title={t('cards.currentLevel')} value={currentLevel?.name ?? '-'} icon={Award} />
139
+ <KpiCard title={t('cards.nextLevel')} value={nextLevel?.name ?? t('cards.none')} icon={ChevronRight} />
140
+ <KpiCard
141
+ title={t('cards.certifications')}
142
+ value={employeeCerts.length}
143
+ description={t('cards.certificationsDescription')}
144
+ icon={Sparkles}
145
+ />
146
+ </div>
147
+
148
+ <div className="grid gap-4 xl:grid-cols-[1.3fr_0.9fr]">
149
+ <SectionCard
150
+ title={t('profile.title', { name: employee?.name ?? '-' })}
151
+ description={t('profile.description', { track: track?.name ?? '-' })}
152
+ >
153
+ <div className="space-y-6">
154
+ <div className="grid gap-4 md:grid-cols-2">
155
+ <div className="space-y-3">
156
+ <div className="flex items-center justify-between">
157
+ <span className="text-sm font-medium">{t('profile.promotionProgress')}</span>
158
+ <StatusBadge
159
+ label={humanizeGrowthStatus(selectedProfile.promotionStatus)}
160
+ className={getGrowthPromotionBadgeClasses(
161
+ selectedProfile.promotionStatus
162
+ )}
163
+ />
164
+ </div>
165
+ <Progress value={selectedProfile.promotionProgress} />
166
+ <p className="text-muted-foreground text-sm">
167
+ {t('profile.progressLabel', {
168
+ progress: selectedProfile.promotionProgress,
169
+ score: selectedProfile.currentScore,
170
+ target: selectedProfile.nextLevelScore,
171
+ })}
172
+ </p>
173
+ </div>
174
+ <div className="rounded-lg border border-dashed border-slate-300 p-4">
175
+ <p className="text-sm font-medium">{t('profile.salaryBand')}</p>
176
+ <p className="mt-2 text-lg font-semibold">
177
+ {currentLevel
178
+ ? `${formatCurrency(currentLevel.salaryMin)} - ${formatCurrency(
179
+ currentLevel.salaryMax
180
+ )}`
181
+ : '-'}
182
+ </p>
183
+ <p className="text-muted-foreground mt-2 text-sm">{selectedProfile.story}</p>
184
+ </div>
185
+ </div>
186
+
187
+ <div className="grid gap-4 md:grid-cols-2">
188
+ <div>
189
+ <p className="mb-3 text-sm font-medium">{t('profile.strengths')}</p>
190
+ <div className="flex flex-wrap gap-2">
191
+ {selectedProfile.strengths.map((item) => (
192
+ <Badge key={item} variant="secondary">
193
+ {item}
194
+ </Badge>
195
+ ))}
196
+ </div>
197
+ </div>
198
+ <div>
199
+ <p className="mb-3 text-sm font-medium">{t('profile.gaps')}</p>
200
+ <div className="flex flex-wrap gap-2">
201
+ {selectedProfile.gaps.map((item) => (
202
+ <Badge key={item} variant="outline">
203
+ {item}
204
+ </Badge>
205
+ ))}
206
+ </div>
207
+ </div>
208
+ </div>
209
+
210
+ <div className="grid gap-4 md:grid-cols-2">
211
+ <div>
212
+ <p className="mb-2 text-sm font-medium">{t('profile.goalsInProgress')}</p>
213
+ <div className="space-y-3">
214
+ {employeeGoals.map((goal) => (
215
+ <div key={goal.id} className="rounded-lg border p-3">
216
+ <div className="mb-2 flex items-center justify-between gap-3">
217
+ <span className="font-medium">{goal.title}</span>
218
+ <span className="text-muted-foreground text-sm">
219
+ {goal.progressPercent}%
220
+ </span>
221
+ </div>
222
+ <Progress value={goal.progressPercent} />
223
+ </div>
224
+ ))}
225
+ </div>
226
+ </div>
227
+ <div>
228
+ <p className="mb-2 text-sm font-medium">{t('profile.recentRecognition')}</p>
229
+ <div className="space-y-3">
230
+ {employeeRecognitions.slice(0, 3).map((recognition) => (
231
+ <div key={recognition.id} className="rounded-lg border p-3">
232
+ <p className="text-sm">{recognition.message}</p>
233
+ <p className="text-muted-foreground mt-2 text-xs">
234
+ {formatDate(recognition.date)}
235
+ </p>
236
+ </div>
237
+ ))}
238
+ </div>
239
+ </div>
240
+ </div>
241
+ </div>
242
+ </SectionCard>
243
+
244
+ <SectionCard title={t('sidebar.title')} description={t('sidebar.description')}>
245
+ <div className="space-y-4">
246
+ <div className="rounded-lg border p-4">
247
+ <p className="text-muted-foreground text-sm">{t('sidebar.currentTrack')}</p>
248
+ <p className="mt-1 text-lg font-semibold">{track?.name ?? '-'}</p>
249
+ </div>
250
+ <div className="rounded-lg border p-4">
251
+ <p className="text-muted-foreground text-sm">{t('sidebar.requiredCerts')}</p>
252
+ <div className="mt-3 space-y-3">
253
+ {employeeCerts.map((item) => {
254
+ const certification = certificationCatalog.find(
255
+ (catalogItem) => catalogItem.id === item.certificationId
256
+ );
257
+
258
+ return (
259
+ <div key={item.id} className="flex items-start justify-between gap-3">
260
+ <div>
261
+ <p className="font-medium">{certification?.name}</p>
262
+ <p className="text-muted-foreground text-xs">{certification?.provider}</p>
263
+ </div>
264
+ <Badge variant={item.status === 'achieved' ? 'default' : 'outline'}>
265
+ {humanizeGrowthStatus(item.status)}
266
+ </Badge>
267
+ </div>
268
+ );
269
+ })}
270
+ </div>
271
+ </div>
272
+ <div className="rounded-lg border p-4">
273
+ <p className="mb-3 text-sm font-medium">{t('sidebar.scoreHistory')}</p>
274
+ <ResponsiveContainer width="100%" height={180}>
275
+ <LineChart data={scoreHistory}>
276
+ <XAxis dataKey="date" tick={{ fontSize: 11 }} />
277
+ <YAxis />
278
+ <Tooltip />
279
+ <Line dataKey="score" type="monotone" stroke="#0f766e" strokeWidth={3} />
280
+ </LineChart>
281
+ </ResponsiveContainer>
282
+ </div>
283
+ </div>
284
+ </SectionCard>
285
+ </div>
286
+ </Page>
287
+ );
288
+ }
@@ -1,9 +1,9 @@
1
- import { ReactNode } from 'react';
2
-
3
- export default function OperationsLayout({
4
- children,
5
- }: {
6
- children: ReactNode;
7
- }) {
8
- return children;
9
- }
1
+ import { ReactNode } from 'react';
2
+
3
+ export default function OperationsLayout({
4
+ children,
5
+ }: {
6
+ children: ReactNode;
7
+ }) {
8
+ return children;
9
+ }
@@ -0,0 +1,175 @@
1
+ 'use client';
2
+
3
+ import { Page } from '@/components/entity-list';
4
+ import { KpiCard } from '@/components/ui/kpi-card';
5
+ import {
6
+ Table,
7
+ TableBody,
8
+ TableCell,
9
+ TableHead,
10
+ TableHeader,
11
+ TableRow,
12
+ } from '@/components/ui/table';
13
+ import { AlertTriangle, ArrowUp, Medal, Users } from 'lucide-react';
14
+ import { useTranslations } from 'next-intl';
15
+ import { OperationsHeader } from '../_components/operations-header';
16
+ import { SectionCard } from '../_components/section-card';
17
+ import { StatusBadge } from '../_components/status-badge';
18
+ import { useOperationsGrowthData } from '../_lib/hooks/use-operations-growth-data';
19
+ import {
20
+ getGrowthGoalBadgeClasses,
21
+ getGrowthPromotionBadgeClasses,
22
+ humanizeGrowthStatus,
23
+ } from '../_lib/utils/growth';
24
+
25
+ export default function OperationsManagerPage() {
26
+ const t = useTranslations('operations.ManagerPage');
27
+ const {
28
+ ranking,
29
+ managerSummary,
30
+ users,
31
+ goals,
32
+ employeeCertifications,
33
+ promotionRecommendations,
34
+ careerLevels,
35
+ } = useOperationsGrowthData();
36
+
37
+ const atRiskGoals = goals.filter((item) => item.status === 'at_risk');
38
+ const pendingCertifications = employeeCertifications.filter(
39
+ (item) => item.status !== 'achieved'
40
+ );
41
+
42
+ return (
43
+ <Page>
44
+ <OperationsHeader
45
+ title={t('title')}
46
+ description={t('description')}
47
+ current={t('breadcrumb')}
48
+ />
49
+
50
+ <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
51
+ <KpiCard title={t('cards.promotionEligible')} value={managerSummary.promotionEligible} icon={ArrowUp} />
52
+ <KpiCard title={t('cards.goalsAtRisk')} value={managerSummary.goalsAtRisk} icon={AlertTriangle} />
53
+ <KpiCard title={t('cards.pendingCertifications')} value={managerSummary.pendingCertifications} icon={Medal} />
54
+ <KpiCard title={t('cards.totalRecognitions')} value={managerSummary.totalRecognitions} icon={Users} />
55
+ </div>
56
+
57
+ <div className="grid gap-4 xl:grid-cols-[1.15fr_0.85fr]">
58
+ <SectionCard title={t('rankingTitle')} description={t('rankingDescription')}>
59
+ <Table>
60
+ <TableHeader>
61
+ <TableRow>
62
+ <TableHead>{t('ranking.employee')}</TableHead>
63
+ <TableHead>{t('ranking.score')}</TableHead>
64
+ <TableHead>{t('ranking.progress')}</TableHead>
65
+ <TableHead>{t('ranking.status')}</TableHead>
66
+ </TableRow>
67
+ </TableHeader>
68
+ <TableBody>
69
+ {ranking.map((profile) => {
70
+ const employee = users.find((item) => item.id === profile.employeeId);
71
+
72
+ return (
73
+ <TableRow key={profile.employeeId}>
74
+ <TableCell className="font-medium">{employee?.name}</TableCell>
75
+ <TableCell>{profile.currentScore}</TableCell>
76
+ <TableCell>{profile.promotionProgress}%</TableCell>
77
+ <TableCell>
78
+ <StatusBadge
79
+ label={humanizeGrowthStatus(profile.promotionStatus)}
80
+ className={getGrowthPromotionBadgeClasses(profile.promotionStatus)}
81
+ />
82
+ </TableCell>
83
+ </TableRow>
84
+ );
85
+ })}
86
+ </TableBody>
87
+ </Table>
88
+ </SectionCard>
89
+
90
+ <SectionCard title={t('recommendationsTitle')} description={t('recommendationsDescription')}>
91
+ <div className="space-y-4">
92
+ {promotionRecommendations.map((recommendation) => {
93
+ const employee = users.find((item) => item.id === recommendation.employeeId);
94
+ const suggestedLevel = careerLevels.find(
95
+ (item) => item.id === recommendation.suggestedLevelId
96
+ );
97
+
98
+ return (
99
+ <div key={recommendation.id} className="rounded-lg border p-4">
100
+ <div className="mb-2 flex items-center justify-between gap-3">
101
+ <span className="font-medium">{employee?.name}</span>
102
+ <StatusBadge
103
+ label={humanizeGrowthStatus(recommendation.status)}
104
+ className={getGrowthPromotionBadgeClasses(
105
+ recommendation.eligible ? 'recommended' : 'developing'
106
+ )}
107
+ />
108
+ </div>
109
+ <p className="text-muted-foreground text-sm">
110
+ {t('recommendations.nextLevel', { level: suggestedLevel?.name ?? '-' })}
111
+ </p>
112
+ <p className="mt-3 text-sm">{recommendation.justification}</p>
113
+ </div>
114
+ );
115
+ })}
116
+ </div>
117
+ </SectionCard>
118
+ </div>
119
+
120
+ <div className="grid gap-4 xl:grid-cols-2">
121
+ <SectionCard title={t('riskTitle')} description={t('riskDescription')}>
122
+ <div className="space-y-3">
123
+ {atRiskGoals.map((goal) => {
124
+ const employee = users.find((item) => item.id === goal.employeeId);
125
+
126
+ return (
127
+ <div key={goal.id} className="rounded-lg border p-4">
128
+ <div className="flex items-center justify-between gap-3">
129
+ <div>
130
+ <p className="font-medium">{goal.title}</p>
131
+ <p className="text-muted-foreground text-sm">
132
+ {employee?.name ?? goal.teamName}
133
+ </p>
134
+ </div>
135
+ <StatusBadge
136
+ label={humanizeGrowthStatus(goal.status)}
137
+ className={getGrowthGoalBadgeClasses(goal.status)}
138
+ />
139
+ </div>
140
+ </div>
141
+ );
142
+ })}
143
+ </div>
144
+ </SectionCard>
145
+
146
+ <SectionCard title={t('certificationTitle')} description={t('certificationDescription')}>
147
+ <div className="space-y-3">
148
+ {pendingCertifications.map((certification) => {
149
+ const employee = users.find((item) => item.id === certification.employeeId);
150
+
151
+ return (
152
+ <div key={certification.id} className="rounded-lg border p-4">
153
+ <div className="flex items-center justify-between gap-3">
154
+ <div>
155
+ <p className="font-medium">{employee?.name}</p>
156
+ <p className="text-muted-foreground text-sm">
157
+ {humanizeGrowthStatus(certification.status)}
158
+ </p>
159
+ </div>
160
+ <StatusBadge
161
+ label={humanizeGrowthStatus(certification.status)}
162
+ className={getGrowthGoalBadgeClasses(
163
+ certification.status === 'planned' ? 'draft' : 'at_risk'
164
+ )}
165
+ />
166
+ </div>
167
+ </div>
168
+ );
169
+ })}
170
+ </div>
171
+ </SectionCard>
172
+ </div>
173
+ </Page>
174
+ );
175
+ }