@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
@@ -42,7 +42,6 @@ import {
42
42
  SheetHeader,
43
43
  SheetTitle,
44
44
  } from '@/components/ui/sheet';
45
- import { Switch } from '@/components/ui/switch';
46
45
  import { Textarea } from '@/components/ui/textarea';
47
46
  import {
48
47
  Tooltip,
@@ -78,7 +77,6 @@ import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
78
77
  import type {
79
78
  OperationsCollaborator,
80
79
  OperationsContract,
81
- OperationsContractTemplate,
82
80
  OperationsProjectDetails,
83
81
  OperationsProjectRole,
84
82
  } from '../_lib/types';
@@ -89,7 +87,6 @@ import {
89
87
  trimToNull,
90
88
  } from '../_lib/utils/forms';
91
89
  import { ContractFormScreen } from './contract-form-screen';
92
- import { ContractTemplateFormScreen } from './contract-template-form-screen';
93
90
  import { DepartmentSelectWithCreate } from './department-select-with-create';
94
91
  import { OperationsHeader } from './operations-header';
95
92
  import { PersonSelectWithCreate } from './person-select-with-create';
@@ -103,13 +100,11 @@ type TeamAssignmentState = {
103
100
  roleLabel: string;
104
101
  weeklyHours: string;
105
102
  allocationPercent: string;
106
- isBillable: boolean;
107
103
  status: string;
108
104
  };
109
105
 
110
106
  type ProjectFormState = {
111
107
  contractId: string;
112
- contractTemplateId: string;
113
108
  managerCollaboratorId: string;
114
109
  code: string;
115
110
  name: string;
@@ -126,7 +121,6 @@ type ProjectFormState = {
126
121
  contractCode: string;
127
122
  contractName: string;
128
123
  contractDescription: string;
129
- autoGenerateContractDraft: boolean;
130
124
  teamAssignments: TeamAssignmentState[];
131
125
  };
132
126
 
@@ -163,7 +157,6 @@ function buildEmptyForm(
163
157
  ): ProjectFormState {
164
158
  return {
165
159
  contractId: 'none',
166
- contractTemplateId: 'none',
167
160
  managerCollaboratorId: 'none',
168
161
  code: '',
169
162
  name: '',
@@ -180,7 +173,6 @@ function buildEmptyForm(
180
173
  contractCode: '',
181
174
  contractName: '',
182
175
  contractDescription: '',
183
- autoGenerateContractDraft: true,
184
176
  teamAssignments: collaborators.map((collaborator) => ({
185
177
  collaboratorId: collaborator.id,
186
178
  selected: false,
@@ -188,7 +180,6 @@ function buildEmptyForm(
188
180
  roleLabel: '',
189
181
  weeklyHours: '',
190
182
  allocationPercent: '',
191
- isBillable: true,
192
183
  status: 'active',
193
184
  })),
194
185
  };
@@ -221,7 +212,6 @@ function toFormState(
221
212
  contractId: project.relatedContract?.id
222
213
  ? String(project.relatedContract.id)
223
214
  : 'none',
224
- contractTemplateId: 'none',
225
215
  managerCollaboratorId: project.managerCollaboratorId
226
216
  ? String(project.managerCollaboratorId)
227
217
  : 'none',
@@ -250,7 +240,6 @@ function toFormState(
250
240
  contractCode: project.relatedContract?.code ?? '',
251
241
  contractName: project.relatedContract?.name ?? '',
252
242
  contractDescription: project.relatedContract?.description ?? '',
253
- autoGenerateContractDraft: true,
254
243
  teamAssignments: collaboratorOptions.map((collaborator) => {
255
244
  const assignment = assignments.get(collaborator.id);
256
245
  return {
@@ -270,7 +259,6 @@ function toFormState(
270
259
  assignment?.allocationPercent !== undefined
271
260
  ? String(assignment.allocationPercent)
272
261
  : '',
273
- isBillable: assignment?.isBillable ?? true,
274
262
  status: assignment?.status ?? 'active',
275
263
  };
276
264
  }),
@@ -474,7 +462,6 @@ type ContractSelectWithCreateProps = {
474
462
  code?: string;
475
463
  name?: string;
476
464
  clientName?: string;
477
- contractTemplateId?: string;
478
465
  contractCategory?: string;
479
466
  contractType?: string;
480
467
  signatureStatus?: string;
@@ -520,7 +507,9 @@ function ContractSelectWithCreate({
520
507
  description: [
521
508
  contract.code,
522
509
  contract.clientName,
523
- formatEnumLabel(contract.contractType),
510
+ contractT.has(`options.contractTypes.${contract.contractType}`)
511
+ ? contractT(`options.contractTypes.${contract.contractType}`)
512
+ : formatEnumLabel(contract.contractType),
524
513
  ]
525
514
  .filter(Boolean)
526
515
  .join(' • '),
@@ -599,104 +588,6 @@ function ContractSelectWithCreate({
599
588
  );
600
589
  }
601
590
 
602
- type ContractTemplateSelectWithCreateProps = {
603
- value: string;
604
- templates: OperationsContractTemplate[];
605
- label: string;
606
- selectPlaceholder: string;
607
- searchPlaceholder: string;
608
- onChange: (value: string) => void;
609
- onCreated?: (template: OperationsContractTemplate) => void | Promise<void>;
610
- };
611
-
612
- function ContractTemplateSelectWithCreate({
613
- value,
614
- templates,
615
- label,
616
- selectPlaceholder,
617
- searchPlaceholder,
618
- onChange,
619
- onCreated,
620
- }: ContractTemplateSelectWithCreateProps) {
621
- const commonT = useTranslations('operations.Common');
622
- const templateT = useTranslations('operations.ContractTemplateFormPage');
623
- const hasSelection = value !== 'none';
624
- const [isCreateSheetOpen, setIsCreateSheetOpen] = useState(false);
625
-
626
- return (
627
- <>
628
- <div className="space-y-2">
629
- <div className="grid min-w-0 grid-cols-[minmax(0,1fr)_auto] gap-2 sm:grid-cols-[minmax(0,1fr)_auto_auto]">
630
- <SearchableSelect
631
- label={label}
632
- value={value}
633
- options={templates.map((template) => ({
634
- id: template.id,
635
- title: template.name,
636
- description: [
637
- template.code,
638
- formatEnumLabel(template.contractType),
639
- formatEnumLabel(template.billingModel),
640
- ]
641
- .filter(Boolean)
642
- .join(' • '),
643
- }))}
644
- placeholder={selectPlaceholder}
645
- searchPlaceholder={searchPlaceholder}
646
- emptyLabel={commonT('labels.notAssigned')}
647
- onChange={onChange}
648
- />
649
-
650
- {hasSelection ? (
651
- <Button
652
- type="button"
653
- variant="outline"
654
- size="icon"
655
- className="shrink-0"
656
- onClick={() => onChange('none')}
657
- aria-label={commonT('actions.clearSelection')}
658
- >
659
- <X className="size-4" />
660
- </Button>
661
- ) : null}
662
-
663
- <Button
664
- type="button"
665
- variant="outline"
666
- size="icon"
667
- className="shrink-0"
668
- onClick={() => setIsCreateSheetOpen(true)}
669
- aria-label={commonT('actions.create')}
670
- >
671
- <Plus className="size-4" />
672
- </Button>
673
- </div>
674
- </div>
675
-
676
- <Sheet open={isCreateSheetOpen} onOpenChange={setIsCreateSheetOpen}>
677
- <SheetContent className="w-full overflow-y-auto sm:max-w-7xl">
678
- <SheetHeader>
679
- <SheetTitle>{templateT('newTitle')}</SheetTitle>
680
- <SheetDescription>{templateT('description')}</SheetDescription>
681
- </SheetHeader>
682
-
683
- <ContractTemplateFormScreen
684
- onCancel={() => setIsCreateSheetOpen(false)}
685
- onSaved={async (template) => {
686
- const createdTemplateId = template?.id
687
- ? String(template.id)
688
- : value;
689
- onChange(createdTemplateId);
690
- await onCreated?.(template);
691
- setIsCreateSheetOpen(false);
692
- }}
693
- />
694
- </SheetContent>
695
- </Sheet>
696
- </>
697
- );
698
- }
699
-
700
591
  export function ProjectFormScreen({
701
592
  projectId,
702
593
  onSaved,
@@ -704,6 +595,7 @@ export function ProjectFormScreen({
704
595
  }: ProjectFormScreenProps) {
705
596
  const t = useTranslations('operations.ProjectFormPage');
706
597
  const commonT = useTranslations('operations.Common');
598
+ const contractT = useTranslations('operations.ContractFormPage');
707
599
  const { request, showToastHandler, currentLocaleCode } = useApp();
708
600
  const access = useOperationsAccess();
709
601
  const router = useRouter();
@@ -715,7 +607,6 @@ export function ProjectFormScreen({
715
607
  () =>
716
608
  z.object({
717
609
  contractId: z.string(),
718
- contractTemplateId: z.string(),
719
610
  managerCollaboratorId: z.string(),
720
611
  code: z.string().trim().min(1, t('messages.requiredFields')),
721
612
  name: z.string().trim().min(1, t('messages.requiredFields')),
@@ -741,7 +632,6 @@ export function ProjectFormScreen({
741
632
  contractCode: z.string(),
742
633
  contractName: z.string(),
743
634
  contractDescription: z.string(),
744
- autoGenerateContractDraft: z.boolean(),
745
635
  teamAssignments: z
746
636
  .array(
747
637
  z.object({
@@ -751,7 +641,6 @@ export function ProjectFormScreen({
751
641
  roleLabel: z.string(),
752
642
  weeklyHours: z.string(),
753
643
  allocationPercent: z.string(),
754
- isBillable: z.boolean(),
755
644
  status: z.string(),
756
645
  })
757
646
  )
@@ -812,20 +701,6 @@ export function ProjectFormScreen({
812
701
  fetchOperations<OperationsContract[]>(request, '/operations/contracts'),
813
702
  });
814
703
 
815
- const { data: contractTemplates = [], refetch: refetchContractTemplates } =
816
- useQuery<OperationsContractTemplate[]>({
817
- queryKey: [
818
- 'operations-project-form-contract-templates',
819
- currentLocaleCode,
820
- ],
821
- enabled: access.isDirector,
822
- queryFn: () =>
823
- fetchOperations<OperationsContractTemplate[]>(
824
- request,
825
- '/operations/contract-templates'
826
- ),
827
- });
828
-
829
704
  const { data: projectRoles = [], refetch: refetchProjectRoles } = useQuery<
830
705
  OperationsProjectRole[]
831
706
  >({
@@ -928,14 +803,6 @@ export function ProjectFormScreen({
928
803
  [availableContracts, form.contractId]
929
804
  );
930
805
 
931
- const selectedContractTemplate = useMemo(
932
- () =>
933
- contractTemplates.find(
934
- (template) => String(template.id) === form.contractTemplateId
935
- ) ?? null,
936
- [contractTemplates, form.contractTemplateId]
937
- );
938
-
939
806
  const managerOptions = useMemo(
940
807
  () =>
941
808
  availableCollaborators.map((collaborator) => ({
@@ -1040,10 +907,6 @@ export function ProjectFormScreen({
1040
907
  values.contractId === 'none'
1041
908
  ? null
1042
909
  : parseNumberInput(values.contractId),
1043
- contractTemplateId:
1044
- values.contractTemplateId === 'none'
1045
- ? null
1046
- : parseNumberInput(values.contractTemplateId),
1047
910
  managerCollaboratorId:
1048
911
  values.managerCollaboratorId === 'none'
1049
912
  ? null
@@ -1063,8 +926,6 @@ export function ProjectFormScreen({
1063
926
  contractCode: trimToNull(values.contractCode),
1064
927
  contractName: trimToNull(values.contractName),
1065
928
  contractDescription: trimToNull(values.contractDescription),
1066
- autoGenerateContractDraft:
1067
- values.contractId === 'none' ? values.autoGenerateContractDraft : false,
1068
929
  teamAssignments: values.teamAssignments
1069
930
  .filter((assignment) => assignment.selected)
1070
931
  .map((assignment) => ({
@@ -1076,7 +937,6 @@ export function ProjectFormScreen({
1076
937
  roleLabel: trimToNull(assignment.roleLabel),
1077
938
  weeklyHours: parseNumberInput(assignment.weeklyHours),
1078
939
  allocationPercent: parseNumberInput(assignment.allocationPercent),
1079
- isBillable: assignment.isBillable,
1080
940
  status: assignment.status,
1081
941
  startDate: trimToNull(values.startDate),
1082
942
  endDate: trimToNull(values.endDate),
@@ -1417,7 +1277,7 @@ export function ProjectFormScreen({
1417
1277
  {t('sections.financialsDescription')}
1418
1278
  </p>
1419
1279
  </div>
1420
- <div className="grid min-w-0 gap-3 md:grid-cols-2 xl:grid-cols-5">
1280
+ <div className="grid min-w-0 gap-3 md:grid-cols-2 xl:grid-cols-4">
1421
1281
  <div className="min-w-0 space-y-2">
1422
1282
  <FieldLabel label={commonT('labels.budget')} />
1423
1283
  <InputMoney
@@ -1438,16 +1298,17 @@ export function ProjectFormScreen({
1438
1298
  hint={t('hints.monthlyHourCap')}
1439
1299
  />
1440
1300
  <Input
1441
- type="number"
1442
- step="0.5"
1301
+ type="text"
1302
+ inputMode="numeric"
1443
1303
  placeholder={t('placeholders.monthlyHourCap')}
1444
1304
  value={form.monthlyHourCap}
1445
- onChange={(event) =>
1305
+ onChange={(event) => {
1306
+ const raw = event.target.value.replace(/[^0-9]/g, '');
1446
1307
  setForm((current) => ({
1447
1308
  ...current,
1448
- monthlyHourCap: event.target.value,
1449
- }))
1450
- }
1309
+ monthlyHourCap: raw,
1310
+ }));
1311
+ }}
1451
1312
  />
1452
1313
  </div>
1453
1314
  <div className="min-w-0 space-y-2">
@@ -1477,34 +1338,6 @@ export function ProjectFormScreen({
1477
1338
  </SelectContent>
1478
1339
  </Select>
1479
1340
  </div>
1480
- <div className="min-w-0 space-y-2">
1481
- <FieldLabel
1482
- label={t('fields.contractTemplate')}
1483
- hint={t('hints.contractTemplate')}
1484
- />
1485
- <ContractTemplateSelectWithCreate
1486
- label=""
1487
- value={form.contractTemplateId}
1488
- templates={contractTemplates}
1489
- selectPlaceholder={commonT('labels.notAssigned')}
1490
- searchPlaceholder={t('placeholders.contractTemplateSearch')}
1491
- onChange={(value) =>
1492
- setForm((current) => ({
1493
- ...current,
1494
- contractTemplateId: value,
1495
- }))
1496
- }
1497
- onCreated={async (template) => {
1498
- await refetchContractTemplates();
1499
- setForm((current) => ({
1500
- ...current,
1501
- contractTemplateId: template?.id
1502
- ? String(template.id)
1503
- : current.contractTemplateId,
1504
- }));
1505
- }}
1506
- />
1507
- </div>
1508
1341
  <div className="min-w-0 space-y-2">
1509
1342
  <FieldLabel
1510
1343
  label={commonT('labels.contract')}
@@ -1537,29 +1370,18 @@ export function ProjectFormScreen({
1537
1370
  }}
1538
1371
  initialValues={{
1539
1372
  code: form.code ? `PRJ-${form.code}` : '',
1540
- name: form.name
1541
- ? `${form.name} Service Agreement`
1542
- : (selectedContractTemplate?.name ?? ''),
1373
+ name: form.name ? `${form.name} Service Agreement` : '',
1543
1374
  clientName: form.clientName,
1544
- contractTemplateId: form.contractTemplateId,
1545
- contractCategory:
1546
- selectedContractTemplate?.contractCategory ?? 'client',
1547
- contractType:
1548
- selectedContractTemplate?.contractType ??
1549
- 'service_agreement',
1550
- signatureStatus:
1551
- selectedContractTemplate?.signatureStatus ??
1552
- 'not_started',
1553
- billingModel:
1554
- selectedContractTemplate?.billingModel ??
1555
- form.billingModel,
1375
+ contractCategory: 'client',
1376
+ contractType: 'service_agreement',
1377
+ signatureStatus: 'not_started',
1378
+ billingModel: form.billingModel,
1556
1379
  budgetAmount: form.budgetAmount,
1557
1380
  monthlyHourCap: form.monthlyHourCap,
1558
1381
  startDate: form.startDate,
1559
1382
  endDate: form.endDate,
1560
- description:
1561
- selectedContractTemplate?.description ?? form.summary,
1562
- contentHtml: selectedContractTemplate?.contentHtml ?? '',
1383
+ description: form.summary,
1384
+ contentHtml: '',
1563
1385
  }}
1564
1386
  />
1565
1387
  </div>
@@ -1593,7 +1415,7 @@ export function ProjectFormScreen({
1593
1415
  ]
1594
1416
  .filter(Boolean)
1595
1417
  .join(' • ')
1596
- : t('fields.autoGenerateContractDraftDescription')}
1418
+ : commonT('labels.notAssigned')}
1597
1419
  </div>
1598
1420
  </div>
1599
1421
  {selectedContract?.status ? (
@@ -1603,31 +1425,18 @@ export function ProjectFormScreen({
1603
1425
  selectedContract.status
1604
1426
  )}`}
1605
1427
  >
1606
- {formatEnumLabel(selectedContract.status)}
1428
+ {contractT.has(
1429
+ `options.statuses.${selectedContract.status}`
1430
+ )
1431
+ ? contractT(
1432
+ `options.statuses.${selectedContract.status}`
1433
+ )
1434
+ : formatEnumLabel(selectedContract.status)}
1607
1435
  </span>
1608
1436
  </span>
1609
1437
  ) : null}
1610
1438
  </div>
1611
1439
 
1612
- {selectedContractTemplate ? (
1613
- <div className="mt-3 rounded-md bg-muted/40 px-2.5 py-2">
1614
- <div className="text-xs font-medium text-foreground">
1615
- {t('labels.templateSelected')}
1616
- </div>
1617
- <div className="mt-1 text-[11px] text-muted-foreground">
1618
- {[
1619
- selectedContractTemplate.name,
1620
- selectedContractTemplate.code,
1621
- formatEnumLabel(
1622
- selectedContractTemplate.contractType
1623
- ),
1624
- ]
1625
- .filter(Boolean)
1626
- .join(' • ')}
1627
- </div>
1628
- </div>
1629
- ) : null}
1630
-
1631
1440
  <div className="mt-3 flex flex-wrap gap-2">
1632
1441
  {selectedContract ? (
1633
1442
  <Button type="button" variant="outline" size="sm" asChild>
@@ -1641,50 +1450,6 @@ export function ProjectFormScreen({
1641
1450
  ) : null}
1642
1451
  </div>
1643
1452
  </div>
1644
-
1645
- <div className="min-w-0 px-1 py-2">
1646
- <div className="flex items-center justify-between gap-3">
1647
- <div className="min-w-0">
1648
- <div className="flex items-center gap-1.5 text-sm font-medium">
1649
- <span>{t('fields.autoGenerateContractDraft')}</span>
1650
- <Tooltip>
1651
- <TooltipTrigger asChild>
1652
- <span className="inline-flex cursor-help text-muted-foreground">
1653
- <Info className="h-3.5 w-3.5" />
1654
- </span>
1655
- </TooltipTrigger>
1656
- <TooltipContent>
1657
- <p>
1658
- {form.contractId === 'none'
1659
- ? t(
1660
- 'fields.autoGenerateContractDraftDescription'
1661
- )
1662
- : t('fields.existingContractSelected')}
1663
- </p>
1664
- </TooltipContent>
1665
- </Tooltip>
1666
- </div>
1667
- <div className="text-[11px] text-muted-foreground">
1668
- {form.contractId === 'none'
1669
- ? t('labels.enabled')
1670
- : t('labels.disabled')}
1671
- </div>
1672
- </div>
1673
- <Switch
1674
- checked={
1675
- form.contractId === 'none' &&
1676
- form.autoGenerateContractDraft
1677
- }
1678
- disabled={form.contractId !== 'none'}
1679
- onCheckedChange={(checked) =>
1680
- setForm((current) => ({
1681
- ...current,
1682
- autoGenerateContractDraft: checked,
1683
- }))
1684
- }
1685
- />
1686
- </div>
1687
- </div>
1688
1453
  </div>
1689
1454
  </section>
1690
1455
 
@@ -1735,7 +1500,7 @@ export function ProjectFormScreen({
1735
1500
  return (
1736
1501
  <div
1737
1502
  key={assignment.collaboratorId}
1738
- className="grid min-w-0 gap-2 rounded-lg border px-3 py-2 xl:grid-cols-[minmax(0,1.25fr)_minmax(0,1.2fr)_110px_110px_auto]"
1503
+ className="grid min-w-0 gap-2 rounded-lg border px-3 py-2 xl:grid-cols-[minmax(0,1.25fr)_minmax(0,1.2fr)_110px_110px]"
1739
1504
  >
1740
1505
  <label className="flex cursor-pointer items-start gap-3 py-1">
1741
1506
  <Checkbox
@@ -1789,56 +1554,60 @@ export function ProjectFormScreen({
1789
1554
  ) : null}
1790
1555
  </div>
1791
1556
  <Input
1792
- className="h-9"
1557
+ className="h-9 mt-2 self-start"
1793
1558
  type="number"
1559
+ min="0"
1560
+ step="0.5"
1794
1561
  placeholder={t('fields.weeklyHours')}
1795
1562
  value={assignment.weeklyHours}
1796
1563
  disabled={!assignment.selected}
1797
- onChange={(event) =>
1798
- updateAssignment(assignment.collaboratorId, {
1799
- weeklyHours: event.target.value,
1800
- })
1801
- }
1564
+ onChange={(event) => {
1565
+ const hours = event.target.value;
1566
+ const updates: Partial<TeamAssignmentState> = {
1567
+ weeklyHours: hours,
1568
+ };
1569
+ const cap = collaborator.weeklyCapacityHours;
1570
+ if (cap && hours && Number(hours) > 0) {
1571
+ const pct = Math.round(
1572
+ (Number(hours) / cap) * 100
1573
+ );
1574
+ updates.allocationPercent = String(
1575
+ Math.min(pct, 100)
1576
+ );
1577
+ }
1578
+ updateAssignment(
1579
+ assignment.collaboratorId,
1580
+ updates
1581
+ );
1582
+ }}
1802
1583
  />
1803
1584
  <Input
1804
- className="h-9"
1585
+ className="h-9 mt-2 self-start"
1805
1586
  type="text"
1806
1587
  inputMode="decimal"
1807
1588
  placeholder={t('fields.allocationPercent')}
1808
1589
  value={assignment.allocationPercent}
1809
1590
  disabled={!assignment.selected}
1810
- onChange={(event) =>
1811
- updateAssignment(assignment.collaboratorId, {
1812
- allocationPercent: normalizePercentInput(
1813
- event.target.value
1814
- ),
1815
- })
1816
- }
1817
- />
1818
- <div className="flex items-center justify-between gap-3 px-1 py-2">
1819
- <div className="flex items-center gap-1.5 text-xs font-medium">
1820
- <span>{t('fields.isBillable')}</span>
1821
- <Tooltip>
1822
- <TooltipTrigger asChild>
1823
- <span className="inline-flex cursor-help text-muted-foreground">
1824
- <Info className="h-3.5 w-3.5" />
1825
- </span>
1826
- </TooltipTrigger>
1827
- <TooltipContent>
1828
- <p>{t('fields.isBillableDescription')}</p>
1829
- </TooltipContent>
1830
- </Tooltip>
1831
- </div>
1832
- <Switch
1833
- checked={assignment.isBillable}
1834
- disabled={!assignment.selected}
1835
- onCheckedChange={(checked) =>
1836
- updateAssignment(assignment.collaboratorId, {
1837
- isBillable: checked,
1838
- })
1591
+ onChange={(event) => {
1592
+ const pct = normalizePercentInput(
1593
+ event.target.value
1594
+ );
1595
+ const updates: Partial<TeamAssignmentState> = {
1596
+ allocationPercent: pct,
1597
+ };
1598
+ const cap = collaborator.weeklyCapacityHours;
1599
+ if (cap && pct && Number(pct) > 0) {
1600
+ const hours = Math.round(
1601
+ (Number(pct) / 100) * cap
1602
+ );
1603
+ updates.weeklyHours = String(hours);
1839
1604
  }
1840
- />
1841
- </div>
1605
+ updateAssignment(
1606
+ assignment.collaboratorId,
1607
+ updates
1608
+ );
1609
+ }}
1610
+ />
1842
1611
  </div>
1843
1612
  );
1844
1613
  })}