@hed-hog/operations 0.0.329 → 0.0.330

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 (31) hide show
  1. package/dist/controllers/operations-tasks.controller.d.ts +4 -4
  2. package/dist/operations.service.d.ts +4 -4
  3. package/dist/operations.service.d.ts.map +1 -1
  4. package/dist/operations.service.js +24 -4
  5. package/dist/operations.service.js.map +1 -1
  6. package/dist/operations.service.spec.js +1 -1
  7. package/dist/operations.service.spec.js.map +1 -1
  8. package/hedhog/data/dashboard_component_role.yaml +8 -8
  9. package/hedhog/data/dashboard_role.yaml +1 -1
  10. package/hedhog/data/menu.yaml +6 -16
  11. package/hedhog/data/role.yaml +1 -1
  12. package/hedhog/data/route.yaml +55 -55
  13. package/hedhog/frontend/app/_components/async-options-combobox.tsx.ejs +15 -9
  14. package/hedhog/frontend/app/_components/project-costs-section.tsx.ejs +51 -81
  15. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +39 -11
  16. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +21 -4
  17. package/hedhog/frontend/app/_components/task-file-attachments.tsx.ejs +10 -8
  18. package/hedhog/frontend/app/_lib/hooks/use-values-visibility.ts.ejs +61 -0
  19. package/hedhog/frontend/app/approvals/page.tsx.ejs +5 -1
  20. package/hedhog/frontend/app/collaborators/page.tsx.ejs +68 -34
  21. package/hedhog/frontend/app/my-projects/page.tsx.ejs +45 -6
  22. package/hedhog/frontend/app/my-tasks/page.tsx.ejs +1 -1
  23. package/hedhog/frontend/app/projects/page.tsx.ejs +60 -5
  24. package/hedhog/frontend/app/reports/collaborators/page.tsx.ejs +65 -52
  25. package/hedhog/frontend/app/reports/projects/page.tsx.ejs +80 -82
  26. package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +7 -1
  27. package/hedhog/frontend/messages/en.json +223 -16
  28. package/hedhog/frontend/messages/pt.json +223 -16
  29. package/package.json +4 -4
  30. package/src/operations.service.spec.ts +1 -1
  31. package/src/operations.service.ts +25 -5
@@ -35,6 +35,8 @@ import {
35
35
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
36
36
  import {
37
37
  CalendarDays,
38
+ Eye,
39
+ EyeOff,
38
40
  FileText,
39
41
  Info,
40
42
  LayoutGrid,
@@ -56,6 +58,10 @@ import { OperationsHeader } from '../_components/operations-header';
56
58
  import { StatusBadge } from '../_components/status-badge';
57
59
  import { fetchOperations, mutateOperations } from '../_lib/api';
58
60
  import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
61
+ import {
62
+ MASKED_VALUE,
63
+ useValuesVisibility,
64
+ } from '../_lib/hooks/use-values-visibility';
59
65
  import type {
60
66
  OperationsCollaborator,
61
67
  OperationsCollaboratorStats,
@@ -128,6 +134,7 @@ export default function OperationsCollaboratorsPage() {
128
134
  return savedViewMode === 'cards' ? 'cards' : 'table';
129
135
  });
130
136
  const [isCreateSheetOpen, setIsCreateSheetOpen] = useState(false);
137
+ const { valuesVisible, toggleValuesVisible } = useValuesVisibility();
131
138
 
132
139
  const editParam = searchParams.get('edit');
133
140
  const editingCollaboratorId =
@@ -345,11 +352,13 @@ export default function OperationsCollaboratorsPage() {
345
352
  t('cards.totalSalaryDescription'),
346
353
  t('cards.totalSalaryTooltip')
347
354
  ),
348
- value: formatCurrency(
349
- collaboratorStats?.totalSalary ?? 0,
350
- getSettingValue,
351
- currentLocaleCode
352
- ),
355
+ value: valuesVisible
356
+ ? formatCurrency(
357
+ collaboratorStats?.totalSalary ?? 0,
358
+ getSettingValue,
359
+ currentLocaleCode
360
+ )
361
+ : MASKED_VALUE,
353
362
  icon: Wallet,
354
363
  accentClassName: 'from-emerald-500/20 via-green-500/10 to-transparent',
355
364
  iconContainerClassName:
@@ -362,11 +371,13 @@ export default function OperationsCollaboratorsPage() {
362
371
  t('cards.totalCostsDescription'),
363
372
  t('cards.totalCostsTooltip')
364
373
  ),
365
- value: formatCurrency(
366
- collaboratorStats?.totalCosts ?? 0,
367
- getSettingValue,
368
- currentLocaleCode
369
- ),
374
+ value: valuesVisible
375
+ ? formatCurrency(
376
+ collaboratorStats?.totalCosts ?? 0,
377
+ getSettingValue,
378
+ currentLocaleCode
379
+ )
380
+ : MASKED_VALUE,
370
381
  icon: TrendingUp,
371
382
  accentClassName: 'from-amber-500/20 via-yellow-500/10 to-transparent',
372
383
  iconContainerClassName:
@@ -379,11 +390,13 @@ export default function OperationsCollaboratorsPage() {
379
390
  t('cards.avgSalaryPlusCostsDescription'),
380
391
  t('cards.avgSalaryPlusCostsTooltip')
381
392
  ),
382
- value: formatCurrency(
383
- collaboratorStats?.avgSalaryPlusCosts ?? 0,
384
- getSettingValue,
385
- currentLocaleCode
386
- ),
393
+ value: valuesVisible
394
+ ? formatCurrency(
395
+ collaboratorStats?.avgSalaryPlusCosts ?? 0,
396
+ getSettingValue,
397
+ currentLocaleCode
398
+ )
399
+ : MASKED_VALUE,
387
400
  icon: Wallet,
388
401
  accentClassName: 'from-violet-500/20 via-purple-500/10 to-transparent',
389
402
  iconContainerClassName:
@@ -396,11 +409,13 @@ export default function OperationsCollaboratorsPage() {
396
409
  t('cards.avgSalaryPlusCostsPerCollaboratorDescription'),
397
410
  t('cards.avgSalaryPlusCostsPerCollaboratorTooltip')
398
411
  ),
399
- value: formatCurrency(
400
- collaboratorStats?.avgSalaryPlusCostsPerCollaborator ?? 0,
401
- getSettingValue,
402
- currentLocaleCode
403
- ),
412
+ value: valuesVisible
413
+ ? formatCurrency(
414
+ collaboratorStats?.avgSalaryPlusCostsPerCollaborator ?? 0,
415
+ getSettingValue,
416
+ currentLocaleCode
417
+ )
418
+ : MASKED_VALUE,
404
419
  icon: TrendingUp,
405
420
  accentClassName: 'from-rose-500/20 via-pink-500/10 to-transparent',
406
421
  iconContainerClassName:
@@ -413,6 +428,7 @@ export default function OperationsCollaboratorsPage() {
413
428
  formatCurrency,
414
429
  getSettingValue,
415
430
  currentLocaleCode,
431
+ valuesVisible,
416
432
  ]);
417
433
 
418
434
  const handleViewModeChange = (value: string) => {
@@ -452,13 +468,27 @@ export default function OperationsCollaboratorsPage() {
452
468
  description={t('description')}
453
469
  current={t('breadcrumb')}
454
470
  actions={
455
- access.isDirector ? (
456
- <div className="flex flex-wrap gap-2">
471
+ <div className="flex flex-wrap items-center gap-2">
472
+ <Button
473
+ size="icon"
474
+ variant="ghost"
475
+ onClick={toggleValuesVisible}
476
+ title={commonT(
477
+ valuesVisible ? 'actions.hideValues' : 'actions.showValues'
478
+ )}
479
+ >
480
+ {valuesVisible ? (
481
+ <EyeOff className="h-4 w-4" />
482
+ ) : (
483
+ <Eye className="h-4 w-4" />
484
+ )}
485
+ </Button>
486
+ {access.isDirector ? (
457
487
  <Button size="sm" onClick={openCreateSheet}>
458
488
  {commonT('actions.create')}
459
489
  </Button>
460
- </div>
461
- ) : undefined
490
+ ) : null}
491
+ </div>
462
492
  }
463
493
  />
464
494
 
@@ -620,11 +650,13 @@ export default function OperationsCollaboratorsPage() {
620
650
  {formT('fields.compensationAmount')}:
621
651
  </span>{' '}
622
652
  {collaborator.compensationAmount != null
623
- ? formatCurrency(
624
- Number(collaborator.compensationAmount),
625
- getSettingValue,
626
- currentLocaleCode
627
- )
653
+ ? valuesVisible
654
+ ? formatCurrency(
655
+ Number(collaborator.compensationAmount),
656
+ getSettingValue,
657
+ currentLocaleCode
658
+ )
659
+ : MASKED_VALUE
628
660
  : commonT('labels.notAvailable')}
629
661
  </div>
630
662
  <div>
@@ -823,11 +855,13 @@ export default function OperationsCollaboratorsPage() {
823
855
  </TableCell>
824
856
  <TableCell className="hidden 2xl:table-cell">
825
857
  {collaborator.compensationAmount != null
826
- ? formatCurrency(
827
- Number(collaborator.compensationAmount),
828
- getSettingValue,
829
- currentLocaleCode
830
- )
858
+ ? valuesVisible
859
+ ? formatCurrency(
860
+ Number(collaborator.compensationAmount),
861
+ getSettingValue,
862
+ currentLocaleCode
863
+ )
864
+ : MASKED_VALUE
831
865
  : commonT('labels.notAvailable')}
832
866
  </TableCell>
833
867
  <TableCell className="hidden xl:table-cell">
@@ -6,6 +6,7 @@ import {
6
6
  PaginationFooter,
7
7
  SearchBar,
8
8
  } from '@/components/entity-list';
9
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
9
10
  import { Button } from '@/components/ui/button';
10
11
  import { Card, CardContent } from '@/components/ui/card';
11
12
  import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
@@ -41,6 +42,22 @@ const MY_PROJECTS_VIEW_STORAGE_KEY = 'operations-my-projects-view-mode';
41
42
 
42
43
  type ProjectViewMode = 'table' | 'cards';
43
44
 
45
+ function getPersonAvatarUrl(avatarId?: number | null): string {
46
+ return typeof avatarId === 'number' && avatarId > 0
47
+ ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/person/avatar/${avatarId}`
48
+ : '/placeholder.png';
49
+ }
50
+
51
+ function getInitials(value?: string | null): string {
52
+ if (!value) return '?';
53
+ return value
54
+ .split(' ')
55
+ .filter(Boolean)
56
+ .slice(0, 2)
57
+ .map((w) => w[0]!.toUpperCase())
58
+ .join('');
59
+ }
60
+
44
61
  export default function OperationsMyProjectsPage() {
45
62
  const t = useTranslations('operations.MyProjectsPage');
46
63
  const commonT = useTranslations('operations.Common');
@@ -266,11 +283,22 @@ export default function OperationsMyProjectsPage() {
266
283
  </div>
267
284
 
268
285
  <div className="grid gap-2 text-sm text-muted-foreground lg:grid-cols-2">
269
- <div>
270
- <span className="font-medium text-foreground">
286
+ <div className="flex flex-wrap items-center gap-1.5">
287
+ <span className="shrink-0 font-medium text-foreground">
271
288
  {commonT('labels.client')}:
272
- </span>{' '}
273
- {project.clientName || commonT('labels.notAvailable')}
289
+ </span>
290
+ <Avatar className="h-5 w-5 shrink-0">
291
+ <AvatarImage
292
+ src={getPersonAvatarUrl(project.clientAvatarId)}
293
+ alt={project.clientName ?? ''}
294
+ />
295
+ <AvatarFallback className="text-[9px] font-medium">
296
+ {getInitials(project.clientName)}
297
+ </AvatarFallback>
298
+ </Avatar>
299
+ <span className="truncate">
300
+ {project.clientName || commonT('labels.notAvailable')}
301
+ </span>
274
302
  </div>
275
303
  <div>
276
304
  <span className="font-medium text-foreground">
@@ -367,8 +395,19 @@ export default function OperationsMyProjectsPage() {
367
395
  </div>
368
396
  </TableCell>
369
397
  <TableCell>
370
- <div className="truncate">
371
- {project.clientName || commonT('labels.notAvailable')}
398
+ <div className="flex min-w-0 items-center gap-1.5">
399
+ <Avatar className="h-6 w-6 shrink-0">
400
+ <AvatarImage
401
+ src={getPersonAvatarUrl(project.clientAvatarId)}
402
+ alt={project.clientName ?? ''}
403
+ />
404
+ <AvatarFallback className="text-[9px] font-medium">
405
+ {getInitials(project.clientName)}
406
+ </AvatarFallback>
407
+ </Avatar>
408
+ <span className="truncate">
409
+ {project.clientName || commonT('labels.notAvailable')}
410
+ </span>
372
411
  </div>
373
412
  </TableCell>
374
413
  <TableCell>
@@ -945,7 +945,7 @@ export default function OperationsMyTasksPage() {
945
945
 
946
946
  <div className="mt-3 space-y-1.5">
947
947
  <div className="flex items-center justify-between text-[11px] text-muted-foreground">
948
- <span>Progresso</span>
948
+ <span>{t('labels.progress')}</span>
949
949
  <span>
950
950
  {getTaskProgress(task.status)}%
951
951
  </span>
@@ -32,6 +32,7 @@ import {
32
32
  ArchiveRestore,
33
33
  CalendarDays,
34
34
  Eye,
35
+ EyeOff,
35
36
  FileText,
36
37
  FolderKanban,
37
38
  LayoutGrid,
@@ -49,8 +50,16 @@ import { ProjectFormScreen } from '../_components/project-form-screen';
49
50
  import { StatusBadge } from '../_components/status-badge';
50
51
  import { fetchOperations, mutateOperations } from '../_lib/api';
51
52
  import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
53
+ import {
54
+ MASKED_VALUE,
55
+ useValuesVisibility,
56
+ } from '../_lib/hooks/use-values-visibility';
52
57
  import type { OperationsProject, PaginatedResponse } from '../_lib/types';
53
- import { formatDate, getStatusBadgeClass } from '../_lib/utils/format';
58
+ import {
59
+ formatCurrency,
60
+ formatDate,
61
+ getStatusBadgeClass,
62
+ } from '../_lib/utils/format';
54
63
 
55
64
  const PROJECT_VIEW_STORAGE_KEY = 'operations-projects-view-mode';
56
65
 
@@ -154,6 +163,8 @@ export default function OperationsProjectsPage() {
154
163
  updateSheetQuery({ editId: projectId });
155
164
  };
156
165
 
166
+ const { valuesVisible, toggleValuesVisible } = useValuesVisibility();
167
+
157
168
  const { data: projectsResponse, refetch } = useQuery<
158
169
  PaginatedResponse<OperationsProject>
159
170
  >({
@@ -269,11 +280,27 @@ export default function OperationsProjectsPage() {
269
280
  description={t('description')}
270
281
  current={t('breadcrumb')}
271
282
  actions={
272
- access.isDirector ? (
273
- <Button size="sm" onClick={openCreateSheet}>
274
- {commonT('actions.create')}
283
+ <div className="flex flex-wrap items-center gap-2">
284
+ <Button
285
+ size="icon"
286
+ variant="ghost"
287
+ onClick={toggleValuesVisible}
288
+ title={commonT(
289
+ valuesVisible ? 'actions.hideValues' : 'actions.showValues'
290
+ )}
291
+ >
292
+ {valuesVisible ? (
293
+ <EyeOff className="h-4 w-4" />
294
+ ) : (
295
+ <Eye className="h-4 w-4" />
296
+ )}
275
297
  </Button>
276
- ) : undefined
298
+ {access.isDirector ? (
299
+ <Button size="sm" onClick={openCreateSheet}>
300
+ {commonT('actions.create')}
301
+ </Button>
302
+ ) : null}
303
+ </div>
277
304
  }
278
305
  />
279
306
 
@@ -441,6 +468,20 @@ export default function OperationsProjectsPage() {
441
468
  currentLocaleCode
442
469
  )}
443
470
  </div>
471
+ <div>
472
+ <span className="font-medium text-foreground">
473
+ {commonT('labels.budget')}:
474
+ </span>{' '}
475
+ {project.budgetAmount != null
476
+ ? valuesVisible
477
+ ? formatCurrency(
478
+ project.budgetAmount,
479
+ getSettingValue,
480
+ currentLocaleCode
481
+ )
482
+ : MASKED_VALUE
483
+ : commonT('labels.notAvailable')}
484
+ </div>
444
485
  <div className="flex items-center gap-2">
445
486
  <span className="font-medium text-foreground">
446
487
  {commonT('labels.contractStatus')}:
@@ -538,6 +579,9 @@ export default function OperationsProjectsPage() {
538
579
  <TableHead className="hidden xl:table-cell">
539
580
  {commonT('labels.endDate')}
540
581
  </TableHead>
582
+ <TableHead className="hidden xl:table-cell">
583
+ {commonT('labels.budget')}
584
+ </TableHead>
541
585
  <TableHead className="hidden 2xl:table-cell">
542
586
  {commonT('labels.contractStatus')}
543
587
  </TableHead>
@@ -626,6 +670,17 @@ export default function OperationsProjectsPage() {
626
670
  currentLocaleCode
627
671
  )}
628
672
  </TableCell>
673
+ <TableCell className="hidden xl:table-cell">
674
+ {project.budgetAmount != null
675
+ ? valuesVisible
676
+ ? formatCurrency(
677
+ project.budgetAmount,
678
+ getSettingValue,
679
+ currentLocaleCode
680
+ )
681
+ : MASKED_VALUE
682
+ : commonT('labels.notAvailable')}
683
+ </TableCell>
629
684
  <TableCell className="hidden 2xl:table-cell">
630
685
  {project.contractStatus ? (
631
686
  <StatusBadge