@hed-hog/operations 0.0.319 → 0.0.322

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 (38) hide show
  1. package/dist/controllers/operations-tasks.controller.d.ts +22 -0
  2. package/dist/controllers/operations-tasks.controller.d.ts.map +1 -1
  3. package/dist/controllers/operations-tasks.controller.js +37 -0
  4. package/dist/controllers/operations-tasks.controller.js.map +1 -1
  5. package/dist/dto/create-task.dto.d.ts.map +1 -1
  6. package/dist/dto/create-task.dto.js +0 -1
  7. package/dist/dto/create-task.dto.js.map +1 -1
  8. package/dist/dto/update-task.dto.d.ts.map +1 -1
  9. package/dist/dto/update-task.dto.js +0 -1
  10. package/dist/dto/update-task.dto.js.map +1 -1
  11. package/dist/operations.service.d.ts +22 -0
  12. package/dist/operations.service.d.ts.map +1 -1
  13. package/dist/operations.service.js +187 -132
  14. package/dist/operations.service.js.map +1 -1
  15. package/hedhog/data/operations_cost_type.yaml +95 -95
  16. package/hedhog/data/route.yaml +39 -0
  17. package/hedhog/frontend/app/_components/collaborator-costs-section.tsx.ejs +884 -884
  18. package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +23 -23
  19. package/hedhog/frontend/app/_components/person-select-with-create.tsx.ejs +49 -22
  20. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +2968 -624
  21. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +62 -68
  22. package/hedhog/frontend/app/_components/task-file-attachments.tsx.ejs +388 -0
  23. package/hedhog/frontend/app/_lib/types.ts.ejs +179 -178
  24. package/hedhog/frontend/app/my-tasks/page.tsx.ejs +121 -11
  25. package/hedhog/frontend/app/projects/page.tsx.ejs +105 -22
  26. package/hedhog/frontend/app/reports/collaborators/page.tsx.ejs +771 -771
  27. package/hedhog/frontend/app/reports/projects/page.tsx.ejs +809 -809
  28. package/hedhog/frontend/messages/en.json +143 -2
  29. package/hedhog/frontend/messages/pt.json +143 -2
  30. package/hedhog/table/operations_task_file.yaml +23 -0
  31. package/package.json +5 -5
  32. package/src/controllers/operations-reports.controller.ts +32 -32
  33. package/src/controllers/operations-tasks.controller.ts +43 -9
  34. package/src/dto/create-task.dto.ts +0 -1
  35. package/src/dto/list-reports.dto.ts +51 -51
  36. package/src/dto/update-task.dto.ts +0 -1
  37. package/src/operations.module.ts +5 -5
  38. package/src/operations.service.ts +754 -632
@@ -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';
@@ -27,6 +28,8 @@ import {
27
28
  import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
28
29
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
29
30
  import {
31
+ Archive,
32
+ ArchiveRestore,
30
33
  CalendarDays,
31
34
  Eye,
32
35
  FileText,
@@ -53,6 +56,22 @@ const PROJECT_VIEW_STORAGE_KEY = 'operations-projects-view-mode';
53
56
 
54
57
  type ProjectViewMode = 'table' | 'cards';
55
58
 
59
+ function getPersonAvatarUrl(avatarId?: number | null): string {
60
+ return typeof avatarId === 'number' && avatarId > 0
61
+ ? `${process.env.NEXT_PUBLIC_API_BASE_URL}/person/avatar/${avatarId}`
62
+ : '/placeholder.png';
63
+ }
64
+
65
+ function getInitials(value?: string | null): string {
66
+ if (!value) return '?';
67
+ return value
68
+ .split(' ')
69
+ .filter(Boolean)
70
+ .slice(0, 2)
71
+ .map((w) => w[0]!.toUpperCase())
72
+ .join('');
73
+ }
74
+
56
75
  function parseEditProjectId(value: string | null) {
57
76
  const parsed = Number(value);
58
77
  return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
@@ -335,6 +354,9 @@ export default function OperationsProjectsPage() {
335
354
  <Card
336
355
  key={project.id}
337
356
  className="cursor-pointer overflow-hidden border-border/60 py-0 shadow-sm transition-all hover:-translate-y-0.5 hover:shadow-md"
357
+ onDoubleClick={() =>
358
+ router.push(`/operations/projects/${project.id}`)
359
+ }
338
360
  >
339
361
  <CardContent className="space-y-4 p-4">
340
362
  <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
@@ -359,17 +381,39 @@ export default function OperationsProjectsPage() {
359
381
  </div>
360
382
 
361
383
  <div className="grid gap-2 text-sm text-muted-foreground lg:grid-cols-2">
362
- <div>
363
- <span className="font-medium text-foreground">
384
+ <div className="flex flex-wrap items-center gap-1.5">
385
+ <span className="shrink-0 font-medium text-foreground">
364
386
  {commonT('labels.client')}:
365
- </span>{' '}
366
- {project.clientName || commonT('labels.notAvailable')}
387
+ </span>
388
+ <Avatar className="h-5 w-5 shrink-0">
389
+ <AvatarImage
390
+ src={getPersonAvatarUrl(project.clientAvatarId)}
391
+ alt={project.clientName ?? ''}
392
+ />
393
+ <AvatarFallback className="text-[9px] font-medium">
394
+ {getInitials(project.clientName)}
395
+ </AvatarFallback>
396
+ </Avatar>
397
+ <span className="truncate">
398
+ {project.clientName || commonT('labels.notAvailable')}
399
+ </span>
367
400
  </div>
368
- <div>
369
- <span className="font-medium text-foreground">
401
+ <div className="flex flex-wrap items-center gap-1.5">
402
+ <span className="shrink-0 font-medium text-foreground">
370
403
  {commonT('labels.manager')}:
371
- </span>{' '}
372
- {project.managerName || commonT('labels.notAssigned')}
404
+ </span>
405
+ <Avatar className="h-5 w-5 shrink-0">
406
+ <AvatarImage
407
+ src={getPersonAvatarUrl(project.managerAvatarId)}
408
+ alt={project.managerName ?? ''}
409
+ />
410
+ <AvatarFallback className="text-[9px] font-medium">
411
+ {getInitials(project.managerName)}
412
+ </AvatarFallback>
413
+ </Avatar>
414
+ <span className="truncate">
415
+ {project.managerName || commonT('labels.notAssigned')}
416
+ </span>
373
417
  </div>
374
418
  <div>
375
419
  <span className="font-medium text-foreground">
@@ -414,7 +458,7 @@ export default function OperationsProjectsPage() {
414
458
  </div>
415
459
  </div>
416
460
 
417
- <div className="flex flex-wrap justify-end gap-2 border-t border-border/60 pt-3">
461
+ <div className="flex items-center justify-end gap-2 border-t border-border/60 pt-3">
418
462
  <Button variant="outline" size="icon" asChild>
419
463
  <Link href={`/operations/projects/${project.id}`}>
420
464
  <Eye className="size-4" />
@@ -451,13 +495,20 @@ export default function OperationsProjectsPage() {
451
495
  {access.isDirector ? (
452
496
  <Button
453
497
  variant="outline"
454
- size="sm"
498
+ size="icon"
455
499
  className="cursor-pointer"
500
+ title={
501
+ project.status === 'archived'
502
+ ? commonT('actions.activate')
503
+ : t('actions.archive')
504
+ }
456
505
  onClick={() => void toggleArchived(project)}
457
506
  >
458
- {project.status === 'archived'
459
- ? commonT('actions.activate')
460
- : t('actions.archive')}
507
+ {project.status === 'archived' ? (
508
+ <ArchiveRestore className="size-4" />
509
+ ) : (
510
+ <Archive className="size-4" />
511
+ )}
461
512
  </Button>
462
513
  ) : null}
463
514
  </div>
@@ -500,6 +551,9 @@ export default function OperationsProjectsPage() {
500
551
  <TableRow
501
552
  key={project.id}
502
553
  className="cursor-pointer hover:bg-muted/30"
554
+ onDoubleClick={() =>
555
+ router.push(`/operations/projects/${project.id}`)
556
+ }
503
557
  >
504
558
  <TableCell>
505
559
  <div className="min-w-0">
@@ -518,8 +572,19 @@ export default function OperationsProjectsPage() {
518
572
  </div>
519
573
  </TableCell>
520
574
  <TableCell>
521
- <div className="truncate">
522
- {project.clientName || commonT('labels.notAvailable')}
575
+ <div className="flex min-w-0 items-center gap-1.5">
576
+ <Avatar className="h-6 w-6 shrink-0">
577
+ <AvatarImage
578
+ src={getPersonAvatarUrl(project.clientAvatarId)}
579
+ alt={project.clientName ?? ''}
580
+ />
581
+ <AvatarFallback className="text-[9px] font-medium">
582
+ {getInitials(project.clientName)}
583
+ </AvatarFallback>
584
+ </Avatar>
585
+ <span className="truncate">
586
+ {project.clientName || commonT('labels.notAvailable')}
587
+ </span>
523
588
  </div>
524
589
  </TableCell>
525
590
  <TableCell>
@@ -529,8 +594,19 @@ export default function OperationsProjectsPage() {
529
594
  />
530
595
  </TableCell>
531
596
  <TableCell className="hidden lg:table-cell">
532
- <div className="truncate">
533
- {project.managerName || commonT('labels.notAssigned')}
597
+ <div className="flex min-w-0 items-center gap-1.5">
598
+ <Avatar className="h-6 w-6 shrink-0">
599
+ <AvatarImage
600
+ src={getPersonAvatarUrl(project.managerAvatarId)}
601
+ alt={project.managerName ?? ''}
602
+ />
603
+ <AvatarFallback className="text-[9px] font-medium">
604
+ {getInitials(project.managerName)}
605
+ </AvatarFallback>
606
+ </Avatar>
607
+ <span className="truncate">
608
+ {project.managerName || commonT('labels.notAssigned')}
609
+ </span>
534
610
  </div>
535
611
  </TableCell>
536
612
  <TableCell className="hidden md:table-cell">
@@ -563,7 +639,7 @@ export default function OperationsProjectsPage() {
563
639
  )}
564
640
  </TableCell>
565
641
  <TableCell>
566
- <div className="flex flex-wrap justify-end gap-1.5 sm:gap-2">
642
+ <div className="flex items-center justify-end gap-1.5">
567
643
  <Button variant="outline" size="icon" asChild>
568
644
  <Link href={`/operations/projects/${project.id}`}>
569
645
  <Eye className="size-4" />
@@ -600,13 +676,20 @@ export default function OperationsProjectsPage() {
600
676
  {access.isDirector ? (
601
677
  <Button
602
678
  variant="outline"
603
- size="sm"
679
+ size="icon"
604
680
  className="cursor-pointer"
681
+ title={
682
+ project.status === 'archived'
683
+ ? commonT('actions.activate')
684
+ : t('actions.archive')
685
+ }
605
686
  onClick={() => void toggleArchived(project)}
606
687
  >
607
- {project.status === 'archived'
608
- ? commonT('actions.activate')
609
- : t('actions.archive')}
688
+ {project.status === 'archived' ? (
689
+ <ArchiveRestore className="size-4" />
690
+ ) : (
691
+ <Archive className="size-4" />
692
+ )}
610
693
  </Button>
611
694
  ) : null}
612
695
  </div>