@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.
- package/dist/controllers/operations-approvals.controller.d.ts +114 -1
- package/dist/controllers/operations-approvals.controller.d.ts.map +1 -1
- package/dist/controllers/operations-approvals.controller.js +16 -3
- package/dist/controllers/operations-approvals.controller.js.map +1 -1
- package/dist/controllers/operations-collaborators.controller.d.ts +16 -1
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
- package/dist/controllers/operations-collaborators.controller.js +16 -3
- package/dist/controllers/operations-collaborators.controller.js.map +1 -1
- package/dist/controllers/operations-contracts.controller.d.ts +14 -453
- package/dist/controllers/operations-contracts.controller.d.ts.map +1 -1
- package/dist/controllers/operations-contracts.controller.js +11 -112
- package/dist/controllers/operations-contracts.controller.js.map +1 -1
- package/dist/controllers/operations-org-structure.controller.d.ts +65 -2
- package/dist/controllers/operations-org-structure.controller.d.ts.map +1 -1
- package/dist/controllers/operations-org-structure.controller.js +18 -5
- package/dist/controllers/operations-org-structure.controller.js.map +1 -1
- package/dist/controllers/operations-projects.controller.d.ts +28 -4
- package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
- package/dist/controllers/operations-projects.controller.js +17 -5
- package/dist/controllers/operations-projects.controller.js.map +1 -1
- package/dist/controllers/operations-timesheets.controller.d.ts +31 -4
- package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -1
- package/dist/controllers/operations-timesheets.controller.js +16 -11
- package/dist/controllers/operations-timesheets.controller.js.map +1 -1
- package/dist/dto/list-approvals.dto.d.ts +6 -0
- package/dist/dto/list-approvals.dto.d.ts.map +1 -0
- package/dist/dto/list-approvals.dto.js +28 -0
- package/dist/dto/list-approvals.dto.js.map +1 -0
- package/dist/dto/list-collaborator-types.dto.d.ts +3 -1
- package/dist/dto/list-collaborator-types.dto.d.ts.map +1 -1
- package/dist/dto/list-collaborator-types.dto.js +7 -1
- package/dist/dto/list-collaborator-types.dto.js.map +1 -1
- package/dist/dto/list-collaborators.dto.d.ts +1 -0
- package/dist/dto/list-collaborators.dto.d.ts.map +1 -1
- package/dist/dto/list-collaborators.dto.js +5 -0
- package/dist/dto/list-collaborators.dto.js.map +1 -1
- package/dist/dto/list-contracts.dto.d.ts +8 -0
- package/dist/dto/list-contracts.dto.d.ts.map +1 -0
- package/dist/dto/list-contracts.dto.js +38 -0
- package/dist/dto/list-contracts.dto.js.map +1 -0
- package/dist/dto/list-departments.dto.d.ts +5 -0
- package/dist/dto/list-departments.dto.d.ts.map +1 -0
- package/dist/dto/list-departments.dto.js +23 -0
- package/dist/dto/list-departments.dto.js.map +1 -0
- package/dist/dto/list-projects.dto.d.ts +5 -0
- package/dist/dto/list-projects.dto.d.ts.map +1 -0
- package/dist/dto/list-projects.dto.js +23 -0
- package/dist/dto/list-projects.dto.js.map +1 -0
- package/dist/dto/list-schedule-adjustments.dto.d.ts +5 -0
- package/dist/dto/list-schedule-adjustments.dto.d.ts.map +1 -0
- package/dist/dto/list-schedule-adjustments.dto.js +23 -0
- package/dist/dto/list-schedule-adjustments.dto.js.map +1 -0
- package/dist/dto/list-time-off-requests.dto.d.ts +5 -0
- package/dist/dto/list-time-off-requests.dto.d.ts.map +1 -0
- package/dist/dto/list-time-off-requests.dto.js +23 -0
- package/dist/dto/list-time-off-requests.dto.js.map +1 -0
- package/dist/dto/list-timesheets.dto.d.ts +5 -0
- package/dist/dto/list-timesheets.dto.d.ts.map +1 -0
- package/dist/dto/list-timesheets.dto.js +23 -0
- package/dist/dto/list-timesheets.dto.js.map +1 -0
- package/dist/dto/reorder-collaborator-types.dto.d.ts +4 -0
- package/dist/dto/reorder-collaborator-types.dto.d.ts.map +1 -0
- package/dist/dto/reorder-collaborator-types.dto.js +25 -0
- package/dist/dto/reorder-collaborator-types.dto.js.map +1 -0
- package/dist/operations.service.d.ts +340 -271
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +1007 -1043
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +0 -22
- package/dist/operations.service.spec.js.map +1 -1
- package/hedhog/data/menu.yaml +0 -36
- package/hedhog/data/route.yaml +42 -73
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +8 -1
- package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +15 -10
- package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +108 -213
- package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +251 -2039
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +167 -60
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +70 -301
- package/hedhog/frontend/app/_components/system-user-select-with-create.tsx.ejs +102 -51
- package/hedhog/frontend/app/_lib/types.ts.ejs +19 -24
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +14 -9
- package/hedhog/frontend/app/approvals/page.tsx.ejs +842 -150
- package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +445 -153
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +118 -49
- package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/contracts/page.tsx.ejs +215 -617
- package/hedhog/frontend/app/departments/page.tsx.ejs +257 -113
- package/hedhog/frontend/app/projects/page.tsx.ejs +90 -51
- package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +412 -147
- package/hedhog/frontend/app/time-off/page.tsx.ejs +400 -123
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +460 -365
- package/hedhog/frontend/messages/en.json +143 -14
- package/hedhog/frontend/messages/pt.json +192 -54
- package/hedhog/table/operations_contract.yaml +0 -9
- package/package.json +4 -4
- package/src/controllers/operations-approvals.controller.ts +9 -3
- package/src/controllers/operations-collaborators.controller.ts +15 -2
- package/src/controllers/operations-contracts.controller.ts +8 -92
- package/src/controllers/operations-org-structure.controller.ts +17 -4
- package/src/controllers/operations-projects.controller.ts +10 -4
- package/src/controllers/operations-timesheets.controller.ts +17 -8
- package/src/dto/list-approvals.dto.ts +12 -0
- package/src/dto/list-collaborator-types.dto.ts +7 -2
- package/src/dto/list-collaborators.dto.ts +4 -0
- package/src/dto/list-contracts.dto.ts +20 -0
- package/src/dto/list-departments.dto.ts +8 -0
- package/src/dto/list-projects.dto.ts +8 -0
- package/src/dto/list-schedule-adjustments.dto.ts +8 -0
- package/src/dto/list-time-off-requests.dto.ts +8 -0
- package/src/dto/list-timesheets.dto.ts +8 -0
- package/src/dto/reorder-collaborator-types.dto.ts +10 -0
- package/src/operations.service.spec.ts +0 -30
- package/src/operations.service.ts +1557 -1806
- package/hedhog/frontend/app/_components/contract-creation-wizard.tsx.ejs +0 -631
- package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +0 -526
- package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +0 -247
- package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +0 -3520
- package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +0 -380
- package/hedhog/frontend/app/team/page.tsx.ejs +0 -352
- package/hedhog/table/operations_contract_financial_term.yaml +0 -40
- package/hedhog/table/operations_contract_revision.yaml +0 -38
- package/hedhog/table/operations_contract_signature.yaml +0 -38
- 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
|
-
|
|
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-
|
|
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="
|
|
1442
|
-
|
|
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:
|
|
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
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
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
|
-
|
|
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
|
-
:
|
|
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
|
-
{
|
|
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)
|
|
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
|
-
|
|
1799
|
-
|
|
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
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
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
|
-
|
|
1605
|
+
updateAssignment(
|
|
1606
|
+
assignment.collaboratorId,
|
|
1607
|
+
updates
|
|
1608
|
+
);
|
|
1609
|
+
}}
|
|
1610
|
+
/>
|
|
1842
1611
|
</div>
|
|
1843
1612
|
);
|
|
1844
1613
|
})}
|