@hed-hog/operations 0.0.296 → 0.0.298

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 (32) hide show
  1. package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +310 -310
  2. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +631 -631
  3. package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +132 -132
  4. package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +558 -558
  5. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +291 -291
  6. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +689 -689
  7. package/hedhog/frontend/app/_lib/api.ts.ejs +32 -32
  8. package/hedhog/frontend/app/_lib/hooks/use-operations-access.ts.ejs +44 -44
  9. package/hedhog/frontend/app/_lib/types.ts.ejs +360 -360
  10. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +129 -129
  11. package/hedhog/frontend/app/_lib/utils/forms.ts.ejs +14 -14
  12. package/hedhog/frontend/app/approvals/page.tsx.ejs +386 -386
  13. package/hedhog/frontend/app/collaborators/[id]/edit/page.tsx.ejs +11 -11
  14. package/hedhog/frontend/app/collaborators/[id]/page.tsx.ejs +11 -11
  15. package/hedhog/frontend/app/collaborators/new/page.tsx.ejs +5 -5
  16. package/hedhog/frontend/app/collaborators/page.tsx.ejs +261 -261
  17. package/hedhog/frontend/app/contracts/[id]/edit/page.tsx.ejs +11 -11
  18. package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +11 -11
  19. package/hedhog/frontend/app/contracts/new/page.tsx.ejs +17 -17
  20. package/hedhog/frontend/app/contracts/page.tsx.ejs +262 -262
  21. package/hedhog/frontend/app/page.tsx.ejs +319 -319
  22. package/hedhog/frontend/app/projects/[id]/edit/page.tsx.ejs +11 -11
  23. package/hedhog/frontend/app/projects/[id]/page.tsx.ejs +11 -11
  24. package/hedhog/frontend/app/projects/new/page.tsx.ejs +5 -5
  25. package/hedhog/frontend/app/projects/page.tsx.ejs +236 -236
  26. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +418 -418
  27. package/hedhog/frontend/app/team/page.tsx.ejs +339 -339
  28. package/hedhog/frontend/app/time-off/page.tsx.ejs +328 -328
  29. package/hedhog/frontend/app/timesheets/page.tsx.ejs +636 -636
  30. package/hedhog/frontend/messages/en.json +648 -648
  31. package/hedhog/frontend/messages/pt.json +647 -647
  32. package/package.json +2 -2
@@ -1,291 +1,291 @@
1
- 'use client';
2
-
3
- import { EmptyState, Page } from '@/components/entity-list';
4
- import { Button } from '@/components/ui/button';
5
- import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
6
- import {
7
- Table,
8
- TableBody,
9
- TableCell,
10
- TableHead,
11
- TableHeader,
12
- TableRow,
13
- } from '@/components/ui/table';
14
- import { useApp, useQuery } from '@hed-hog/next-app-provider';
15
- import { FileText, FolderKanban, Pencil } from 'lucide-react';
16
- import Link from 'next/link';
17
- import { useTranslations } from 'next-intl';
18
- import { OperationsHeader } from './operations-header';
19
- import { SectionCard } from './section-card';
20
- import { StatusBadge } from './status-badge';
21
- import { fetchOperations } from '../_lib/api';
22
- import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
23
- import type { OperationsProjectDetails } from '../_lib/types';
24
- import {
25
- formatCurrency,
26
- formatDate,
27
- formatDateRange,
28
- formatEnumLabel,
29
- formatHours,
30
- formatPercent,
31
- getStatusBadgeClass,
32
- } from '../_lib/utils/format';
33
-
34
- export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
35
- const t = useTranslations('operations.ProjectDetailsPage');
36
- const commonT = useTranslations('operations.Common');
37
- const { request, currentLocaleCode } = useApp();
38
- const access = useOperationsAccess();
39
-
40
- const { data: project, refetch } = useQuery<OperationsProjectDetails>({
41
- queryKey: ['operations-project-details', currentLocaleCode, projectId],
42
- queryFn: () =>
43
- fetchOperations<OperationsProjectDetails>(request, `/operations/projects/${projectId}`),
44
- });
45
-
46
- if (!project) {
47
- return (
48
- <Page>
49
- <OperationsHeader
50
- title={t('title')}
51
- description={t('description')}
52
- current={t('breadcrumb')}
53
- />
54
- <EmptyState
55
- icon={<FolderKanban className="size-12" />}
56
- title={commonT('states.emptyTitle')}
57
- description={t('notFound')}
58
- actionLabel={commonT('actions.refresh')}
59
- onAction={() => void refetch()}
60
- />
61
- </Page>
62
- );
63
- }
64
-
65
- const cards = [
66
- {
67
- key: 'teamSize',
68
- title: t('cards.teamSize'),
69
- value: project.teamSize ?? 0,
70
- description: t('cards.teamSizeDescription'),
71
- },
72
- {
73
- key: 'timesheets',
74
- title: t('cards.timesheets'),
75
- value: project.timesheetSummary.totalTimesheets,
76
- description: t('cards.timesheetsDescription', {
77
- pending: project.timesheetSummary.pendingTimesheets,
78
- }),
79
- },
80
- {
81
- key: 'hours',
82
- title: t('cards.loggedHours'),
83
- value: formatHours(project.timesheetSummary.totalHours),
84
- description: t('cards.loggedHoursDescription'),
85
- },
86
- {
87
- key: 'allocation',
88
- title: t('cards.allocation'),
89
- value: formatPercent(project.operationalIndicators.averageAllocation),
90
- description: t('cards.allocationDescription'),
91
- },
92
- ];
93
-
94
- return (
95
- <Page>
96
- <OperationsHeader
97
- title={project.name}
98
- description={t('description')}
99
- current={t('breadcrumb')}
100
- actions={
101
- access.isDirector ? (
102
- <div className="flex gap-2">
103
- {project.contractId ? (
104
- <Button variant="outline" size="sm" asChild>
105
- <Link href={`/operations/contracts?edit=${project.contractId}`}>
106
- <FileText className="size-4" />
107
- {commonT('actions.openContract')}
108
- </Link>
109
- </Button>
110
- ) : null}
111
- <Button size="sm" asChild>
112
- <Link href={`/operations/projects/${project.id}/edit`}>
113
- <Pencil className="size-4" />
114
- {commonT('actions.edit')}
115
- </Link>
116
- </Button>
117
- </div>
118
- ) : undefined
119
- }
120
- />
121
-
122
- <div className="grid gap-4 xl:grid-cols-4">
123
- <SectionCard title={t('sections.overview')} className="xl:col-span-2">
124
- <dl className="grid gap-3 text-sm md:grid-cols-2">
125
- <div>
126
- <dt className="text-muted-foreground">{commonT('labels.project')}</dt>
127
- <dd className="font-medium">{project.name}</dd>
128
- </div>
129
- <div>
130
- <dt className="text-muted-foreground">{commonT('labels.client')}</dt>
131
- <dd className="font-medium">
132
- {project.clientName || commonT('labels.notAvailable')}
133
- </dd>
134
- </div>
135
- <div>
136
- <dt className="text-muted-foreground">{commonT('labels.manager')}</dt>
137
- <dd className="font-medium">
138
- {project.managerName || commonT('labels.notAssigned')}
139
- </dd>
140
- </div>
141
- <div>
142
- <dt className="text-muted-foreground">{commonT('labels.status')}</dt>
143
- <dd className="font-medium">
144
- <StatusBadge
145
- label={formatEnumLabel(project.status)}
146
- className={getStatusBadgeClass(project.status)}
147
- />
148
- </dd>
149
- </div>
150
- <div>
151
- <dt className="text-muted-foreground">{commonT('labels.startDate')}</dt>
152
- <dd className="font-medium">{formatDate(project.startDate)}</dd>
153
- </div>
154
- <div>
155
- <dt className="text-muted-foreground">{commonT('labels.endDate')}</dt>
156
- <dd className="font-medium">{formatDate(project.endDate)}</dd>
157
- </div>
158
- <div>
159
- <dt className="text-muted-foreground">{commonT('labels.budget')}</dt>
160
- <dd className="font-medium">
161
- {project.budgetAmount
162
- ? formatCurrency(project.budgetAmount)
163
- : commonT('labels.notAvailable')}
164
- </dd>
165
- </div>
166
- <div>
167
- <dt className="text-muted-foreground">{commonT('labels.progress')}</dt>
168
- <dd className="font-medium">{formatPercent(project.progressPercent)}</dd>
169
- </div>
170
- </dl>
171
- {project.summary ? (
172
- <p className="mt-4 text-sm text-muted-foreground">{project.summary}</p>
173
- ) : null}
174
- </SectionCard>
175
-
176
- <SectionCard title={t('sections.contract')} className="xl:col-span-2">
177
- {project.relatedContract ? (
178
- <div className="space-y-3">
179
- <div className="flex items-center justify-between rounded-lg border px-4 py-3">
180
- <div>
181
- <div className="font-medium">{project.relatedContract.name}</div>
182
- <div className="text-sm text-muted-foreground">
183
- {project.relatedContract.code} •{' '}
184
- {formatEnumLabel(project.relatedContract.contractCategory)}
185
- </div>
186
- </div>
187
- <StatusBadge
188
- label={formatEnumLabel(project.relatedContract.status)}
189
- className={getStatusBadgeClass(project.relatedContract.status)}
190
- />
191
- </div>
192
- <dl className="grid gap-3 text-sm md:grid-cols-2">
193
- <div>
194
- <dt className="text-muted-foreground">{commonT('labels.billingModel')}</dt>
195
- <dd className="font-medium">
196
- {formatEnumLabel(project.relatedContract.billingModel)}
197
- </dd>
198
- </div>
199
- <div>
200
- <dt className="text-muted-foreground">{commonT('labels.timeline')}</dt>
201
- <dd className="font-medium">
202
- {formatDateRange(
203
- project.relatedContract.startDate,
204
- project.relatedContract.endDate
205
- )}
206
- </dd>
207
- </div>
208
- </dl>
209
- </div>
210
- ) : (
211
- <p className="text-sm text-muted-foreground">{t('noContract')}</p>
212
- )}
213
- </SectionCard>
214
- </div>
215
-
216
- <KpiCardsGrid items={cards} />
217
-
218
- <div className="grid gap-4 xl:grid-cols-2">
219
- <SectionCard title={t('sections.team')} description={t('sections.teamDescription')}>
220
- {project.assignments.length > 0 ? (
221
- <div className="overflow-x-auto rounded-md border">
222
- <Table>
223
- <TableHeader>
224
- <TableRow>
225
- <TableHead>{commonT('labels.collaborator')}</TableHead>
226
- <TableHead>{commonT('labels.role')}</TableHead>
227
- <TableHead>{commonT('labels.weeklyCapacity')}</TableHead>
228
- <TableHead>{commonT('labels.status')}</TableHead>
229
- </TableRow>
230
- </TableHeader>
231
- <TableBody>
232
- {project.assignments.map((assignment) => (
233
- <TableRow key={assignment.id}>
234
- <TableCell>{assignment.collaboratorName}</TableCell>
235
- <TableCell>
236
- {assignment.roleLabel || commonT('labels.notAssigned')}
237
- </TableCell>
238
- <TableCell>
239
- {assignment.weeklyHours
240
- ? formatHours(assignment.weeklyHours)
241
- : commonT('labels.notAvailable')}
242
- </TableCell>
243
- <TableCell>
244
- <StatusBadge
245
- label={formatEnumLabel(assignment.status)}
246
- className={getStatusBadgeClass(assignment.status)}
247
- />
248
- </TableCell>
249
- </TableRow>
250
- ))}
251
- </TableBody>
252
- </Table>
253
- </div>
254
- ) : (
255
- <p className="text-sm text-muted-foreground">{t('noAssignments')}</p>
256
- )}
257
- </SectionCard>
258
-
259
- <SectionCard
260
- title={t('sections.indicators')}
261
- description={t('sections.indicatorsDescription')}
262
- >
263
- <dl className="grid gap-3 text-sm md:grid-cols-2">
264
- <div>
265
- <dt className="text-muted-foreground">{t('indicators.activeAssignments')}</dt>
266
- <dd className="font-medium">{project.operationalIndicators.activeAssignments}</dd>
267
- </div>
268
- <div>
269
- <dt className="text-muted-foreground">{t('indicators.billableAssignments')}</dt>
270
- <dd className="font-medium">
271
- {project.operationalIndicators.billableAssignments}
272
- </dd>
273
- </div>
274
- <div>
275
- <dt className="text-muted-foreground">{t('indicators.averageAllocation')}</dt>
276
- <dd className="font-medium">
277
- {formatPercent(project.operationalIndicators.averageAllocation)}
278
- </dd>
279
- </div>
280
- <div>
281
- <dt className="text-muted-foreground">{t('indicators.totalWeeklyHours')}</dt>
282
- <dd className="font-medium">
283
- {formatHours(project.operationalIndicators.totalWeeklyHours)}
284
- </dd>
285
- </div>
286
- </dl>
287
- </SectionCard>
288
- </div>
289
- </Page>
290
- );
291
- }
1
+ 'use client';
2
+
3
+ import { EmptyState, Page } from '@/components/entity-list';
4
+ import { Button } from '@/components/ui/button';
5
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
6
+ import {
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableHead,
11
+ TableHeader,
12
+ TableRow,
13
+ } from '@/components/ui/table';
14
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
15
+ import { FileText, FolderKanban, Pencil } from 'lucide-react';
16
+ import Link from 'next/link';
17
+ import { useTranslations } from 'next-intl';
18
+ import { OperationsHeader } from './operations-header';
19
+ import { SectionCard } from './section-card';
20
+ import { StatusBadge } from './status-badge';
21
+ import { fetchOperations } from '../_lib/api';
22
+ import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
23
+ import type { OperationsProjectDetails } from '../_lib/types';
24
+ import {
25
+ formatCurrency,
26
+ formatDate,
27
+ formatDateRange,
28
+ formatEnumLabel,
29
+ formatHours,
30
+ formatPercent,
31
+ getStatusBadgeClass,
32
+ } from '../_lib/utils/format';
33
+
34
+ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
35
+ const t = useTranslations('operations.ProjectDetailsPage');
36
+ const commonT = useTranslations('operations.Common');
37
+ const { request, currentLocaleCode } = useApp();
38
+ const access = useOperationsAccess();
39
+
40
+ const { data: project, refetch } = useQuery<OperationsProjectDetails>({
41
+ queryKey: ['operations-project-details', currentLocaleCode, projectId],
42
+ queryFn: () =>
43
+ fetchOperations<OperationsProjectDetails>(request, `/operations/projects/${projectId}`),
44
+ });
45
+
46
+ if (!project) {
47
+ return (
48
+ <Page>
49
+ <OperationsHeader
50
+ title={t('title')}
51
+ description={t('description')}
52
+ current={t('breadcrumb')}
53
+ />
54
+ <EmptyState
55
+ icon={<FolderKanban className="size-12" />}
56
+ title={commonT('states.emptyTitle')}
57
+ description={t('notFound')}
58
+ actionLabel={commonT('actions.refresh')}
59
+ onAction={() => void refetch()}
60
+ />
61
+ </Page>
62
+ );
63
+ }
64
+
65
+ const cards = [
66
+ {
67
+ key: 'teamSize',
68
+ title: t('cards.teamSize'),
69
+ value: project.teamSize ?? 0,
70
+ description: t('cards.teamSizeDescription'),
71
+ },
72
+ {
73
+ key: 'timesheets',
74
+ title: t('cards.timesheets'),
75
+ value: project.timesheetSummary.totalTimesheets,
76
+ description: t('cards.timesheetsDescription', {
77
+ pending: project.timesheetSummary.pendingTimesheets,
78
+ }),
79
+ },
80
+ {
81
+ key: 'hours',
82
+ title: t('cards.loggedHours'),
83
+ value: formatHours(project.timesheetSummary.totalHours),
84
+ description: t('cards.loggedHoursDescription'),
85
+ },
86
+ {
87
+ key: 'allocation',
88
+ title: t('cards.allocation'),
89
+ value: formatPercent(project.operationalIndicators.averageAllocation),
90
+ description: t('cards.allocationDescription'),
91
+ },
92
+ ];
93
+
94
+ return (
95
+ <Page>
96
+ <OperationsHeader
97
+ title={project.name}
98
+ description={t('description')}
99
+ current={t('breadcrumb')}
100
+ actions={
101
+ access.isDirector ? (
102
+ <div className="flex gap-2">
103
+ {project.contractId ? (
104
+ <Button variant="outline" size="sm" asChild>
105
+ <Link href={`/operations/contracts?edit=${project.contractId}`}>
106
+ <FileText className="size-4" />
107
+ {commonT('actions.openContract')}
108
+ </Link>
109
+ </Button>
110
+ ) : null}
111
+ <Button size="sm" asChild>
112
+ <Link href={`/operations/projects/${project.id}/edit`}>
113
+ <Pencil className="size-4" />
114
+ {commonT('actions.edit')}
115
+ </Link>
116
+ </Button>
117
+ </div>
118
+ ) : undefined
119
+ }
120
+ />
121
+
122
+ <div className="grid gap-4 xl:grid-cols-4">
123
+ <SectionCard title={t('sections.overview')} className="xl:col-span-2">
124
+ <dl className="grid gap-3 text-sm md:grid-cols-2">
125
+ <div>
126
+ <dt className="text-muted-foreground">{commonT('labels.project')}</dt>
127
+ <dd className="font-medium">{project.name}</dd>
128
+ </div>
129
+ <div>
130
+ <dt className="text-muted-foreground">{commonT('labels.client')}</dt>
131
+ <dd className="font-medium">
132
+ {project.clientName || commonT('labels.notAvailable')}
133
+ </dd>
134
+ </div>
135
+ <div>
136
+ <dt className="text-muted-foreground">{commonT('labels.manager')}</dt>
137
+ <dd className="font-medium">
138
+ {project.managerName || commonT('labels.notAssigned')}
139
+ </dd>
140
+ </div>
141
+ <div>
142
+ <dt className="text-muted-foreground">{commonT('labels.status')}</dt>
143
+ <dd className="font-medium">
144
+ <StatusBadge
145
+ label={formatEnumLabel(project.status)}
146
+ className={getStatusBadgeClass(project.status)}
147
+ />
148
+ </dd>
149
+ </div>
150
+ <div>
151
+ <dt className="text-muted-foreground">{commonT('labels.startDate')}</dt>
152
+ <dd className="font-medium">{formatDate(project.startDate)}</dd>
153
+ </div>
154
+ <div>
155
+ <dt className="text-muted-foreground">{commonT('labels.endDate')}</dt>
156
+ <dd className="font-medium">{formatDate(project.endDate)}</dd>
157
+ </div>
158
+ <div>
159
+ <dt className="text-muted-foreground">{commonT('labels.budget')}</dt>
160
+ <dd className="font-medium">
161
+ {project.budgetAmount
162
+ ? formatCurrency(project.budgetAmount)
163
+ : commonT('labels.notAvailable')}
164
+ </dd>
165
+ </div>
166
+ <div>
167
+ <dt className="text-muted-foreground">{commonT('labels.progress')}</dt>
168
+ <dd className="font-medium">{formatPercent(project.progressPercent)}</dd>
169
+ </div>
170
+ </dl>
171
+ {project.summary ? (
172
+ <p className="mt-4 text-sm text-muted-foreground">{project.summary}</p>
173
+ ) : null}
174
+ </SectionCard>
175
+
176
+ <SectionCard title={t('sections.contract')} className="xl:col-span-2">
177
+ {project.relatedContract ? (
178
+ <div className="space-y-3">
179
+ <div className="flex items-center justify-between rounded-lg border px-4 py-3">
180
+ <div>
181
+ <div className="font-medium">{project.relatedContract.name}</div>
182
+ <div className="text-sm text-muted-foreground">
183
+ {project.relatedContract.code} •{' '}
184
+ {formatEnumLabel(project.relatedContract.contractCategory)}
185
+ </div>
186
+ </div>
187
+ <StatusBadge
188
+ label={formatEnumLabel(project.relatedContract.status)}
189
+ className={getStatusBadgeClass(project.relatedContract.status)}
190
+ />
191
+ </div>
192
+ <dl className="grid gap-3 text-sm md:grid-cols-2">
193
+ <div>
194
+ <dt className="text-muted-foreground">{commonT('labels.billingModel')}</dt>
195
+ <dd className="font-medium">
196
+ {formatEnumLabel(project.relatedContract.billingModel)}
197
+ </dd>
198
+ </div>
199
+ <div>
200
+ <dt className="text-muted-foreground">{commonT('labels.timeline')}</dt>
201
+ <dd className="font-medium">
202
+ {formatDateRange(
203
+ project.relatedContract.startDate,
204
+ project.relatedContract.endDate
205
+ )}
206
+ </dd>
207
+ </div>
208
+ </dl>
209
+ </div>
210
+ ) : (
211
+ <p className="text-sm text-muted-foreground">{t('noContract')}</p>
212
+ )}
213
+ </SectionCard>
214
+ </div>
215
+
216
+ <KpiCardsGrid items={cards} />
217
+
218
+ <div className="grid gap-4 xl:grid-cols-2">
219
+ <SectionCard title={t('sections.team')} description={t('sections.teamDescription')}>
220
+ {project.assignments.length > 0 ? (
221
+ <div className="overflow-x-auto rounded-md border">
222
+ <Table>
223
+ <TableHeader>
224
+ <TableRow>
225
+ <TableHead>{commonT('labels.collaborator')}</TableHead>
226
+ <TableHead>{commonT('labels.role')}</TableHead>
227
+ <TableHead>{commonT('labels.weeklyCapacity')}</TableHead>
228
+ <TableHead>{commonT('labels.status')}</TableHead>
229
+ </TableRow>
230
+ </TableHeader>
231
+ <TableBody>
232
+ {project.assignments.map((assignment) => (
233
+ <TableRow key={assignment.id}>
234
+ <TableCell>{assignment.collaboratorName}</TableCell>
235
+ <TableCell>
236
+ {assignment.roleLabel || commonT('labels.notAssigned')}
237
+ </TableCell>
238
+ <TableCell>
239
+ {assignment.weeklyHours
240
+ ? formatHours(assignment.weeklyHours)
241
+ : commonT('labels.notAvailable')}
242
+ </TableCell>
243
+ <TableCell>
244
+ <StatusBadge
245
+ label={formatEnumLabel(assignment.status)}
246
+ className={getStatusBadgeClass(assignment.status)}
247
+ />
248
+ </TableCell>
249
+ </TableRow>
250
+ ))}
251
+ </TableBody>
252
+ </Table>
253
+ </div>
254
+ ) : (
255
+ <p className="text-sm text-muted-foreground">{t('noAssignments')}</p>
256
+ )}
257
+ </SectionCard>
258
+
259
+ <SectionCard
260
+ title={t('sections.indicators')}
261
+ description={t('sections.indicatorsDescription')}
262
+ >
263
+ <dl className="grid gap-3 text-sm md:grid-cols-2">
264
+ <div>
265
+ <dt className="text-muted-foreground">{t('indicators.activeAssignments')}</dt>
266
+ <dd className="font-medium">{project.operationalIndicators.activeAssignments}</dd>
267
+ </div>
268
+ <div>
269
+ <dt className="text-muted-foreground">{t('indicators.billableAssignments')}</dt>
270
+ <dd className="font-medium">
271
+ {project.operationalIndicators.billableAssignments}
272
+ </dd>
273
+ </div>
274
+ <div>
275
+ <dt className="text-muted-foreground">{t('indicators.averageAllocation')}</dt>
276
+ <dd className="font-medium">
277
+ {formatPercent(project.operationalIndicators.averageAllocation)}
278
+ </dd>
279
+ </div>
280
+ <div>
281
+ <dt className="text-muted-foreground">{t('indicators.totalWeeklyHours')}</dt>
282
+ <dd className="font-medium">
283
+ {formatHours(project.operationalIndicators.totalWeeklyHours)}
284
+ </dd>
285
+ </div>
286
+ </dl>
287
+ </SectionCard>
288
+ </div>
289
+ </Page>
290
+ );
291
+ }