@hed-hog/operations 0.0.299 → 0.0.301

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 (97) hide show
  1. package/dist/operations.controller.d.ts +713 -31
  2. package/dist/operations.controller.d.ts.map +1 -1
  3. package/dist/operations.controller.js +157 -0
  4. package/dist/operations.controller.js.map +1 -1
  5. package/dist/operations.module.d.ts.map +1 -1
  6. package/dist/operations.module.js +5 -1
  7. package/dist/operations.module.js.map +1 -1
  8. package/dist/operations.proposal.subscriber.d.ts +11 -0
  9. package/dist/operations.proposal.subscriber.d.ts.map +1 -0
  10. package/dist/operations.proposal.subscriber.js +80 -0
  11. package/dist/operations.proposal.subscriber.js.map +1 -0
  12. package/dist/operations.proposal.subscriber.spec.d.ts +2 -0
  13. package/dist/operations.proposal.subscriber.spec.d.ts.map +1 -0
  14. package/dist/operations.proposal.subscriber.spec.js +88 -0
  15. package/dist/operations.proposal.subscriber.spec.js.map +1 -0
  16. package/dist/operations.service.d.ts +490 -46
  17. package/dist/operations.service.d.ts.map +1 -1
  18. package/dist/operations.service.js +3590 -1267
  19. package/dist/operations.service.js.map +1 -1
  20. package/dist/operations.service.spec.d.ts +2 -0
  21. package/dist/operations.service.spec.d.ts.map +1 -0
  22. package/dist/operations.service.spec.js +159 -0
  23. package/dist/operations.service.spec.js.map +1 -0
  24. package/hedhog/data/menu.yaml +232 -198
  25. package/hedhog/data/role.yaml +23 -23
  26. package/hedhog/data/role_route.yaml +39 -0
  27. package/hedhog/data/route.yaml +447 -317
  28. package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +8 -6
  29. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +1163 -327
  30. package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +256 -0
  31. package/hedhog/frontend/app/_components/contract-content-editor.tsx.ejs +258 -0
  32. package/hedhog/frontend/app/_components/contract-creation-wizard.tsx.ejs +631 -0
  33. package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +353 -27
  34. package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +1926 -87
  35. package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +526 -0
  36. package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +247 -0
  37. package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +3520 -0
  38. package/hedhog/frontend/app/_components/department-select-with-create.tsx.ejs +370 -0
  39. package/hedhog/frontend/app/_components/person-select-with-create.tsx.ejs +826 -0
  40. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +1251 -364
  41. package/hedhog/frontend/app/_components/section-card.tsx.ejs +48 -13
  42. package/hedhog/frontend/app/_lib/api.ts.ejs +2 -5
  43. package/hedhog/frontend/app/_lib/types.ts.ejs +76 -33
  44. package/hedhog/frontend/app/_lib/utils/format.ts.ejs +85 -8
  45. package/hedhog/frontend/app/approvals/page.tsx.ejs +90 -54
  46. package/hedhog/frontend/app/collaborators/[id]/edit/page.tsx.ejs +2 -2
  47. package/hedhog/frontend/app/collaborators/[id]/page.tsx.ejs +2 -2
  48. package/hedhog/frontend/app/collaborators/page.tsx.ejs +597 -140
  49. package/hedhog/frontend/app/contracts/[id]/edit/page.tsx.ejs +2 -2
  50. package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +2 -2
  51. package/hedhog/frontend/app/contracts/page.tsx.ejs +941 -262
  52. package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +384 -0
  53. package/hedhog/frontend/app/departments/page.tsx.ejs +442 -0
  54. package/hedhog/frontend/app/page.tsx.ejs +36 -12
  55. package/hedhog/frontend/app/projects/[id]/edit/page.tsx.ejs +2 -2
  56. package/hedhog/frontend/app/projects/new/page.tsx.ejs +2 -2
  57. package/hedhog/frontend/app/projects/page.tsx.ejs +264 -102
  58. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +50 -28
  59. package/hedhog/frontend/app/time-off/page.tsx.ejs +57 -31
  60. package/hedhog/frontend/app/timesheets/page.tsx.ejs +85 -42
  61. package/hedhog/frontend/messages/en.json +473 -12
  62. package/hedhog/frontend/messages/pt.json +528 -66
  63. package/hedhog/table/operations_approval.yaml +49 -49
  64. package/hedhog/table/operations_approval_history.yaml +29 -29
  65. package/hedhog/table/operations_collaborator.yaml +87 -67
  66. package/hedhog/table/operations_collaborator_schedule_day.yaml +34 -34
  67. package/hedhog/table/operations_contract.yaml +121 -100
  68. package/hedhog/table/operations_contract_document.yaml +40 -23
  69. package/hedhog/table/operations_contract_financial_term.yaml +40 -40
  70. package/hedhog/table/operations_contract_history.yaml +27 -27
  71. package/hedhog/table/operations_contract_party.yaml +46 -46
  72. package/hedhog/table/operations_contract_revision.yaml +38 -38
  73. package/hedhog/table/operations_contract_signature.yaml +38 -38
  74. package/hedhog/table/operations_contract_template.yaml +58 -0
  75. package/hedhog/table/operations_department.yaml +24 -0
  76. package/hedhog/table/operations_project.yaml +54 -54
  77. package/hedhog/table/operations_project_assignment.yaml +55 -55
  78. package/hedhog/table/operations_schedule_adjustment_day.yaml +34 -34
  79. package/hedhog/table/operations_schedule_adjustment_request.yaml +53 -53
  80. package/hedhog/table/operations_time_off_request.yaml +57 -57
  81. package/hedhog/table/operations_timesheet.yaml +41 -41
  82. package/hedhog/table/operations_timesheet_entry.yaml +40 -40
  83. package/package.json +5 -3
  84. package/src/operations.controller.ts +304 -182
  85. package/src/operations.module.ts +26 -22
  86. package/src/operations.proposal.subscriber.spec.ts +121 -0
  87. package/src/operations.proposal.subscriber.ts +86 -0
  88. package/src/operations.service.spec.ts +210 -0
  89. package/src/operations.service.ts +7317 -3595
  90. package/dist/operations-data.controller.d.ts +0 -139
  91. package/dist/operations-data.controller.d.ts.map +0 -1
  92. package/dist/operations-data.controller.js +0 -113
  93. package/dist/operations-data.controller.js.map +0 -1
  94. package/dist/operations-growth.controller.d.ts +0 -48
  95. package/dist/operations-growth.controller.d.ts.map +0 -1
  96. package/dist/operations-growth.controller.js +0 -90
  97. package/dist/operations-growth.controller.js.map +0 -1
@@ -0,0 +1,370 @@
1
+ 'use client';
2
+
3
+ import { Button } from '@/components/ui/button';
4
+ import {
5
+ Command,
6
+ CommandEmpty,
7
+ CommandGroup,
8
+ CommandInput,
9
+ CommandItem,
10
+ CommandList,
11
+ } from '@/components/ui/command';
12
+ import { Input } from '@/components/ui/input';
13
+ import { Label } from '@/components/ui/label';
14
+ import {
15
+ Popover,
16
+ PopoverContent,
17
+ PopoverTrigger,
18
+ } from '@/components/ui/popover';
19
+ import {
20
+ Sheet,
21
+ SheetContent,
22
+ SheetDescription,
23
+ SheetHeader,
24
+ SheetTitle,
25
+ } from '@/components/ui/sheet';
26
+ import { ChevronsUpDown, Plus, X } from 'lucide-react';
27
+ import { useTranslations } from 'next-intl';
28
+ import { useMemo, useRef, useState } from 'react';
29
+
30
+ type DepartmentOption = {
31
+ id?: number | null;
32
+ name: string;
33
+ code?: string | null;
34
+ description?: string | null;
35
+ };
36
+
37
+ type DepartmentSelectWithCreateProps = {
38
+ label: string;
39
+ value?: string | null;
40
+ options: DepartmentOption[];
41
+ onChange: (department: DepartmentOption) => void;
42
+ selectPlaceholder: string;
43
+ createDescription?: string;
44
+ createPlaceholder?: string;
45
+ disabled?: boolean;
46
+ };
47
+
48
+ export function DepartmentSelectWithCreate({
49
+ label,
50
+ value,
51
+ options,
52
+ onChange,
53
+ selectPlaceholder,
54
+ createDescription,
55
+ createPlaceholder,
56
+ disabled = false,
57
+ }: DepartmentSelectWithCreateProps) {
58
+ const commonT = useTranslations('operations.Common');
59
+ const [open, setOpen] = useState(false);
60
+ const [search, setSearch] = useState('');
61
+ const [createOpen, setCreateOpen] = useState(false);
62
+ const [newDepartment, setNewDepartment] = useState('');
63
+ const [visibleCount, setVisibleCount] = useState(20);
64
+ const parentScrollContainerRef = useRef<HTMLElement | null>(null);
65
+ const parentScrollTopRef = useRef(0);
66
+
67
+ const normalizedValue = value?.trim() ?? '';
68
+
69
+ const normalizedOptions = useMemo(() => {
70
+ const values = new Map<string, DepartmentOption>();
71
+
72
+ for (const option of options) {
73
+ const normalizedName = option.name.trim();
74
+
75
+ if (!normalizedName) {
76
+ continue;
77
+ }
78
+
79
+ values.set(normalizedName.toLowerCase(), {
80
+ ...option,
81
+ name: normalizedName,
82
+ });
83
+ }
84
+
85
+ if (normalizedValue && !values.has(normalizedValue.toLowerCase())) {
86
+ values.set(normalizedValue.toLowerCase(), {
87
+ id: null,
88
+ name: normalizedValue,
89
+ });
90
+ }
91
+
92
+ return Array.from(values.values()).sort((left, right) =>
93
+ left.name.localeCompare(right.name)
94
+ );
95
+ }, [options, normalizedValue]);
96
+
97
+ const filteredOptions = useMemo(() => {
98
+ const normalizedSearch = search.trim().toLowerCase();
99
+
100
+ if (!normalizedSearch) {
101
+ return normalizedOptions;
102
+ }
103
+
104
+ return normalizedOptions.filter((option) =>
105
+ [option.name, option.code, option.description]
106
+ .filter(Boolean)
107
+ .some((field) => String(field).toLowerCase().includes(normalizedSearch))
108
+ );
109
+ }, [normalizedOptions, search]);
110
+
111
+ const visibleOptions = filteredOptions.slice(0, visibleCount);
112
+
113
+ const captureParentScrollPosition = (trigger: HTMLElement) => {
114
+ const parentSheetContent = trigger.closest(
115
+ '[data-radix-dialog-content]'
116
+ ) as HTMLElement | null;
117
+
118
+ if (!parentSheetContent) {
119
+ parentScrollContainerRef.current = null;
120
+ parentScrollTopRef.current = 0;
121
+ return;
122
+ }
123
+
124
+ parentScrollContainerRef.current = parentSheetContent;
125
+ parentScrollTopRef.current = parentSheetContent.scrollTop;
126
+ };
127
+
128
+ const restoreParentScrollPosition = () => {
129
+ const fallbackOpenDialog = (
130
+ Array.from(
131
+ document.querySelectorAll(
132
+ '[data-radix-dialog-content][data-state="open"]'
133
+ )
134
+ ) as HTMLElement[]
135
+ ).at(-1);
136
+
137
+ const container =
138
+ parentScrollContainerRef.current &&
139
+ document.body.contains(parentScrollContainerRef.current)
140
+ ? parentScrollContainerRef.current
141
+ : fallbackOpenDialog || null;
142
+
143
+ if (!container) {
144
+ return;
145
+ }
146
+
147
+ const restore = () => {
148
+ container.scrollTop = parentScrollTopRef.current;
149
+ };
150
+
151
+ requestAnimationFrame(restore);
152
+ setTimeout(restore, 0);
153
+ setTimeout(restore, 120);
154
+ };
155
+
156
+ const saveDepartment = () => {
157
+ const normalizedDepartment = newDepartment.trim();
158
+
159
+ if (!normalizedDepartment) {
160
+ return;
161
+ }
162
+
163
+ onChange({
164
+ id: null,
165
+ name: normalizedDepartment,
166
+ });
167
+ setNewDepartment('');
168
+ setSearch(normalizedDepartment);
169
+ setCreateOpen(false);
170
+ setOpen(false);
171
+ };
172
+
173
+ return (
174
+ <>
175
+ <div className="grid gap-2">
176
+ <Label>{label}</Label>
177
+
178
+ <div className="flex w-full min-w-0 items-center gap-2">
179
+ <Popover
180
+ open={!disabled && open}
181
+ onOpenChange={(nextOpen) => {
182
+ if (!disabled) {
183
+ setOpen(nextOpen);
184
+ }
185
+ }}
186
+ >
187
+ <PopoverTrigger asChild>
188
+ <Button
189
+ type="button"
190
+ variant="outline"
191
+ role="combobox"
192
+ disabled={disabled}
193
+ className="h-10 flex-1 min-w-0 justify-between overflow-hidden"
194
+ >
195
+ <span className="truncate text-left">
196
+ {normalizedValue || selectPlaceholder}
197
+ </span>
198
+ <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
199
+ </Button>
200
+ </PopoverTrigger>
201
+ <PopoverContent
202
+ className="p-0"
203
+ style={{ width: 'var(--radix-popover-trigger-width)' }}
204
+ >
205
+ <Command shouldFilter={false}>
206
+ <CommandInput
207
+ placeholder={selectPlaceholder}
208
+ value={search}
209
+ onValueChange={(value) => {
210
+ setSearch(value);
211
+ setVisibleCount(20);
212
+ }}
213
+ />
214
+ <CommandList>
215
+ <CommandEmpty>
216
+ <div className="space-y-2 p-2 text-center">
217
+ <p className="text-sm text-muted-foreground">
218
+ {commonT('states.emptyDescription')}
219
+ </p>
220
+ <Button
221
+ type="button"
222
+ variant="outline"
223
+ className="w-full"
224
+ onClick={(event) => {
225
+ captureParentScrollPosition(event.currentTarget);
226
+ setNewDepartment(search.trim());
227
+ setOpen(false);
228
+ setCreateOpen(true);
229
+ }}
230
+ >
231
+ {commonT('actions.create')} {label.toLowerCase()}
232
+ </Button>
233
+ </div>
234
+ </CommandEmpty>
235
+
236
+ <CommandGroup>
237
+ {visibleOptions.map((option) => (
238
+ <CommandItem
239
+ key={`${option.id ?? 'new'}-${option.name}`}
240
+ value={`${option.name} ${option.code ?? ''} ${option.description ?? ''}`}
241
+ onSelect={() => {
242
+ onChange(option);
243
+ setSearch(option.name);
244
+ setOpen(false);
245
+ }}
246
+ >
247
+ <div className="min-w-0">
248
+ <div className="truncate">{option.name}</div>
249
+ {option.code || option.description ? (
250
+ <div className="truncate text-xs text-muted-foreground">
251
+ {[option.code, option.description]
252
+ .filter(Boolean)
253
+ .join(' • ')}
254
+ </div>
255
+ ) : null}
256
+ </div>
257
+ </CommandItem>
258
+ ))}
259
+ </CommandGroup>
260
+
261
+ {filteredOptions.length > visibleCount ? (
262
+ <div className="border-t p-2">
263
+ <Button
264
+ type="button"
265
+ variant="ghost"
266
+ className="w-full"
267
+ onClick={() =>
268
+ setVisibleCount((current) => current + 20)
269
+ }
270
+ >
271
+ {commonT('actions.loadMore')}
272
+ </Button>
273
+ </div>
274
+ ) : null}
275
+ </CommandList>
276
+ </Command>
277
+ </PopoverContent>
278
+ </Popover>
279
+
280
+ {normalizedValue ? (
281
+ <Button
282
+ type="button"
283
+ variant="outline"
284
+ size="icon"
285
+ className="h-10 w-10 shrink-0"
286
+ disabled={disabled}
287
+ onClick={() => {
288
+ onChange({ id: null, name: '' });
289
+ setSearch('');
290
+ setVisibleCount(20);
291
+ setOpen(false);
292
+ }}
293
+ aria-label={commonT('actions.cancel')}
294
+ >
295
+ <X className="h-4 w-4" />
296
+ </Button>
297
+ ) : null}
298
+
299
+ <Button
300
+ type="button"
301
+ variant="outline"
302
+ size="icon"
303
+ className="h-10 w-10 shrink-0"
304
+ disabled={disabled}
305
+ onClick={(event) => {
306
+ captureParentScrollPosition(event.currentTarget);
307
+ setNewDepartment(normalizedValue);
308
+ setOpen(false);
309
+ setCreateOpen(true);
310
+ }}
311
+ aria-label={`${commonT('actions.create')} ${label.toLowerCase()}`}
312
+ >
313
+ <Plus className="h-4 w-4" />
314
+ </Button>
315
+ </div>
316
+ </div>
317
+
318
+ <Sheet
319
+ open={createOpen}
320
+ onOpenChange={(nextOpen) => {
321
+ setCreateOpen(nextOpen);
322
+ if (!nextOpen) {
323
+ setNewDepartment('');
324
+ restoreParentScrollPosition();
325
+ }
326
+ }}
327
+ >
328
+ <SheetContent
329
+ className="w-full overflow-y-auto sm:max-w-md px-2"
330
+ onCloseAutoFocus={(event) => event.preventDefault()}
331
+ >
332
+ <SheetHeader>
333
+ <SheetTitle>
334
+ {commonT('actions.create')} {label.toLowerCase()}
335
+ </SheetTitle>
336
+ <SheetDescription>
337
+ {createDescription || selectPlaceholder}
338
+ </SheetDescription>
339
+ </SheetHeader>
340
+
341
+ <div className="mt-6 space-y-4">
342
+ <div className="space-y-2">
343
+ <Label>{label}</Label>
344
+ <Input
345
+ value={newDepartment}
346
+ placeholder={createPlaceholder || label}
347
+ onChange={(event) => setNewDepartment(event.target.value)}
348
+ onKeyDown={(event) => {
349
+ if (event.key === 'Enter') {
350
+ event.preventDefault();
351
+ saveDepartment();
352
+ }
353
+ }}
354
+ />
355
+ </div>
356
+
357
+ <Button
358
+ type="button"
359
+ className="w-full"
360
+ disabled={!newDepartment.trim()}
361
+ onClick={saveDepartment}
362
+ >
363
+ {commonT('actions.save')}
364
+ </Button>
365
+ </div>
366
+ </SheetContent>
367
+ </Sheet>
368
+ </>
369
+ );
370
+ }