@hed-hog/operations 0.0.306 → 0.0.310

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 (123) hide show
  1. package/dist/controllers/operations-approvals.controller.d.ts +114 -1
  2. package/dist/controllers/operations-approvals.controller.d.ts.map +1 -1
  3. package/dist/controllers/operations-approvals.controller.js +16 -3
  4. package/dist/controllers/operations-approvals.controller.js.map +1 -1
  5. package/dist/controllers/operations-collaborators.controller.d.ts +16 -1
  6. package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
  7. package/dist/controllers/operations-collaborators.controller.js +16 -3
  8. package/dist/controllers/operations-collaborators.controller.js.map +1 -1
  9. package/dist/controllers/operations-contracts.controller.d.ts +14 -453
  10. package/dist/controllers/operations-contracts.controller.d.ts.map +1 -1
  11. package/dist/controllers/operations-contracts.controller.js +11 -112
  12. package/dist/controllers/operations-contracts.controller.js.map +1 -1
  13. package/dist/controllers/operations-org-structure.controller.d.ts +65 -2
  14. package/dist/controllers/operations-org-structure.controller.d.ts.map +1 -1
  15. package/dist/controllers/operations-org-structure.controller.js +18 -5
  16. package/dist/controllers/operations-org-structure.controller.js.map +1 -1
  17. package/dist/controllers/operations-projects.controller.d.ts +28 -4
  18. package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
  19. package/dist/controllers/operations-projects.controller.js +17 -5
  20. package/dist/controllers/operations-projects.controller.js.map +1 -1
  21. package/dist/controllers/operations-timesheets.controller.d.ts +31 -4
  22. package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -1
  23. package/dist/controllers/operations-timesheets.controller.js +16 -11
  24. package/dist/controllers/operations-timesheets.controller.js.map +1 -1
  25. package/dist/dto/list-approvals.dto.d.ts +6 -0
  26. package/dist/dto/list-approvals.dto.d.ts.map +1 -0
  27. package/dist/dto/list-approvals.dto.js +28 -0
  28. package/dist/dto/list-approvals.dto.js.map +1 -0
  29. package/dist/dto/list-collaborator-types.dto.d.ts +3 -1
  30. package/dist/dto/list-collaborator-types.dto.d.ts.map +1 -1
  31. package/dist/dto/list-collaborator-types.dto.js +7 -1
  32. package/dist/dto/list-collaborator-types.dto.js.map +1 -1
  33. package/dist/dto/list-collaborators.dto.d.ts +1 -0
  34. package/dist/dto/list-collaborators.dto.d.ts.map +1 -1
  35. package/dist/dto/list-collaborators.dto.js +5 -0
  36. package/dist/dto/list-collaborators.dto.js.map +1 -1
  37. package/dist/dto/list-contracts.dto.d.ts +8 -0
  38. package/dist/dto/list-contracts.dto.d.ts.map +1 -0
  39. package/dist/dto/list-contracts.dto.js +38 -0
  40. package/dist/dto/list-contracts.dto.js.map +1 -0
  41. package/dist/dto/list-departments.dto.d.ts +5 -0
  42. package/dist/dto/list-departments.dto.d.ts.map +1 -0
  43. package/dist/dto/list-departments.dto.js +23 -0
  44. package/dist/dto/list-departments.dto.js.map +1 -0
  45. package/dist/dto/list-projects.dto.d.ts +5 -0
  46. package/dist/dto/list-projects.dto.d.ts.map +1 -0
  47. package/dist/dto/list-projects.dto.js +23 -0
  48. package/dist/dto/list-projects.dto.js.map +1 -0
  49. package/dist/dto/list-schedule-adjustments.dto.d.ts +5 -0
  50. package/dist/dto/list-schedule-adjustments.dto.d.ts.map +1 -0
  51. package/dist/dto/list-schedule-adjustments.dto.js +23 -0
  52. package/dist/dto/list-schedule-adjustments.dto.js.map +1 -0
  53. package/dist/dto/list-time-off-requests.dto.d.ts +5 -0
  54. package/dist/dto/list-time-off-requests.dto.d.ts.map +1 -0
  55. package/dist/dto/list-time-off-requests.dto.js +23 -0
  56. package/dist/dto/list-time-off-requests.dto.js.map +1 -0
  57. package/dist/dto/list-timesheets.dto.d.ts +5 -0
  58. package/dist/dto/list-timesheets.dto.d.ts.map +1 -0
  59. package/dist/dto/list-timesheets.dto.js +23 -0
  60. package/dist/dto/list-timesheets.dto.js.map +1 -0
  61. package/dist/dto/reorder-collaborator-types.dto.d.ts +4 -0
  62. package/dist/dto/reorder-collaborator-types.dto.d.ts.map +1 -0
  63. package/dist/dto/reorder-collaborator-types.dto.js +25 -0
  64. package/dist/dto/reorder-collaborator-types.dto.js.map +1 -0
  65. package/dist/operations.service.d.ts +340 -271
  66. package/dist/operations.service.d.ts.map +1 -1
  67. package/dist/operations.service.js +1007 -1043
  68. package/dist/operations.service.js.map +1 -1
  69. package/dist/operations.service.spec.js +0 -22
  70. package/dist/operations.service.spec.js.map +1 -1
  71. package/hedhog/data/menu.yaml +0 -36
  72. package/hedhog/data/route.yaml +42 -73
  73. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +8 -1
  74. package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +15 -10
  75. package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +108 -213
  76. package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +251 -2039
  77. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +167 -60
  78. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +70 -301
  79. package/hedhog/frontend/app/_components/system-user-select-with-create.tsx.ejs +102 -51
  80. package/hedhog/frontend/app/_lib/types.ts.ejs +19 -24
  81. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +14 -9
  82. package/hedhog/frontend/app/approvals/page.tsx.ejs +842 -150
  83. package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +445 -153
  84. package/hedhog/frontend/app/collaborators/page.tsx.ejs +118 -49
  85. package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +2 -2
  86. package/hedhog/frontend/app/contracts/page.tsx.ejs +215 -617
  87. package/hedhog/frontend/app/departments/page.tsx.ejs +257 -113
  88. package/hedhog/frontend/app/projects/page.tsx.ejs +90 -51
  89. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +412 -147
  90. package/hedhog/frontend/app/time-off/page.tsx.ejs +400 -123
  91. package/hedhog/frontend/app/timesheets/page.tsx.ejs +460 -365
  92. package/hedhog/frontend/messages/en.json +143 -14
  93. package/hedhog/frontend/messages/pt.json +192 -54
  94. package/hedhog/table/operations_contract.yaml +0 -9
  95. package/package.json +4 -4
  96. package/src/controllers/operations-approvals.controller.ts +9 -3
  97. package/src/controllers/operations-collaborators.controller.ts +15 -2
  98. package/src/controllers/operations-contracts.controller.ts +8 -92
  99. package/src/controllers/operations-org-structure.controller.ts +17 -4
  100. package/src/controllers/operations-projects.controller.ts +10 -4
  101. package/src/controllers/operations-timesheets.controller.ts +17 -8
  102. package/src/dto/list-approvals.dto.ts +12 -0
  103. package/src/dto/list-collaborator-types.dto.ts +7 -2
  104. package/src/dto/list-collaborators.dto.ts +4 -0
  105. package/src/dto/list-contracts.dto.ts +20 -0
  106. package/src/dto/list-departments.dto.ts +8 -0
  107. package/src/dto/list-projects.dto.ts +8 -0
  108. package/src/dto/list-schedule-adjustments.dto.ts +8 -0
  109. package/src/dto/list-time-off-requests.dto.ts +8 -0
  110. package/src/dto/list-timesheets.dto.ts +8 -0
  111. package/src/dto/reorder-collaborator-types.dto.ts +10 -0
  112. package/src/operations.service.spec.ts +0 -30
  113. package/src/operations.service.ts +1557 -1806
  114. package/hedhog/frontend/app/_components/contract-creation-wizard.tsx.ejs +0 -631
  115. package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +0 -526
  116. package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +0 -247
  117. package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +0 -3520
  118. package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +0 -380
  119. package/hedhog/frontend/app/team/page.tsx.ejs +0 -352
  120. package/hedhog/table/operations_contract_financial_term.yaml +0 -40
  121. package/hedhog/table/operations_contract_revision.yaml +0 -38
  122. package/hedhog/table/operations_contract_signature.yaml +0 -38
  123. package/hedhog/table/operations_contract_template.yaml +0 -58
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
3
4
  import { Button } from '@/components/ui/button';
4
5
  import {
5
6
  Command,
@@ -26,11 +27,12 @@ import {
26
27
  import { useApp } from '@hed-hog/next-app-provider';
27
28
  import { Check, ChevronsUpDown, Plus, X } from 'lucide-react';
28
29
  import { useTranslations } from 'next-intl';
29
- import { useEffect, useMemo, useRef, useState } from 'react';
30
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
30
31
 
31
32
  export type SystemUserOption = {
32
33
  id: number;
33
34
  name: string;
35
+ photoId?: number | null;
34
36
  };
35
37
 
36
38
  type SystemUserSelectWithCreateProps = {
@@ -50,7 +52,7 @@ type SystemUserSelectWithCreateProps = {
50
52
 
51
53
  type UserListPayload = {
52
54
  paginate: {
53
- data: Array<{ id: number; name: string | null }>;
55
+ data: Array<{ id: number; name: string | null; photo_id?: number | null }>;
54
56
  total: number;
55
57
  lastPage: number;
56
58
  page: number;
@@ -83,12 +85,36 @@ function parseUserListResponse(payload: UserListPayload): {
83
85
  hasMore: boolean;
84
86
  } {
85
87
  const raw = payload?.paginate?.data ?? [];
86
- const items = raw.map((u) => ({ id: u.id, name: u.name || `#${u.id}` }));
88
+ const items = raw.map((u) => ({
89
+ id: u.id,
90
+ name: u.name || `#${u.id}`,
91
+ photoId: u.photo_id ?? null,
92
+ }));
87
93
  const hasMore =
88
94
  (payload?.paginate?.page ?? 1) < (payload?.paginate?.lastPage ?? 1);
89
95
  return { items, hasMore };
90
96
  }
91
97
 
98
+ function getUserPhotoUrl(photoId?: number | null) {
99
+ return typeof photoId === 'number' && photoId > 0
100
+ ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/file/open/${photoId}`
101
+ : undefined;
102
+ }
103
+
104
+ function getInitials(value?: string | null) {
105
+ const parts = String(value ?? '')
106
+ .trim()
107
+ .split(/\s+/)
108
+ .filter(Boolean)
109
+ .slice(0, 2);
110
+
111
+ if (!parts.length) {
112
+ return '??';
113
+ }
114
+
115
+ return parts.map((part) => part[0]?.toUpperCase() ?? '').join('');
116
+ }
117
+
92
118
  export function SystemUserSelectWithCreate({
93
119
  label,
94
120
  value,
@@ -132,56 +158,55 @@ export function SystemUserSelectWithCreate({
132
158
  return () => clearTimeout(timeout);
133
159
  }, [search]);
134
160
 
135
- const loadOwnerOptions = async (
136
- page: number,
137
- searchTerm: string,
138
- reset: boolean
139
- ) => {
140
- const requestId = requestIdRef.current + 1;
141
- requestIdRef.current = requestId;
142
-
143
- if (reset) {
144
- setIsLoadingOptions(true);
145
- } else {
146
- setIsLoadingMoreOptions(true);
147
- }
148
-
149
- try {
150
- const params = new URLSearchParams();
151
- params.set('page', String(page));
152
- params.set('pageSize', String(PAGE_SIZE));
161
+ const loadOwnerOptions = useCallback(
162
+ async (page: number, searchTerm: string, reset: boolean) => {
163
+ const requestId = requestIdRef.current + 1;
164
+ requestIdRef.current = requestId;
153
165
 
154
- const normalizedSearch = searchTerm.trim();
155
- if (normalizedSearch) {
156
- params.set('search', normalizedSearch);
166
+ if (reset) {
167
+ setIsLoadingOptions(true);
168
+ } else {
169
+ setIsLoadingMoreOptions(true);
157
170
  }
158
171
 
159
- const response = await request<UserListPayload>({
160
- url: `/user?${params.toString()}`,
161
- method: 'GET',
162
- });
172
+ try {
173
+ const params = new URLSearchParams();
174
+ params.set('page', String(page));
175
+ params.set('pageSize', String(PAGE_SIZE));
163
176
 
164
- if (requestIdRef.current !== requestId) {
165
- return;
166
- }
177
+ const normalizedSearch = searchTerm.trim();
178
+ if (normalizedSearch) {
179
+ params.set('search', normalizedSearch);
180
+ }
167
181
 
168
- const parsed = parseUserListResponse(response.data);
169
- setCurrentPage(page);
170
- setHasMore(parsed.hasMore);
171
- setRemoteOptions((current) =>
172
- reset ? parsed.items : mergeOptions(current, parsed.items)
173
- );
174
- } finally {
175
- if (requestIdRef.current === requestId) {
176
- setIsLoadingOptions(false);
177
- setIsLoadingMoreOptions(false);
182
+ const response = await request<UserListPayload>({
183
+ url: `/user?${params.toString()}`,
184
+ method: 'GET',
185
+ });
186
+
187
+ if (requestIdRef.current !== requestId) {
188
+ return;
189
+ }
190
+
191
+ const parsed = parseUserListResponse(response.data);
192
+ setCurrentPage(page);
193
+ setHasMore(parsed.hasMore);
194
+ setRemoteOptions((current) =>
195
+ reset ? parsed.items : mergeOptions(current, parsed.items)
196
+ );
197
+ } finally {
198
+ if (requestIdRef.current === requestId) {
199
+ setIsLoadingOptions(false);
200
+ setIsLoadingMoreOptions(false);
201
+ }
178
202
  }
179
- }
180
- };
203
+ },
204
+ [request]
205
+ );
181
206
 
182
207
  useEffect(() => {
183
208
  void loadOwnerOptions(1, debouncedSearch, true);
184
- }, [debouncedSearch]);
209
+ }, [debouncedSearch, loadOwnerOptions]);
185
210
 
186
211
  useEffect(() => {
187
212
  const normalizedValue = value.trim();
@@ -213,13 +238,14 @@ export function SystemUserSelectWithCreate({
213
238
  }
214
239
 
215
240
  const rawUser = response.data as
216
- | { id?: number; name?: string | null }
241
+ | { id?: number; name?: string | null; photo_id?: number | null }
217
242
  | undefined;
218
243
  const resolvedOption =
219
244
  rawUser?.id && String(rawUser.id) === normalizedValue
220
245
  ? {
221
246
  id: rawUser.id,
222
247
  name: rawUser.name || `#${rawUser.id}`,
248
+ photoId: rawUser.photo_id ?? null,
223
249
  }
224
250
  : null;
225
251
 
@@ -232,6 +258,7 @@ export function SystemUserSelectWithCreate({
232
258
  setSelectedValueOption({
233
259
  id: Number(normalizedValue),
234
260
  name: `#${normalizedValue}`,
261
+ photoId: null,
235
262
  });
236
263
  })
237
264
  .catch(() => {
@@ -239,6 +266,7 @@ export function SystemUserSelectWithCreate({
239
266
  setSelectedValueOption({
240
267
  id: Number(normalizedValue),
241
268
  name: `#${normalizedValue}`,
269
+ photoId: null,
242
270
  });
243
271
  }
244
272
  });
@@ -371,13 +399,26 @@ export function SystemUserSelectWithCreate({
371
399
  type="button"
372
400
  variant="outline"
373
401
  role="combobox"
374
- className="h-10 min-w-0 flex-1 justify-between overflow-hidden"
402
+ className="h-10 min-w-0 flex-1 justify-between gap-2 overflow-hidden"
375
403
  >
376
- <span className="truncate text-left">
377
- {selectedOption
378
- ? `${selectedOption.name} (#${selectedOption.id})`
379
- : emptyLabel}
380
- </span>
404
+ <div className="flex min-w-0 items-center gap-2">
405
+ {selectedOption ? (
406
+ <Avatar className="h-7 w-7 shrink-0 border border-border/60 bg-muted">
407
+ <AvatarImage
408
+ src={getUserPhotoUrl(selectedOption.photoId)}
409
+ alt={selectedOption.name}
410
+ />
411
+ <AvatarFallback className="bg-muted text-[11px] font-semibold text-foreground">
412
+ {getInitials(selectedOption.name)}
413
+ </AvatarFallback>
414
+ </Avatar>
415
+ ) : null}
416
+ <span className="truncate text-left">
417
+ {selectedOption
418
+ ? `${selectedOption.name} (#${selectedOption.id})`
419
+ : emptyLabel}
420
+ </span>
421
+ </div>
381
422
  <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
382
423
  </Button>
383
424
  </PopoverTrigger>
@@ -433,6 +474,7 @@ export function SystemUserSelectWithCreate({
433
474
  <CommandItem
434
475
  key={option.id}
435
476
  value={`${option.name} #${option.id}`}
477
+ className="gap-2"
436
478
  onSelect={() => {
437
479
  onChange(String(option.id));
438
480
  setOpen(false);
@@ -443,6 +485,15 @@ export function SystemUserSelectWithCreate({
443
485
  ) : (
444
486
  <span className="mr-2 h-4 w-4" />
445
487
  )}
488
+ <Avatar className="h-7 w-7 shrink-0 border border-border/60 bg-muted">
489
+ <AvatarImage
490
+ src={getUserPhotoUrl(option.photoId)}
491
+ alt={option.name}
492
+ />
493
+ <AvatarFallback className="bg-muted text-[11px] font-semibold text-foreground">
494
+ {getInitials(option.name)}
495
+ </AvatarFallback>
496
+ </Avatar>
446
497
  <div className="min-w-0">
447
498
  <div className="truncate">{option.name}</div>
448
499
  <div className="truncate text-xs text-muted-foreground">
@@ -67,6 +67,20 @@ export type OperationsCollaborator = {
67
67
  pendingScheduleAdjustmentRequests?: number;
68
68
  };
69
69
 
70
+ export type OperationsCollaboratorStats = {
71
+ total: number;
72
+ active: number;
73
+ onLeave: number;
74
+ withContracts: number;
75
+ };
76
+
77
+ export type OperationsContractStats = {
78
+ total: number;
79
+ active: number;
80
+ inactive: number;
81
+ withFile: number;
82
+ };
83
+
70
84
  export type OperationsDepartment = {
71
85
  id: number;
72
86
  slug: string;
@@ -182,10 +196,6 @@ export type OperationsContract = {
182
196
  accountManagerName?: string | null;
183
197
  relatedCollaboratorId?: number | null;
184
198
  relatedCollaboratorName?: string | null;
185
- contractTemplateId?: number | null;
186
- contractTemplateName?: string | null;
187
- contractTemplateSlug?: string | null;
188
- contractTemplateCode?: string | null;
189
199
  mainRelatedPartyName?: string | null;
190
200
  originType?: string | null;
191
201
  originId?: number | null;
@@ -209,24 +219,6 @@ export type OperationsContract = {
209
219
  scheduleSummary?: OperationsWeeklyScheduleDay[];
210
220
  };
211
221
 
212
- export type OperationsContractTemplate = {
213
- id: number;
214
- slug: string;
215
- code?: string | null;
216
- name: string;
217
- description?: string | null;
218
- contractCategory?: string | null;
219
- contractType?: string | null;
220
- billingModel?: string | null;
221
- signatureStatus?: string | null;
222
- isActive?: boolean;
223
- status: string;
224
- contentHtml?: string | null;
225
- usageCount?: number;
226
- createdAt?: string | null;
227
- updatedAt?: string | null;
228
- };
229
-
230
222
  export type OperationsWeeklyScheduleDay = {
231
223
  weekday: string;
232
224
  isWorkingDay: boolean;
@@ -353,6 +345,10 @@ export type OperationsApproval = {
353
345
  scheduleStartDate?: string | null;
354
346
  scheduleEndDate?: string | null;
355
347
  scheduleReason?: string | null;
348
+ // enriched detail (returned by GET /operations/approvals/:id)
349
+ entries?: OperationsTimesheetEntry[];
350
+ days?: OperationsScheduleAdjustmentDay[];
351
+ currentSchedule?: OperationsWeeklyScheduleDay[];
356
352
  };
357
353
 
358
354
  export type OperationsTeamOverview = {
@@ -426,7 +422,6 @@ export type OperationsProjectDetails = OperationsProject & {
426
422
  roleLabel?: string | null;
427
423
  allocationPercent?: number | null;
428
424
  weeklyHours?: number | null;
429
- isBillable?: boolean;
430
425
  startDate?: string | null;
431
426
  endDate?: string | null;
432
427
  status: string;
@@ -439,7 +434,7 @@ export type OperationsProjectDetails = OperationsProject & {
439
434
  };
440
435
  operationalIndicators: {
441
436
  activeAssignments: number;
442
- billableAssignments: number;
437
+ completedAssignments: number;
443
438
  averageAllocation: number;
444
439
  totalWeeklyHours: number;
445
440
  };
@@ -3,14 +3,19 @@ import {
3
3
  formatDate as formatDateWithSettings,
4
4
  } from '@/lib/format-date';
5
5
 
6
- export const operationsCurrency = new Intl.NumberFormat('en-US', {
7
- style: 'currency',
8
- currency: 'USD',
9
- maximumFractionDigits: 0,
10
- });
11
-
12
- export function formatCurrency(value: number) {
13
- return operationsCurrency.format(value);
6
+ export function formatCurrency(
7
+ value: number,
8
+ getSettingValue?: ((key: string) => unknown) | null,
9
+ currentLocaleCode = 'pt-BR'
10
+ ) {
11
+ const currency =
12
+ (getSettingValue?.('currency') as string | undefined) || 'BRL';
13
+ const locale = resolveLocale(currentLocaleCode);
14
+ return new Intl.NumberFormat(locale, {
15
+ style: 'currency',
16
+ currency,
17
+ maximumFractionDigits: 2,
18
+ }).format(value);
14
19
  }
15
20
 
16
21
  function resolveLocale(currentLocaleCode: string) {
@@ -179,7 +184,7 @@ export function formatDateRange(
179
184
  return formatDate(startDate, getSettingValue, currentLocaleCode);
180
185
  }
181
186
 
182
- return `${formatDate(startDate, getSettingValue, currentLocaleCode)} to ${formatDate(endDate, getSettingValue, currentLocaleCode)}`;
187
+ return `${formatDate(startDate, getSettingValue, currentLocaleCode)} - ${formatDate(endDate, getSettingValue, currentLocaleCode)}`;
183
188
  }
184
189
 
185
190
  export function getStatusBadgeClass(value?: string | null) {