@hed-hog/operations 0.0.303 → 0.0.305

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 (178) hide show
  1. package/README.md +200 -43
  2. package/dist/controllers/operations-approvals.controller.d.ts +9 -0
  3. package/dist/controllers/operations-approvals.controller.d.ts.map +1 -0
  4. package/dist/controllers/operations-approvals.controller.js +64 -0
  5. package/dist/controllers/operations-approvals.controller.js.map +1 -0
  6. package/dist/controllers/operations-collaborators.controller.d.ts +223 -0
  7. package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -0
  8. package/dist/controllers/operations-collaborators.controller.js +96 -0
  9. package/dist/controllers/operations-collaborators.controller.js.map +1 -0
  10. package/dist/controllers/operations-contracts.controller.d.ts +683 -0
  11. package/dist/controllers/operations-contracts.controller.d.ts.map +1 -0
  12. package/dist/controllers/operations-contracts.controller.js +198 -0
  13. package/dist/controllers/operations-contracts.controller.js.map +1 -0
  14. package/dist/controllers/operations-org-structure.controller.d.ts +108 -0
  15. package/dist/controllers/operations-org-structure.controller.d.ts.map +1 -0
  16. package/dist/controllers/operations-org-structure.controller.js +143 -0
  17. package/dist/controllers/operations-org-structure.controller.js.map +1 -0
  18. package/dist/controllers/operations-projects.controller.d.ts +184 -0
  19. package/dist/controllers/operations-projects.controller.d.ts.map +1 -0
  20. package/dist/controllers/operations-projects.controller.js +87 -0
  21. package/dist/controllers/operations-projects.controller.js.map +1 -0
  22. package/dist/controllers/operations-tasks.controller.d.ts +85 -0
  23. package/dist/controllers/operations-tasks.controller.d.ts.map +1 -0
  24. package/dist/controllers/operations-tasks.controller.js +90 -0
  25. package/dist/controllers/operations-tasks.controller.js.map +1 -0
  26. package/dist/controllers/operations-timesheets.controller.d.ts +99 -0
  27. package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -0
  28. package/dist/controllers/operations-timesheets.controller.js +154 -0
  29. package/dist/controllers/operations-timesheets.controller.js.map +1 -0
  30. package/dist/dto/create-collaborator-type.dto.d.ts +10 -0
  31. package/dist/dto/create-collaborator-type.dto.d.ts.map +1 -0
  32. package/dist/dto/create-collaborator-type.dto.js +56 -0
  33. package/dist/dto/create-collaborator-type.dto.js.map +1 -0
  34. package/dist/dto/create-collaborator.dto.d.ts +42 -0
  35. package/dist/dto/create-collaborator.dto.d.ts.map +1 -0
  36. package/dist/dto/create-collaborator.dto.js +228 -0
  37. package/dist/dto/create-collaborator.dto.js.map +1 -0
  38. package/dist/dto/create-schedule-adjustment-request.dto.d.ts +17 -0
  39. package/dist/dto/create-schedule-adjustment-request.dto.d.ts.map +1 -0
  40. package/dist/dto/create-schedule-adjustment-request.dto.js +89 -0
  41. package/dist/dto/create-schedule-adjustment-request.dto.js.map +1 -0
  42. package/dist/dto/create-task.dto.d.ts +14 -0
  43. package/dist/dto/create-task.dto.d.ts.map +1 -0
  44. package/dist/dto/create-task.dto.js +83 -0
  45. package/dist/dto/create-task.dto.js.map +1 -0
  46. package/dist/dto/create-time-off-request.dto.d.ts +9 -0
  47. package/dist/dto/create-time-off-request.dto.d.ts.map +1 -0
  48. package/dist/dto/create-time-off-request.dto.js +54 -0
  49. package/dist/dto/create-time-off-request.dto.js.map +1 -0
  50. package/dist/dto/create-timesheet-entry.dto.d.ts +12 -0
  51. package/dist/dto/create-timesheet-entry.dto.d.ts.map +1 -0
  52. package/dist/dto/create-timesheet-entry.dto.js +75 -0
  53. package/dist/dto/create-timesheet-entry.dto.js.map +1 -0
  54. package/dist/dto/list-collaborator-types.dto.d.ts +4 -0
  55. package/dist/dto/list-collaborator-types.dto.d.ts.map +1 -0
  56. package/dist/dto/list-collaborator-types.dto.js +29 -0
  57. package/dist/dto/list-collaborator-types.dto.js.map +1 -0
  58. package/dist/dto/list-collaborators.dto.d.ts +8 -0
  59. package/dist/dto/list-collaborators.dto.d.ts.map +1 -0
  60. package/dist/dto/list-collaborators.dto.js +42 -0
  61. package/dist/dto/list-collaborators.dto.js.map +1 -0
  62. package/dist/dto/list-project-options.dto.d.ts +4 -0
  63. package/dist/dto/list-project-options.dto.d.ts.map +1 -0
  64. package/dist/dto/list-project-options.dto.js +8 -0
  65. package/dist/dto/list-project-options.dto.js.map +1 -0
  66. package/dist/dto/list-tasks.dto.d.ts +7 -0
  67. package/dist/dto/list-tasks.dto.d.ts.map +1 -0
  68. package/dist/dto/list-tasks.dto.js +38 -0
  69. package/dist/dto/list-tasks.dto.js.map +1 -0
  70. package/dist/dto/list-timesheet-entries.dto.d.ts +10 -0
  71. package/dist/dto/list-timesheet-entries.dto.d.ts.map +1 -0
  72. package/dist/dto/list-timesheet-entries.dto.js +54 -0
  73. package/dist/dto/list-timesheet-entries.dto.js.map +1 -0
  74. package/dist/dto/update-collaborator-type.dto.d.ts +4 -0
  75. package/dist/dto/update-collaborator-type.dto.d.ts.map +1 -0
  76. package/dist/dto/update-collaborator-type.dto.js +8 -0
  77. package/dist/dto/update-collaborator-type.dto.js.map +1 -0
  78. package/dist/dto/update-collaborator.dto.d.ts +4 -0
  79. package/dist/dto/update-collaborator.dto.d.ts.map +1 -0
  80. package/dist/dto/update-collaborator.dto.js +8 -0
  81. package/dist/dto/update-collaborator.dto.js.map +1 -0
  82. package/dist/dto/update-task.dto.d.ts +14 -0
  83. package/dist/dto/update-task.dto.d.ts.map +1 -0
  84. package/dist/dto/update-task.dto.js +84 -0
  85. package/dist/dto/update-task.dto.js.map +1 -0
  86. package/dist/operations.controller.d.ts +0 -1045
  87. package/dist/operations.controller.d.ts.map +1 -1
  88. package/dist/operations.controller.js +0 -429
  89. package/dist/operations.controller.js.map +1 -1
  90. package/dist/operations.module.d.ts.map +1 -1
  91. package/dist/operations.module.js +23 -2
  92. package/dist/operations.module.js.map +1 -1
  93. package/dist/operations.service.d.ts +429 -8
  94. package/dist/operations.service.d.ts.map +1 -1
  95. package/dist/operations.service.js +1931 -165
  96. package/dist/operations.service.js.map +1 -1
  97. package/dist/operations.service.spec.js +315 -1
  98. package/dist/operations.service.spec.js.map +1 -1
  99. package/dist/services/shared/operations-access.service.d.ts +16 -0
  100. package/dist/services/shared/operations-access.service.d.ts.map +1 -0
  101. package/dist/services/shared/operations-access.service.js +48 -0
  102. package/dist/services/shared/operations-access.service.js.map +1 -0
  103. package/hedhog/data/dashboard.yaml +20 -0
  104. package/hedhog/data/dashboard_component.yaml +274 -0
  105. package/hedhog/data/dashboard_component_role.yaml +174 -0
  106. package/hedhog/data/dashboard_item.yaml +299 -0
  107. package/hedhog/data/dashboard_role.yaml +20 -0
  108. package/hedhog/data/menu.yaml +30 -13
  109. package/hedhog/data/operations_collaborator_type.yaml +76 -0
  110. package/hedhog/data/route.yaml +196 -0
  111. package/hedhog/frontend/app/_components/async-options-combobox.tsx.ejs +231 -0
  112. package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +125 -40
  113. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +740 -106
  114. package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +256 -256
  115. package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +7 -7
  116. package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +306 -306
  117. package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +247 -247
  118. package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +3520 -3520
  119. package/hedhog/frontend/app/_components/department-select-with-create.tsx.ejs +38 -16
  120. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +1504 -52
  121. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +1017 -649
  122. package/hedhog/frontend/app/_components/section-card.tsx.ejs +25 -18
  123. package/hedhog/frontend/app/_components/system-user-select-with-create.tsx.ejs +609 -0
  124. package/hedhog/frontend/app/_components/timesheet-task-create-sheet.tsx.ejs +213 -0
  125. package/hedhog/frontend/app/_lib/api.ts.ejs +30 -1
  126. package/hedhog/frontend/app/_lib/types.ts.ejs +147 -39
  127. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +40 -9
  128. package/hedhog/frontend/app/_lib/utils/forms.ts.ejs +48 -1
  129. package/hedhog/frontend/app/approvals/page.tsx.ejs +116 -98
  130. package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +502 -0
  131. package/hedhog/frontend/app/collaborators/page.tsx.ejs +116 -72
  132. package/hedhog/frontend/app/contracts/page.tsx.ejs +938 -938
  133. package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +11 -9
  134. package/hedhog/frontend/app/departments/page.tsx.ejs +1 -1
  135. package/hedhog/frontend/app/projects/[id]/edit/page.tsx.ejs +1 -1
  136. package/hedhog/frontend/app/projects/page.tsx.ejs +364 -133
  137. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +244 -120
  138. package/hedhog/frontend/app/team/page.tsx.ejs +15 -2
  139. package/hedhog/frontend/app/time-off/page.tsx.ejs +158 -82
  140. package/hedhog/frontend/app/timesheets/page.tsx.ejs +814 -357
  141. package/hedhog/frontend/messages/en.json +268 -53
  142. package/hedhog/frontend/messages/pt.json +484 -271
  143. package/hedhog/table/operations_collaborator.yaml +26 -13
  144. package/hedhog/table/operations_collaborator_equity_participation.yaml +43 -0
  145. package/hedhog/table/operations_collaborator_type.yaml +33 -0
  146. package/hedhog/table/operations_job_title.yaml +24 -0
  147. package/hedhog/table/operations_project.yaml +9 -0
  148. package/hedhog/table/operations_project_assignment.yaml +9 -0
  149. package/hedhog/table/operations_project_role.yaml +39 -0
  150. package/hedhog/table/operations_task.yaml +69 -0
  151. package/hedhog/table/operations_timesheet_entry.yaml +12 -0
  152. package/package.json +6 -6
  153. package/src/controllers/operations-approvals.controller.ts +24 -0
  154. package/src/controllers/operations-collaborators.controller.ts +60 -0
  155. package/src/controllers/operations-contracts.controller.ts +138 -0
  156. package/src/controllers/operations-org-structure.controller.ts +92 -0
  157. package/src/controllers/operations-projects.controller.ts +50 -0
  158. package/src/controllers/operations-tasks.controller.ts +63 -0
  159. package/src/controllers/operations-timesheets.controller.ts +100 -0
  160. package/src/dto/create-collaborator-type.dto.ts +43 -0
  161. package/src/dto/create-collaborator.dto.ts +223 -0
  162. package/src/dto/create-schedule-adjustment-request.dto.ts +91 -0
  163. package/src/dto/create-task.dto.ts +75 -0
  164. package/src/dto/create-time-off-request.dto.ts +53 -0
  165. package/src/dto/create-timesheet-entry.dto.ts +67 -0
  166. package/src/dto/list-collaborator-types.dto.ts +15 -0
  167. package/src/dto/list-collaborators.dto.ts +30 -0
  168. package/src/dto/list-project-options.dto.ts +3 -0
  169. package/src/dto/list-tasks.dto.ts +25 -0
  170. package/src/dto/list-timesheet-entries.dto.ts +40 -0
  171. package/src/dto/update-collaborator-type.dto.ts +3 -0
  172. package/src/dto/update-collaborator.dto.ts +3 -0
  173. package/src/dto/update-task.dto.ts +76 -0
  174. package/src/operations.controller.ts +1 -278
  175. package/src/operations.module.ts +23 -2
  176. package/src/operations.service.spec.ts +450 -0
  177. package/src/operations.service.ts +4507 -1561
  178. package/src/services/shared/operations-access.service.ts +52 -0
@@ -0,0 +1,502 @@
1
+ 'use client';
2
+
3
+ import { EmptyState, Page, SearchBar } from '@/components/entity-list';
4
+ import { Button } from '@/components/ui/button';
5
+ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
6
+ import {
7
+ Sheet,
8
+ SheetContent,
9
+ SheetDescription,
10
+ SheetHeader,
11
+ SheetTitle,
12
+ } from '@/components/ui/sheet';
13
+ import {
14
+ Table,
15
+ TableBody,
16
+ TableCell,
17
+ TableHead,
18
+ TableHeader,
19
+ TableRow,
20
+ } from '@/components/ui/table';
21
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
22
+ import { Briefcase, Pencil, Users } from 'lucide-react';
23
+ import { useTranslations } from 'next-intl';
24
+ import Link from 'next/link';
25
+ import { useMemo, useState } from 'react';
26
+ import { OperationsHeader } from '../_components/operations-header';
27
+ import { StatusBadge } from '../_components/status-badge';
28
+ import { fetchOperations, mutateOperations } from '../_lib/api';
29
+ import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
30
+ import type { OperationsCollaboratorType } from '../_lib/types';
31
+ import { formatEnumLabel, getStatusBadgeClass } from '../_lib/utils/format';
32
+
33
+ type CollaboratorTypeFormState = {
34
+ name: string;
35
+ slug: string;
36
+ description: string;
37
+ category: string;
38
+ sortOrder: string;
39
+ status: 'active' | 'inactive';
40
+ };
41
+
42
+ const EMPTY_FORM_STATE: CollaboratorTypeFormState = {
43
+ name: '',
44
+ slug: '',
45
+ description: '',
46
+ category: '',
47
+ sortOrder: '0',
48
+ status: 'active',
49
+ };
50
+
51
+ export default function OperationsCollaboratorTypesPage() {
52
+ const t = useTranslations('operations.CollaboratorTypesPage');
53
+ const commonT = useTranslations('operations.Common');
54
+ const { request, showToastHandler, currentLocaleCode } = useApp();
55
+ const access = useOperationsAccess();
56
+ const [search, setSearch] = useState('');
57
+ const [statusFilter, setStatusFilter] = useState<'all' | 'active' | 'inactive'>(
58
+ 'all'
59
+ );
60
+ const [isSheetOpen, setIsSheetOpen] = useState(false);
61
+ const [isSaving, setIsSaving] = useState(false);
62
+ const [editingType, setEditingType] = useState<OperationsCollaboratorType | null>(
63
+ null
64
+ );
65
+ const [form, setForm] = useState<CollaboratorTypeFormState>(EMPTY_FORM_STATE);
66
+
67
+ const {
68
+ data: collaboratorTypes = [],
69
+ isLoading,
70
+ refetch,
71
+ } = useQuery<OperationsCollaboratorType[]>({
72
+ queryKey: ['operations-collaborator-types-page', currentLocaleCode],
73
+ enabled: access.isDirector,
74
+ queryFn: () =>
75
+ fetchOperations<OperationsCollaboratorType[]>(
76
+ request,
77
+ '/operations/collaborator-types'
78
+ ),
79
+ });
80
+
81
+ const filteredTypes = useMemo(
82
+ () =>
83
+ collaboratorTypes.filter((collaboratorType) => {
84
+ const matchesSearch = !search.trim()
85
+ ? true
86
+ : [
87
+ collaboratorType.name,
88
+ collaboratorType.slug,
89
+ collaboratorType.category,
90
+ collaboratorType.description,
91
+ ]
92
+ .filter(Boolean)
93
+ .some((value) =>
94
+ String(value)
95
+ .toLowerCase()
96
+ .includes(search.trim().toLowerCase())
97
+ );
98
+
99
+ const matchesStatus =
100
+ statusFilter === 'all' ? true : collaboratorType.status === statusFilter;
101
+
102
+ return matchesSearch && matchesStatus;
103
+ }),
104
+ [collaboratorTypes, search, statusFilter]
105
+ );
106
+
107
+ const statsCards = useMemo(
108
+ () => [
109
+ {
110
+ key: 'total',
111
+ title: t('cards.total'),
112
+ value: collaboratorTypes.length,
113
+ icon: Briefcase,
114
+ },
115
+ {
116
+ key: 'active',
117
+ title: t('cards.active'),
118
+ value: collaboratorTypes.filter((item) => item.status === 'active').length,
119
+ icon: Briefcase,
120
+ },
121
+ {
122
+ key: 'linkedCollaborators',
123
+ title: t('cards.linkedCollaborators'),
124
+ value: collaboratorTypes.reduce(
125
+ (total, item) => total + Number(item.collaboratorCount ?? 0),
126
+ 0
127
+ ),
128
+ icon: Users,
129
+ },
130
+ {
131
+ key: 'inactive',
132
+ title: t('cards.inactive'),
133
+ value: collaboratorTypes.filter((item) => item.status === 'inactive').length,
134
+ icon: Briefcase,
135
+ },
136
+ ],
137
+ [collaboratorTypes, t]
138
+ );
139
+
140
+ const updateForm = <K extends keyof CollaboratorTypeFormState>(
141
+ field: K,
142
+ value: CollaboratorTypeFormState[K]
143
+ ) => {
144
+ setForm((current) => ({
145
+ ...current,
146
+ [field]: value,
147
+ }));
148
+ };
149
+
150
+ const openCreateSheet = () => {
151
+ setEditingType(null);
152
+ setForm(EMPTY_FORM_STATE);
153
+ setIsSheetOpen(true);
154
+ };
155
+
156
+ const openEditSheet = (collaboratorType: OperationsCollaboratorType) => {
157
+ setEditingType(collaboratorType);
158
+ setForm({
159
+ name: collaboratorType.name ?? '',
160
+ slug: collaboratorType.slug ?? '',
161
+ description: collaboratorType.description ?? '',
162
+ category: collaboratorType.category ?? '',
163
+ sortOrder: String(collaboratorType.sortOrder ?? 0),
164
+ status: collaboratorType.status ?? 'active',
165
+ });
166
+ setIsSheetOpen(true);
167
+ };
168
+
169
+ const saveCollaboratorType = async () => {
170
+ if (!form.name.trim()) {
171
+ showToastHandler?.('error', t('messages.requiredFields'));
172
+ return;
173
+ }
174
+
175
+ setIsSaving(true);
176
+
177
+ try {
178
+ await mutateOperations(
179
+ request,
180
+ editingType
181
+ ? `/operations/collaborator-types/${editingType.id}`
182
+ : '/operations/collaborator-types',
183
+ editingType ? 'PATCH' : 'POST',
184
+ {
185
+ name: form.name.trim(),
186
+ slug: form.slug.trim() || null,
187
+ description: form.description.trim() || null,
188
+ category: form.category.trim() || null,
189
+ sortOrder: Number(form.sortOrder || '0'),
190
+ status: form.status,
191
+ isActive: form.status === 'active',
192
+ }
193
+ );
194
+
195
+ showToastHandler?.('success', t('messages.saveSuccess'));
196
+ setIsSheetOpen(false);
197
+ setEditingType(null);
198
+ setForm(EMPTY_FORM_STATE);
199
+ await refetch();
200
+ } catch {
201
+ showToastHandler?.('error', t('messages.saveError'));
202
+ } finally {
203
+ setIsSaving(false);
204
+ }
205
+ };
206
+
207
+ const toggleStatus = async (collaboratorType: OperationsCollaboratorType) => {
208
+ const nextStatus =
209
+ collaboratorType.status === 'inactive' ? 'active' : 'inactive';
210
+
211
+ try {
212
+ await mutateOperations(
213
+ request,
214
+ `/operations/collaborator-types/${collaboratorType.id}`,
215
+ 'PATCH',
216
+ { status: nextStatus, isActive: nextStatus === 'active' }
217
+ );
218
+ showToastHandler?.('success', t('messages.statusSuccess'));
219
+ await refetch();
220
+ } catch {
221
+ showToastHandler?.('error', t('messages.statusError'));
222
+ }
223
+ };
224
+
225
+ if (!access.isLoading && !access.isDirector) {
226
+ return (
227
+ <Page>
228
+ <OperationsHeader
229
+ title={t('title')}
230
+ description={t('description')}
231
+ current={t('breadcrumb')}
232
+ actions={
233
+ <Button variant="outline" size="sm" asChild>
234
+ <Link href="/operations/collaborators">
235
+ {commonT('actions.back')}
236
+ </Link>
237
+ </Button>
238
+ }
239
+ />
240
+
241
+ <EmptyState
242
+ icon={<Briefcase className="size-12" />}
243
+ title={commonT('states.noAccessTitle')}
244
+ description={t('noAccessDescription')}
245
+ actionLabel={commonT('actions.back')}
246
+ onAction={() => {
247
+ window.location.href = '/operations';
248
+ }}
249
+ />
250
+ </Page>
251
+ );
252
+ }
253
+
254
+ return (
255
+ <Page>
256
+ <OperationsHeader
257
+ title={t('title')}
258
+ description={t('description')}
259
+ current={t('breadcrumb')}
260
+ actions={
261
+ <div className="flex flex-wrap gap-2">
262
+ <Button variant="outline" size="sm" asChild>
263
+ <Link href="/operations/collaborators">
264
+ {commonT('actions.back')}
265
+ </Link>
266
+ </Button>
267
+ <Button size="sm" onClick={openCreateSheet}>
268
+ {commonT('actions.create')}
269
+ </Button>
270
+ </div>
271
+ }
272
+ />
273
+
274
+ <KpiCardsGrid items={statsCards} columns={4} />
275
+
276
+ <div className="flex flex-col gap-4">
277
+ <SearchBar
278
+ searchQuery={search}
279
+ onSearchChange={setSearch}
280
+ onSearch={() => undefined}
281
+ placeholder={t('searchPlaceholder')}
282
+ controls={[
283
+ {
284
+ id: 'status',
285
+ type: 'select',
286
+ value: statusFilter,
287
+ onChange: (value) =>
288
+ setStatusFilter(
289
+ (value as 'all' | 'active' | 'inactive') ?? 'all'
290
+ ),
291
+ placeholder: commonT('labels.status'),
292
+ options: [
293
+ { value: 'all', label: commonT('filters.allStatuses') },
294
+ { value: 'active', label: formatEnumLabel('active') },
295
+ { value: 'inactive', label: formatEnumLabel('inactive') },
296
+ ],
297
+ },
298
+ ]}
299
+ />
300
+ </div>
301
+
302
+ {isLoading ? (
303
+ <div className="rounded-md border px-4 py-6 text-sm text-muted-foreground">
304
+ {commonT('actions.refresh')}...
305
+ </div>
306
+ ) : filteredTypes.length > 0 ? (
307
+ <div className="overflow-x-auto rounded-md border">
308
+ <Table>
309
+ <TableHeader>
310
+ <TableRow>
311
+ <TableHead>{t('columns.name')}</TableHead>
312
+ <TableHead>{t('columns.slug')}</TableHead>
313
+ <TableHead>{t('columns.category')}</TableHead>
314
+ <TableHead>{t('columns.description')}</TableHead>
315
+ <TableHead>{t('columns.sortOrder')}</TableHead>
316
+ <TableHead>{t('columns.collaborators')}</TableHead>
317
+ <TableHead>{commonT('labels.status')}</TableHead>
318
+ <TableHead className="text-right">
319
+ {commonT('labels.actions')}
320
+ </TableHead>
321
+ </TableRow>
322
+ </TableHeader>
323
+ <TableBody>
324
+ {filteredTypes.map((collaboratorType) => (
325
+ <TableRow key={collaboratorType.id}>
326
+ <TableCell className="font-medium">
327
+ {collaboratorType.name}
328
+ </TableCell>
329
+ <TableCell>{collaboratorType.slug}</TableCell>
330
+ <TableCell>
331
+ {collaboratorType.category || commonT('labels.notAvailable')}
332
+ </TableCell>
333
+ <TableCell className="max-w-md text-sm text-muted-foreground">
334
+ {collaboratorType.description || commonT('labels.notAvailable')}
335
+ </TableCell>
336
+ <TableCell>{Number(collaboratorType.sortOrder ?? 0)}</TableCell>
337
+ <TableCell>
338
+ {Number(collaboratorType.collaboratorCount ?? 0)}
339
+ </TableCell>
340
+ <TableCell>
341
+ <StatusBadge
342
+ label={formatEnumLabel(collaboratorType.status)}
343
+ className={getStatusBadgeClass(collaboratorType.status)}
344
+ />
345
+ </TableCell>
346
+ <TableCell>
347
+ <div className="flex justify-end gap-2">
348
+ <Button
349
+ variant="outline"
350
+ size="icon"
351
+ className="cursor-pointer"
352
+ onClick={() => openEditSheet(collaboratorType)}
353
+ >
354
+ <Pencil className="size-4" />
355
+ </Button>
356
+ <Button
357
+ variant="outline"
358
+ size="sm"
359
+ className="cursor-pointer"
360
+ onClick={() => void toggleStatus(collaboratorType)}
361
+ >
362
+ {collaboratorType.status === 'inactive'
363
+ ? commonT('actions.activate')
364
+ : commonT('actions.deactivate')}
365
+ </Button>
366
+ </div>
367
+ </TableCell>
368
+ </TableRow>
369
+ ))}
370
+ </TableBody>
371
+ </Table>
372
+ </div>
373
+ ) : (
374
+ <EmptyState
375
+ icon={<Briefcase className="size-12" />}
376
+ title={commonT('states.emptyTitle')}
377
+ description={t('emptyDescription')}
378
+ actionLabel={commonT('actions.create')}
379
+ onAction={openCreateSheet}
380
+ />
381
+ )}
382
+
383
+ <Sheet open={isSheetOpen} onOpenChange={setIsSheetOpen}>
384
+ <SheetContent className="w-full overflow-y-auto sm:max-w-xl">
385
+ <SheetHeader>
386
+ <SheetTitle>
387
+ {editingType ? t('sheet.editTitle') : t('sheet.createTitle')}
388
+ </SheetTitle>
389
+ <SheetDescription>{t('sheet.description')}</SheetDescription>
390
+ </SheetHeader>
391
+
392
+ <form
393
+ className="mt-6 space-y-4"
394
+ onSubmit={(event) => {
395
+ event.preventDefault();
396
+ void saveCollaboratorType();
397
+ }}
398
+ >
399
+ <div className="space-y-2">
400
+ <label className="text-sm font-medium" htmlFor="type-name">
401
+ {t('form.name')}
402
+ </label>
403
+ <input
404
+ id="type-name"
405
+ value={form.name}
406
+ onChange={(event) => updateForm('name', event.target.value)}
407
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
408
+ placeholder={t('form.name')}
409
+ />
410
+ </div>
411
+
412
+ <div className="space-y-2">
413
+ <label className="text-sm font-medium" htmlFor="type-slug">
414
+ {t('form.slug')}
415
+ </label>
416
+ <input
417
+ id="type-slug"
418
+ value={form.slug}
419
+ onChange={(event) => updateForm('slug', event.target.value)}
420
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
421
+ placeholder={t('form.slug')}
422
+ />
423
+ </div>
424
+
425
+ <div className="space-y-2">
426
+ <label className="text-sm font-medium" htmlFor="type-category">
427
+ {t('form.category')}
428
+ </label>
429
+ <input
430
+ id="type-category"
431
+ value={form.category}
432
+ onChange={(event) => updateForm('category', event.target.value)}
433
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
434
+ placeholder={t('form.category')}
435
+ />
436
+ </div>
437
+
438
+ <div className="space-y-2">
439
+ <label className="text-sm font-medium" htmlFor="type-description">
440
+ {t('form.description')}
441
+ </label>
442
+ <textarea
443
+ id="type-description"
444
+ value={form.description}
445
+ onChange={(event) => updateForm('description', event.target.value)}
446
+ className="min-h-24 w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
447
+ placeholder={t('form.description')}
448
+ />
449
+ </div>
450
+
451
+ <div className="space-y-2">
452
+ <label className="text-sm font-medium" htmlFor="type-sort-order">
453
+ {t('form.sortOrder')}
454
+ </label>
455
+ <input
456
+ id="type-sort-order"
457
+ type="number"
458
+ value={form.sortOrder}
459
+ onChange={(event) => updateForm('sortOrder', event.target.value)}
460
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
461
+ placeholder="0"
462
+ />
463
+ </div>
464
+
465
+ <div className="space-y-2">
466
+ <label className="text-sm font-medium" htmlFor="type-status">
467
+ {t('form.status')}
468
+ </label>
469
+ <select
470
+ id="type-status"
471
+ value={form.status}
472
+ onChange={(event) =>
473
+ updateForm(
474
+ 'status',
475
+ event.target.value === 'inactive' ? 'inactive' : 'active'
476
+ )
477
+ }
478
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
479
+ >
480
+ <option value="active">{formatEnumLabel('active')}</option>
481
+ <option value="inactive">{formatEnumLabel('inactive')}</option>
482
+ </select>
483
+ </div>
484
+
485
+ <div className="flex justify-end gap-2 pt-2">
486
+ <Button
487
+ type="button"
488
+ variant="outline"
489
+ onClick={() => setIsSheetOpen(false)}
490
+ >
491
+ {commonT('actions.cancel')}
492
+ </Button>
493
+ <Button type="submit" disabled={isSaving}>
494
+ {commonT('actions.save')}
495
+ </Button>
496
+ </div>
497
+ </form>
498
+ </SheetContent>
499
+ </Sheet>
500
+ </Page>
501
+ );
502
+ }