@hed-hog/operations 0.0.332 → 0.0.347

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 (70) hide show
  1. package/dist/controllers/operations-collaborators.controller.d.ts +55 -36
  2. package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
  3. package/dist/controllers/operations-projects.controller.d.ts +3 -0
  4. package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
  5. package/dist/operations.service.d.ts +58 -36
  6. package/dist/operations.service.d.ts.map +1 -1
  7. package/dist/operations.service.js +34 -34
  8. package/dist/operations.service.js.map +1 -1
  9. package/dist/operations.service.spec.js +6 -0
  10. package/dist/operations.service.spec.js.map +1 -1
  11. package/hedhog/data/menu.yaml +5 -3
  12. package/hedhog/data/route.yaml +7 -7
  13. package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +476 -0
  14. package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +3 -1
  15. package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +261 -0
  16. package/hedhog/frontend/app/_components/collaborator-tasks-tab.tsx.ejs +358 -358
  17. package/hedhog/frontend/app/_components/collaborator-timesheets-tab.tsx.ejs +6 -6
  18. package/hedhog/frontend/app/_components/contract-content-editor.tsx.ejs +258 -0
  19. package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +5 -4
  20. package/hedhog/frontend/app/_components/person-select-with-create.tsx.ejs +1 -0
  21. package/hedhog/frontend/app/_components/project-assignments-tab.tsx.ejs +0 -6
  22. package/hedhog/frontend/app/_components/project-cost-report-screen.tsx.ejs +23 -23
  23. package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +23 -50
  24. package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +62 -28
  25. package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +23 -6
  26. package/hedhog/frontend/app/_components/task-form-sheet.tsx.ejs +629 -629
  27. package/hedhog/frontend/app/_lib/api.ts.ejs +2 -2
  28. package/hedhog/frontend/app/_lib/utils/task-ui.ts.ejs +1 -1
  29. package/hedhog/frontend/app/my-projects/page.tsx.ejs +2 -16
  30. package/hedhog/frontend/app/my-tasks/page.tsx.ejs +86 -24
  31. package/hedhog/frontend/app/projects/page.tsx.ejs +6 -42
  32. package/hedhog/frontend/messages/operations/operations/en.json +2100 -0
  33. package/hedhog/frontend/messages/operations/operations/pt.json +2111 -0
  34. package/hedhog/frontend/widgets/capacity-distribution.tsx.ejs +16 -16
  35. package/hedhog/frontend/widgets/effort-by-project.tsx.ejs +16 -16
  36. package/hedhog/frontend/widgets/headcount-by-area.tsx.ejs +16 -16
  37. package/hedhog/frontend/widgets/index.ts.ejs +25 -25
  38. package/hedhog/frontend/widgets/managed-projects-status.tsx.ejs +16 -16
  39. package/hedhog/frontend/widgets/my-hours-period-kpi.tsx.ejs +16 -16
  40. package/hedhog/frontend/widgets/my-open-requests-kpi.tsx.ejs +16 -16
  41. package/hedhog/frontend/widgets/my-pending-requests-list.tsx.ejs +16 -16
  42. package/hedhog/frontend/widgets/my-project-allocations-kpi.tsx.ejs +16 -16
  43. package/hedhog/frontend/widgets/my-quick-actions.tsx.ejs +16 -16
  44. package/hedhog/frontend/widgets/my-relevant-deadlines.tsx.ejs +16 -16
  45. package/hedhog/frontend/widgets/my-timesheet-status-kpi.tsx.ejs +16 -16
  46. package/hedhog/frontend/widgets/my-weekly-journey.tsx.ejs +16 -16
  47. package/hedhog/frontend/widgets/portfolio-costs-kpi.tsx.ejs +16 -16
  48. package/hedhog/frontend/widgets/portfolio-effort-kpi.tsx.ejs +16 -16
  49. package/hedhog/frontend/widgets/portfolio-projects-kpi.tsx.ejs +16 -16
  50. package/hedhog/frontend/widgets/portfolio-risk-kpi.tsx.ejs +16 -16
  51. package/hedhog/frontend/widgets/project-status-overview.tsx.ejs +16 -16
  52. package/hedhog/frontend/widgets/shared-operations-widget.tsx.ejs +169 -169
  53. package/hedhog/frontend/widgets/strategic-deadlines.tsx.ejs +16 -16
  54. package/hedhog/frontend/widgets/team-approval-queue.tsx.ejs +16 -16
  55. package/hedhog/frontend/widgets/team-capacity-kpi.tsx.ejs +16 -16
  56. package/hedhog/frontend/widgets/team-headcount-kpi.tsx.ejs +16 -16
  57. package/hedhog/frontend/widgets/team-hours-kpi.tsx.ejs +16 -16
  58. package/hedhog/frontend/widgets/team-pending-approvals-kpi.tsx.ejs +16 -16
  59. package/hedhog/frontend/widgets/team-utilization-overview.tsx.ejs +16 -16
  60. package/hedhog/frontend/widgets/team-workload-alerts.tsx.ejs +16 -16
  61. package/hedhog/table/operations_collaborator.yaml +8 -8
  62. package/hedhog/table/operations_task.yaml +76 -76
  63. package/hedhog/table/operations_task_activity.yaml +51 -51
  64. package/package.json +6 -6
  65. package/src/controllers/operations-collaborators.controller.ts +9 -9
  66. package/src/controllers/operations-tasks.controller.ts +156 -156
  67. package/src/dashboard/widgets/MyQuickActions.tsx +22 -22
  68. package/src/dto/create-collaborator.dto.ts +4 -4
  69. package/src/operations.service.spec.ts +1006 -988
  70. package/src/operations.service.ts +40 -42
@@ -97,6 +97,7 @@ import {
97
97
  } from '../_lib/utils/forms';
98
98
  import { ContractFormScreen } from './contract-form-screen';
99
99
  import { OperationsHeader } from './operations-header';
100
+ import { ProjectFileAttachments } from './project-file-attachments';
100
101
 
101
102
  const OPTION_PAGE_SIZE = 12;
102
103
 
@@ -158,12 +159,33 @@ type ProjectFormState = {
158
159
  teamAssignments: TeamAssignmentState[];
159
160
  };
160
161
 
161
- type ProjectFormValues = ProjectFormState;
162
-
163
- function generateProjectCode(name: string): string {
164
- const words = name.trim().split(/\s+/).filter(Boolean);
165
- if (words.length === 0) return '';
166
- if (words.length === 1) return words[0]!.slice(0, 5).toUpperCase();
162
+ type ProjectFormValues = ProjectFormState;
163
+
164
+ type ManagerOption = {
165
+ id: number;
166
+ title: string;
167
+ description: string;
168
+ avatarUrl: string | null;
169
+ };
170
+
171
+ function toManagerOption(collaborator: OperationsCollaborator): ManagerOption {
172
+ return {
173
+ id: collaborator.id,
174
+ title: collaborator.displayName,
175
+ description: [collaborator.department, collaborator.title]
176
+ .filter(Boolean)
177
+ .join(' • '),
178
+ avatarUrl:
179
+ getUserPhotoUrl(collaborator.userPhotoId) ??
180
+ getPersonAvatarUrl(collaborator.personAvatarId) ??
181
+ null,
182
+ };
183
+ }
184
+
185
+ function generateProjectCode(name: string): string {
186
+ const words = name.trim().split(/\s+/).filter(Boolean);
187
+ if (words.length === 0) return '';
188
+ if (words.length === 1) return words[0]!.slice(0, 5).toUpperCase();
167
189
  return words
168
190
  .map((w) => w[0]!.toUpperCase())
169
191
  .join('')
@@ -673,11 +695,11 @@ export function ProjectFormScreen({
673
695
  const personSheetModeRef = useRef<'create' | 'edit'>('edit');
674
696
  const [personSheetOpen, setPersonSheetOpen] = useState(false);
675
697
  const [personToEdit, setPersonToEdit] = useState<Person | null>(null);
676
- const [createdManagerCollaborators, setCreatedManagerCollaborators] =
677
- useState<OperationsCollaborator[]>([]);
678
698
  const isSheetMode = Boolean(onCancel);
679
699
  const isCreateMode = !projectId;
680
700
  const [codeAutoMode, setCodeAutoMode] = useState(isCreateMode);
701
+ const [createdManagerCollaborators, setCreatedManagerCollaborators] =
702
+ useState<OperationsCollaborator[]>([]);
681
703
 
682
704
  const projectFormSchema = useMemo(
683
705
  () =>
@@ -769,7 +791,9 @@ export function ProjectFormScreen({
769
791
  [rawCollaborators]
770
792
  );
771
793
 
772
- const createManagerCollaborator = async (values: Record<string, string>) => {
794
+ const createManagerCollaborator = async (
795
+ values: Record<string, string>
796
+ ): Promise<ManagerOption | null> => {
773
797
  const displayName = values.displayName?.trim() ?? '';
774
798
  const weeklyCapacityHours = parseNumberInput(
775
799
  values.weeklyCapacityHours ?? ''
@@ -797,8 +821,8 @@ export function ProjectFormScreen({
797
821
  return next;
798
822
  });
799
823
 
800
- return created;
801
- };
824
+ return toManagerOption(created);
825
+ };
802
826
 
803
827
  const { data: contracts = [], refetch: refetchContracts } = useQuery<
804
828
  OperationsContract[]
@@ -929,21 +953,13 @@ export function ProjectFormScreen({
929
953
  [availableContracts, form.contractId]
930
954
  );
931
955
 
932
- const managerOptions = useMemo(
933
- () =>
934
- availableCollaborators.map((collaborator) => ({
935
- id: collaborator.id,
936
- title: collaborator.displayName,
937
- description: [collaborator.department, collaborator.title]
938
- .filter(Boolean)
939
- .join(' • '),
940
- avatarUrl:
941
- getUserPhotoUrl(collaborator.userPhotoId) ??
942
- getPersonAvatarUrl(collaborator.personAvatarId) ??
943
- null,
944
- })),
945
- [availableCollaborators]
946
- );
956
+ const managerOptions = useMemo(
957
+ () =>
958
+ availableCollaborators.map((collaborator) =>
959
+ toManagerOption(collaborator)
960
+ ),
961
+ [availableCollaborators]
962
+ );
947
963
 
948
964
  const selectedAssignmentsCount = useMemo(
949
965
  () =>
@@ -1233,6 +1249,9 @@ export function ProjectFormScreen({
1233
1249
  : null
1234
1250
  }
1235
1251
  initialSelectedLabel={field.value}
1252
+ initialSelectedAvatarId={
1253
+ project?.clientAvatarId ?? null
1254
+ }
1236
1255
  selectPlaceholder={t('placeholders.clientName')}
1237
1256
  onChange={(personId, personName) => {
1238
1257
  field.onChange(personName ?? '');
@@ -1321,7 +1340,7 @@ export function ProjectFormScreen({
1321
1340
  )}
1322
1341
  </p>
1323
1342
  </div>
1324
- <div className="grid min-w-0 gap-3 md:grid-cols-2 xl:grid-cols-3">
1343
+ <div className="grid min-w-0 gap-3 md:grid-cols-2 xl:grid-cols-5">
1325
1344
  <div className="min-w-0 space-y-2">
1326
1345
  <FieldLabel label={commonT('labels.manager')} />
1327
1346
  <EntityPicker
@@ -1509,11 +1528,12 @@ export function ProjectFormScreen({
1509
1528
  {!isCreateMode ? (
1510
1529
  <>
1511
1530
  <Tabs defaultValue="financials" className="space-y-3">
1512
- <TabsList className="grid w-full grid-cols-2">
1531
+ <TabsList className="grid w-full grid-cols-3">
1513
1532
  <TabsTrigger value="financials">
1514
1533
  {t('sections.financials')}
1515
1534
  </TabsTrigger>
1516
1535
  <TabsTrigger value="team">{t('sections.team')}</TabsTrigger>
1536
+ <TabsTrigger value="files">{t('sections.files')}</TabsTrigger>
1517
1537
  </TabsList>
1518
1538
 
1519
1539
  <TabsContent value="financials" className="space-y-3">
@@ -1800,6 +1820,20 @@ export function ProjectFormScreen({
1800
1820
  </div>
1801
1821
  </div>
1802
1822
  </TabsContent>
1823
+
1824
+ <TabsContent value="files" className="space-y-3">
1825
+ <div className="space-y-0.5">
1826
+ <h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
1827
+ {t('sections.files')}
1828
+ </h3>
1829
+ <p className="text-[11px] text-muted-foreground/80">
1830
+ {t('sections.filesDescription')}
1831
+ </p>
1832
+ </div>
1833
+ {projectId ? (
1834
+ <ProjectFileAttachments projectId={projectId} />
1835
+ ) : null}
1836
+ </TabsContent>
1803
1837
  </Tabs>
1804
1838
  </>
1805
1839
  ) : (
@@ -41,7 +41,11 @@ import type {
41
41
  OperationsTaskActivity,
42
42
  OperationsTaskComment,
43
43
  } from '../_lib/types';
44
- import { formatDate, formatDateTime, getStatusBadgeClass } from '../_lib/utils/format';
44
+ import {
45
+ formatDate,
46
+ formatDateTime,
47
+ getStatusBadgeClass,
48
+ } from '../_lib/utils/format';
45
49
  import {
46
50
  formatDurationMinutes,
47
51
  getElapsedDoingMinutes,
@@ -138,7 +142,10 @@ export type TaskCommentsSectionProps = {
138
142
  onChanged?: () => void;
139
143
  };
140
144
 
141
- export function TaskCommentsSection({ taskId, onChanged }: TaskCommentsSectionProps) {
145
+ export function TaskCommentsSection({
146
+ taskId,
147
+ onChanged,
148
+ }: TaskCommentsSectionProps) {
142
149
  const { request, showToastHandler, getSettingValue } = useApp();
143
150
  const ct = useTranslations('operations.ProjectDetailsPage.commentsSection');
144
151
  const editWindowMinutes = Number(
@@ -386,9 +393,13 @@ export function TaskCommentsSection({ taskId, onChanged }: TaskCommentsSectionPr
386
393
  />
387
394
  <div className="flex items-center justify-end gap-2">
388
395
  <span className="text-[10px] text-muted-foreground select-none">
389
- <kbd className="rounded border bg-muted px-1 py-0.5 font-mono text-[10px]">Ctrl</kbd>
396
+ <kbd className="rounded border bg-muted px-1 py-0.5 font-mono text-[10px]">
397
+ Ctrl
398
+ </kbd>
390
399
  {' + '}
391
- <kbd className="rounded border bg-muted px-1 py-0.5 font-mono text-[10px]">↵</kbd>
400
+ <kbd className="rounded border bg-muted px-1 py-0.5 font-mono text-[10px]">
401
+
402
+ </kbd>
392
403
  </span>
393
404
  <Button
394
405
  size="sm"
@@ -655,7 +666,9 @@ export function TaskDetailSheet({
655
666
 
656
667
  <Tabs
657
668
  value={tab}
658
- onValueChange={(v) => setTab(v as 'comments' | 'activities')}
669
+ onValueChange={(v) =>
670
+ setTab(v as 'comments' | 'activities')
671
+ }
659
672
  className="flex flex-1 min-h-0 flex-col"
660
673
  >
661
674
  <div className="shrink-0 border-b px-5 py-2">
@@ -745,7 +758,11 @@ export function TaskDetailSheet({
745
758
  <div className="flex items-center gap-1.5 text-sm">
746
759
  <Calendar className="size-3.5 shrink-0 text-muted-foreground" />
747
760
  <span>
748
- {formatDate(task.dueDate, getSettingValue, currentLocaleCode)}
761
+ {formatDate(
762
+ task.dueDate,
763
+ getSettingValue,
764
+ currentLocaleCode
765
+ )}
749
766
  </span>
750
767
  </div>
751
768
  </div>