@hed-hog/operations 0.0.330 → 0.0.331
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/README.md +5 -5
- package/dist/controllers/operations-collaborators.controller.d.ts +7 -216
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
- package/dist/controllers/operations-contracts.controller.d.ts +6 -6
- package/dist/controllers/operations-projects.controller.d.ts +25 -0
- package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
- package/dist/controllers/operations-projects.controller.js +48 -0
- package/dist/controllers/operations-projects.controller.js.map +1 -1
- package/dist/controllers/operations-reports.controller.d.ts +1 -1
- package/dist/controllers/operations-tasks.controller.d.ts +34 -9
- package/dist/controllers/operations-tasks.controller.d.ts.map +1 -1
- package/dist/controllers/operations-tasks.controller.js +43 -32
- package/dist/controllers/operations-tasks.controller.js.map +1 -1
- package/dist/controllers/operations-timesheets.controller.d.ts +9 -9
- package/dist/dashboard/components/DashboardLayout.d.ts +30 -0
- package/dist/dashboard/components/DashboardLayout.d.ts.map +1 -0
- package/dist/dashboard/components/DashboardLayout.js +87 -0
- package/dist/dashboard/components/DashboardLayout.js.map +1 -0
- package/dist/dashboard/components/widget-registry.d.ts +23 -0
- package/dist/dashboard/components/widget-registry.d.ts.map +1 -0
- package/dist/dashboard/components/widget-registry.js +245 -0
- package/dist/dashboard/components/widget-registry.js.map +1 -0
- package/dist/dashboard/hooks/useDashboardData.d.ts +20 -0
- package/dist/dashboard/hooks/useDashboardData.d.ts.map +1 -0
- package/dist/dashboard/hooks/useDashboardData.js +24 -0
- package/dist/dashboard/hooks/useDashboardData.js.map +1 -0
- package/dist/dashboard/types/widgets.types.d.ts +233 -0
- package/dist/dashboard/types/widgets.types.d.ts.map +1 -0
- package/dist/dashboard/types/widgets.types.js +6 -0
- package/dist/dashboard/types/widgets.types.js.map +1 -0
- package/dist/dashboard/widgets/CapacityDistribution.d.ts +23 -0
- package/dist/dashboard/widgets/CapacityDistribution.d.ts.map +1 -0
- package/dist/dashboard/widgets/CapacityDistribution.js +11 -0
- package/dist/dashboard/widgets/CapacityDistribution.js.map +1 -0
- package/dist/dashboard/widgets/EffortByProject.d.ts +22 -0
- package/dist/dashboard/widgets/EffortByProject.d.ts.map +1 -0
- package/dist/dashboard/widgets/EffortByProject.js +11 -0
- package/dist/dashboard/widgets/EffortByProject.js.map +1 -0
- package/dist/dashboard/widgets/HeadcountByArea.d.ts +24 -0
- package/dist/dashboard/widgets/HeadcountByArea.d.ts.map +1 -0
- package/dist/dashboard/widgets/HeadcountByArea.js +11 -0
- package/dist/dashboard/widgets/HeadcountByArea.js.map +1 -0
- package/dist/dashboard/widgets/ManagedProjectsStatus.d.ts +18 -0
- package/dist/dashboard/widgets/ManagedProjectsStatus.d.ts.map +1 -0
- package/dist/dashboard/widgets/ManagedProjectsStatus.js +12 -0
- package/dist/dashboard/widgets/ManagedProjectsStatus.js.map +1 -0
- package/dist/dashboard/widgets/MyHoursPeriodKpi.d.ts +22 -0
- package/dist/dashboard/widgets/MyHoursPeriodKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyHoursPeriodKpi.js +12 -0
- package/dist/dashboard/widgets/MyHoursPeriodKpi.js.map +1 -0
- package/dist/dashboard/widgets/MyOpenRequestsKpi.d.ts +19 -0
- package/dist/dashboard/widgets/MyOpenRequestsKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyOpenRequestsKpi.js +17 -0
- package/dist/dashboard/widgets/MyOpenRequestsKpi.js.map +1 -0
- package/dist/dashboard/widgets/MyPendingRequestsList.d.ts +23 -0
- package/dist/dashboard/widgets/MyPendingRequestsList.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyPendingRequestsList.js +14 -0
- package/dist/dashboard/widgets/MyPendingRequestsList.js.map +1 -0
- package/dist/dashboard/widgets/MyProjectAllocationsKpi.d.ts +22 -0
- package/dist/dashboard/widgets/MyProjectAllocationsKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyProjectAllocationsKpi.js +11 -0
- package/dist/dashboard/widgets/MyProjectAllocationsKpi.js.map +1 -0
- package/dist/dashboard/widgets/MyQuickActions.d.ts +23 -0
- package/dist/dashboard/widgets/MyQuickActions.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyQuickActions.js +18 -0
- package/dist/dashboard/widgets/MyQuickActions.js.map +1 -0
- package/dist/dashboard/widgets/MyRelevantDeadlines.d.ts +23 -0
- package/dist/dashboard/widgets/MyRelevantDeadlines.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyRelevantDeadlines.js +22 -0
- package/dist/dashboard/widgets/MyRelevantDeadlines.js.map +1 -0
- package/dist/dashboard/widgets/MyTimesheetStatusKpi.d.ts +17 -0
- package/dist/dashboard/widgets/MyTimesheetStatusKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyTimesheetStatusKpi.js +11 -0
- package/dist/dashboard/widgets/MyTimesheetStatusKpi.js.map +1 -0
- package/dist/dashboard/widgets/MyWeeklyJourney.d.ts +21 -0
- package/dist/dashboard/widgets/MyWeeklyJourney.d.ts.map +1 -0
- package/dist/dashboard/widgets/MyWeeklyJourney.js +19 -0
- package/dist/dashboard/widgets/MyWeeklyJourney.js.map +1 -0
- package/dist/dashboard/widgets/PortfolioCostsKpi.d.ts +19 -0
- package/dist/dashboard/widgets/PortfolioCostsKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/PortfolioCostsKpi.js +12 -0
- package/dist/dashboard/widgets/PortfolioCostsKpi.js.map +1 -0
- package/dist/dashboard/widgets/PortfolioEffortKpi.d.ts +18 -0
- package/dist/dashboard/widgets/PortfolioEffortKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/PortfolioEffortKpi.js +8 -0
- package/dist/dashboard/widgets/PortfolioEffortKpi.js.map +1 -0
- package/dist/dashboard/widgets/PortfolioProjectsKpi.d.ts +22 -0
- package/dist/dashboard/widgets/PortfolioProjectsKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/PortfolioProjectsKpi.js +56 -0
- package/dist/dashboard/widgets/PortfolioProjectsKpi.js.map +1 -0
- package/dist/dashboard/widgets/PortfolioRiskKpi.d.ts +19 -0
- package/dist/dashboard/widgets/PortfolioRiskKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/PortfolioRiskKpi.js +11 -0
- package/dist/dashboard/widgets/PortfolioRiskKpi.js.map +1 -0
- package/dist/dashboard/widgets/ProjectStatusOverview.d.ts +19 -0
- package/dist/dashboard/widgets/ProjectStatusOverview.d.ts.map +1 -0
- package/dist/dashboard/widgets/ProjectStatusOverview.js +18 -0
- package/dist/dashboard/widgets/ProjectStatusOverview.js.map +1 -0
- package/dist/dashboard/widgets/StrategicDeadlines.d.ts +24 -0
- package/dist/dashboard/widgets/StrategicDeadlines.d.ts.map +1 -0
- package/dist/dashboard/widgets/StrategicDeadlines.js +22 -0
- package/dist/dashboard/widgets/StrategicDeadlines.js.map +1 -0
- package/dist/dashboard/widgets/TeamApprovalQueue.d.ts +24 -0
- package/dist/dashboard/widgets/TeamApprovalQueue.d.ts.map +1 -0
- package/dist/dashboard/widgets/TeamApprovalQueue.js +12 -0
- package/dist/dashboard/widgets/TeamApprovalQueue.js.map +1 -0
- package/dist/dashboard/widgets/TeamCapacityKpi.d.ts +18 -0
- package/dist/dashboard/widgets/TeamCapacityKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/TeamCapacityKpi.js +19 -0
- package/dist/dashboard/widgets/TeamCapacityKpi.js.map +1 -0
- package/dist/dashboard/widgets/TeamHeadcountKpi.d.ts +22 -0
- package/dist/dashboard/widgets/TeamHeadcountKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/TeamHeadcountKpi.js +56 -0
- package/dist/dashboard/widgets/TeamHeadcountKpi.js.map +1 -0
- package/dist/dashboard/widgets/TeamHoursKpi.d.ts +19 -0
- package/dist/dashboard/widgets/TeamHoursKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/TeamHoursKpi.js +13 -0
- package/dist/dashboard/widgets/TeamHoursKpi.js.map +1 -0
- package/dist/dashboard/widgets/TeamPendingApprovalsKpi.d.ts +20 -0
- package/dist/dashboard/widgets/TeamPendingApprovalsKpi.d.ts.map +1 -0
- package/dist/dashboard/widgets/TeamPendingApprovalsKpi.js +11 -0
- package/dist/dashboard/widgets/TeamPendingApprovalsKpi.js.map +1 -0
- package/dist/dashboard/widgets/TeamUtilizationOverview.d.ts +18 -0
- package/dist/dashboard/widgets/TeamUtilizationOverview.d.ts.map +1 -0
- package/dist/dashboard/widgets/TeamUtilizationOverview.js +17 -0
- package/dist/dashboard/widgets/TeamUtilizationOverview.js.map +1 -0
- package/dist/dashboard/widgets/TeamWorkloadAlerts.d.ts +24 -0
- package/dist/dashboard/widgets/TeamWorkloadAlerts.d.ts.map +1 -0
- package/dist/dashboard/widgets/TeamWorkloadAlerts.js +19 -0
- package/dist/dashboard/widgets/TeamWorkloadAlerts.js.map +1 -0
- package/dist/dashboard/widgets/index.d.ts +24 -0
- package/dist/dashboard/widgets/index.d.ts.map +1 -0
- package/dist/dashboard/widgets/index.js +54 -0
- package/dist/dashboard/widgets/index.js.map +1 -0
- package/dist/dto/create-collaborator.dto.d.ts +0 -1
- package/dist/dto/create-collaborator.dto.d.ts.map +1 -1
- package/dist/dto/create-collaborator.dto.js +0 -6
- package/dist/dto/create-collaborator.dto.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/operations.controller.d.ts +42 -0
- package/dist/operations.controller.d.ts.map +1 -1
- package/dist/operations.service.d.ts +182 -268
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +2147 -1337
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +345 -174
- package/dist/operations.service.spec.js.map +1 -1
- package/hedhog/data/dashboard_component.yaml +66 -0
- package/hedhog/data/dashboard_item.yaml +25 -25
- package/hedhog/data/route.yaml +61 -0
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +39 -99
- package/hedhog/frontend/app/_components/collaborator-picker.tsx.ejs +158 -0
- package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +314 -116
- package/hedhog/frontend/app/_components/project-assignments-tab.tsx.ejs +434 -449
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +289 -412
- package/hedhog/frontend/app/_components/project-file-attachments.tsx.ejs +371 -0
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +426 -374
- package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +803 -581
- package/hedhog/frontend/app/_components/task-file-attachments.tsx.ejs +4 -1
- package/hedhog/frontend/app/_components/task-form-fields.tsx.ejs +406 -0
- package/hedhog/frontend/app/_components/task-form-sheet.tsx.ejs +629 -784
- package/hedhog/frontend/app/_components/task-info-display.tsx.ejs +137 -0
- package/hedhog/frontend/app/_components/timesheet-entry-create-sheet.tsx.ejs +306 -0
- package/hedhog/frontend/app/_lib/api.ts.ejs +480 -476
- package/hedhog/frontend/app/_lib/types.ts.ejs +66 -5
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +0 -2
- package/hedhog/frontend/app/_lib/utils/task-ui.ts.ejs +43 -0
- package/hedhog/frontend/app/approvals/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +59 -8
- package/hedhog/frontend/app/contracts/page.tsx.ejs +29 -8
- package/hedhog/frontend/app/dashboard/widgets/CapacityDistribution.tsx.ejs +84 -0
- package/hedhog/frontend/app/dashboard/widgets/EffortByProject.tsx.ejs +85 -0
- package/hedhog/frontend/app/dashboard/widgets/HeadcountByArea.tsx.ejs +101 -0
- package/hedhog/frontend/app/dashboard/widgets/ManagedProjectsStatus.tsx.ejs +113 -0
- package/hedhog/frontend/app/dashboard/widgets/MyHoursPeriodKpi.tsx.ejs +87 -0
- package/hedhog/frontend/app/dashboard/widgets/MyOpenRequestsKpi.tsx.ejs +97 -0
- package/hedhog/frontend/app/dashboard/widgets/MyPendingRequestsList.tsx.ejs +99 -0
- package/hedhog/frontend/app/dashboard/widgets/MyProjectAllocationsKpi.tsx.ejs +78 -0
- package/hedhog/frontend/app/dashboard/widgets/MyQuickActions.tsx.ejs +130 -0
- package/hedhog/frontend/app/dashboard/widgets/MyRelevantDeadlines.tsx.ejs +144 -0
- package/hedhog/frontend/app/dashboard/widgets/MyTimesheetStatusKpi.tsx.ejs +78 -0
- package/hedhog/frontend/app/dashboard/widgets/MyWeeklyJourney.tsx.ejs +99 -0
- package/hedhog/frontend/app/dashboard/widgets/PortfolioCostsKpi.tsx.ejs +112 -0
- package/hedhog/frontend/app/dashboard/widgets/PortfolioEffortKpi.tsx.ejs +93 -0
- package/hedhog/frontend/app/dashboard/widgets/PortfolioProjectsKpi.tsx.ejs +96 -0
- package/hedhog/frontend/app/dashboard/widgets/PortfolioRiskKpi.tsx.ejs +115 -0
- package/hedhog/frontend/app/dashboard/widgets/ProjectStatusOverview.tsx.ejs +120 -0
- package/hedhog/frontend/app/dashboard/widgets/StrategicDeadlines.tsx.ejs +146 -0
- package/hedhog/frontend/app/dashboard/widgets/TeamApprovalQueue.tsx.ejs +108 -0
- package/hedhog/frontend/app/dashboard/widgets/TeamCapacityKpi.tsx.ejs +97 -0
- package/hedhog/frontend/app/dashboard/widgets/TeamHeadcountKpi.tsx.ejs +100 -0
- package/hedhog/frontend/app/dashboard/widgets/TeamHoursKpi.tsx.ejs +104 -0
- package/hedhog/frontend/app/dashboard/widgets/TeamPendingApprovalsKpi.tsx.ejs +110 -0
- package/hedhog/frontend/app/dashboard/widgets/TeamUtilizationOverview.tsx.ejs +115 -0
- package/hedhog/frontend/app/dashboard/widgets/TeamWorkloadAlerts.tsx.ejs +117 -0
- package/hedhog/frontend/app/dashboard/widgets/index.ts.ejs +26 -0
- package/hedhog/frontend/app/departments/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/my-projects/page.tsx.ejs +14 -10
- package/hedhog/frontend/app/my-tasks/page.tsx.ejs +328 -105
- package/hedhog/frontend/app/project-cost-categories/page.tsx.ejs +58 -52
- package/hedhog/frontend/app/project-cost-types/page.tsx.ejs +58 -51
- package/hedhog/frontend/app/projects/page.tsx.ejs +376 -30
- package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/time-off/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +10 -4
- package/hedhog/frontend/messages/en.json +238 -46
- package/hedhog/frontend/messages/operations/en.json +61 -52
- package/hedhog/frontend/messages/operations/pt.json +59 -43
- package/hedhog/frontend/messages/pt.json +238 -46
- package/hedhog/frontend/widgets/capacity-distribution.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/effort-by-project.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/headcount-by-area.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/index.ts.ejs +25 -0
- package/hedhog/frontend/widgets/managed-projects-status.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-hours-period-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-open-requests-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-pending-requests-list.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-project-allocations-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-quick-actions.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-relevant-deadlines.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-timesheet-status-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/my-weekly-journey.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/portfolio-costs-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/portfolio-effort-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/portfolio-projects-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/portfolio-risk-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/project-status-overview.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/shared-operations-widget.tsx.ejs +170 -0
- package/hedhog/frontend/widgets/strategic-deadlines.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/team-approval-queue.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/team-capacity-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/team-headcount-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/team-hours-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/team-pending-approvals-kpi.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/team-utilization-overview.tsx.ejs +17 -0
- package/hedhog/frontend/widgets/team-workload-alerts.tsx.ejs +17 -0
- package/hedhog/table/operations_collaborator.yaml +8 -13
- package/hedhog/table/operations_project.yaml +1 -1
- package/hedhog/table/operations_project_file.yaml +23 -0
- package/hedhog/table/operations_task.yaml +76 -69
- package/hedhog/table/operations_task_activity.yaml +51 -0
- package/package.json +6 -5
- package/src/controllers/operations-projects.controller.ts +41 -8
- package/src/controllers/operations-tasks.controller.ts +156 -166
- package/src/dashboard/README.md +214 -0
- package/src/dashboard/components/DashboardLayout.tsx +131 -0
- package/src/dashboard/components/widget-registry.ts +255 -0
- package/src/dashboard/hooks/useDashboardData.ts +29 -0
- package/src/dashboard/types/widgets.types.ts +237 -0
- package/src/dashboard/widgets/CapacityDistribution.tsx +56 -0
- package/src/dashboard/widgets/EffortByProject.tsx +51 -0
- package/src/dashboard/widgets/HeadcountByArea.tsx +57 -0
- package/src/dashboard/widgets/ManagedProjectsStatus.tsx +53 -0
- package/src/dashboard/widgets/MyHoursPeriodKpi.tsx +87 -0
- package/src/dashboard/widgets/MyOpenRequestsKpi.tsx +51 -0
- package/src/dashboard/widgets/MyPendingRequestsList.tsx +63 -0
- package/src/dashboard/widgets/MyProjectAllocationsKpi.tsx +57 -0
- package/src/dashboard/widgets/MyQuickActions.tsx +62 -0
- package/src/dashboard/widgets/MyRelevantDeadlines.tsx +84 -0
- package/src/dashboard/widgets/MyTimesheetStatusKpi.tsx +65 -0
- package/src/dashboard/widgets/MyWeeklyJourney.tsx +57 -0
- package/src/dashboard/widgets/PortfolioCostsKpi.tsx +48 -0
- package/src/dashboard/widgets/PortfolioEffortKpi.tsx +41 -0
- package/src/dashboard/widgets/PortfolioRiskKpi.tsx +50 -0
- package/src/dashboard/widgets/ProjectStatusOverview.tsx +52 -0
- package/src/dashboard/widgets/StrategicDeadlines.tsx +93 -0
- package/src/dashboard/widgets/TeamApprovalQueue.tsx +70 -0
- package/src/dashboard/widgets/TeamCapacityKpi.tsx +50 -0
- package/src/dashboard/widgets/TeamHoursKpi.tsx +51 -0
- package/src/dashboard/widgets/TeamPendingApprovalsKpi.tsx +53 -0
- package/src/dashboard/widgets/TeamUtilizationOverview.tsx +62 -0
- package/src/dashboard/widgets/TeamWorkloadAlerts.tsx +81 -0
- package/src/dashboard/widgets/index.ts +26 -0
- package/src/dto/create-collaborator.dto.ts +4 -11
- package/src/index.ts +3 -0
- package/src/operations.service.spec.ts +988 -764
- package/src/operations.service.ts +4277 -2535
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { PersonFormSheet } from '@/app/(app)/(libraries)/contact/person/_components/person-form-sheet';
|
|
4
|
+
import type {
|
|
5
|
+
ContactTypeOption,
|
|
6
|
+
DocumentTypeOption,
|
|
7
|
+
Person,
|
|
8
|
+
} from '@/app/(app)/(libraries)/contact/person/_components/person-types';
|
|
3
9
|
import { EmptyState, Page } from '@/components/entity-list';
|
|
4
10
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
5
11
|
import { Button } from '@/components/ui/button';
|
|
@@ -43,6 +49,12 @@ import {
|
|
|
43
49
|
SheetHeader,
|
|
44
50
|
SheetTitle,
|
|
45
51
|
} from '@/components/ui/sheet';
|
|
52
|
+
import {
|
|
53
|
+
Tabs,
|
|
54
|
+
TabsContent,
|
|
55
|
+
TabsList,
|
|
56
|
+
TabsTrigger,
|
|
57
|
+
} from '@/components/ui/tabs';
|
|
46
58
|
import { Textarea } from '@/components/ui/textarea';
|
|
47
59
|
import {
|
|
48
60
|
Tooltip,
|
|
@@ -66,7 +78,7 @@ import {
|
|
|
66
78
|
import { useTranslations } from 'next-intl';
|
|
67
79
|
import Link from 'next/link';
|
|
68
80
|
import { useRouter } from 'next/navigation';
|
|
69
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
81
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
70
82
|
import { useForm } from 'react-hook-form';
|
|
71
83
|
import { z } from 'zod';
|
|
72
84
|
import { PersonPicker } from '../../contact/_components/person-picker';
|
|
@@ -80,7 +92,6 @@ import type {
|
|
|
80
92
|
OperationsCollaborator,
|
|
81
93
|
OperationsContract,
|
|
82
94
|
OperationsProjectDetails,
|
|
83
|
-
OperationsProjectRole,
|
|
84
95
|
} from '../_lib/types';
|
|
85
96
|
import { formatEnumLabel } from '../_lib/utils/format';
|
|
86
97
|
import {
|
|
@@ -91,14 +102,13 @@ import {
|
|
|
91
102
|
import { ContractFormScreen } from './contract-form-screen';
|
|
92
103
|
import { DepartmentPicker } from './department-picker';
|
|
93
104
|
import { OperationsHeader } from './operations-header';
|
|
105
|
+
import { ProjectFileAttachments } from './project-file-attachments';
|
|
94
106
|
|
|
95
107
|
const OPTION_PAGE_SIZE = 12;
|
|
96
108
|
|
|
97
109
|
type TeamAssignmentState = {
|
|
98
110
|
collaboratorId: number;
|
|
99
111
|
selected: boolean;
|
|
100
|
-
projectRoleId: string;
|
|
101
|
-
roleLabel: string;
|
|
102
112
|
weeklyHours: string;
|
|
103
113
|
allocationPercent: string;
|
|
104
114
|
status: string;
|
|
@@ -191,8 +201,6 @@ function buildEmptyForm(
|
|
|
191
201
|
teamAssignments: collaborators.map((collaborator) => ({
|
|
192
202
|
collaboratorId: collaborator.id,
|
|
193
203
|
selected: false,
|
|
194
|
-
projectRoleId: 'none',
|
|
195
|
-
roleLabel: '',
|
|
196
204
|
weeklyHours: '',
|
|
197
205
|
allocationPercent: '',
|
|
198
206
|
status: 'active',
|
|
@@ -265,10 +273,6 @@ function toFormState(
|
|
|
265
273
|
return {
|
|
266
274
|
collaboratorId: collaborator.id,
|
|
267
275
|
selected: Boolean(assignment),
|
|
268
|
-
projectRoleId: assignment?.projectRoleId
|
|
269
|
-
? String(assignment.projectRoleId)
|
|
270
|
-
: 'none',
|
|
271
|
-
roleLabel: assignment?.roleLabel ?? '',
|
|
272
276
|
weeklyHours:
|
|
273
277
|
assignment?.weeklyHours !== null &&
|
|
274
278
|
assignment?.weeklyHours !== undefined
|
|
@@ -645,6 +649,9 @@ export function ProjectFormScreen({
|
|
|
645
649
|
const access = useOperationsAccess();
|
|
646
650
|
const router = useRouter();
|
|
647
651
|
const [assignmentSearch, setAssignmentSearch] = useState('');
|
|
652
|
+
const personSheetModeRef = useRef<'create' | 'edit'>('edit');
|
|
653
|
+
const [personSheetOpen, setPersonSheetOpen] = useState(false);
|
|
654
|
+
const [personToEdit, setPersonToEdit] = useState<Person | null>(null);
|
|
648
655
|
const isSheetMode = Boolean(onCancel);
|
|
649
656
|
const isCreateMode = !projectId;
|
|
650
657
|
const [codeAutoMode, setCodeAutoMode] = useState(isCreateMode);
|
|
@@ -684,8 +691,6 @@ export function ProjectFormScreen({
|
|
|
684
691
|
z.object({
|
|
685
692
|
collaboratorId: z.number(),
|
|
686
693
|
selected: z.boolean(),
|
|
687
|
-
projectRoleId: z.string(),
|
|
688
|
-
roleLabel: z.string(),
|
|
689
694
|
weeklyHours: z.string(),
|
|
690
695
|
allocationPercent: z.string(),
|
|
691
696
|
status: z.string(),
|
|
@@ -694,14 +699,8 @@ export function ProjectFormScreen({
|
|
|
694
699
|
})
|
|
695
700
|
)
|
|
696
701
|
.superRefine((assignments, ctx) => {
|
|
697
|
-
assignments.forEach((
|
|
698
|
-
|
|
699
|
-
ctx.addIssue({
|
|
700
|
-
code: z.ZodIssueCode.custom,
|
|
701
|
-
message: t('messages.roleRequired'),
|
|
702
|
-
path: [index, 'roleLabel'],
|
|
703
|
-
});
|
|
704
|
-
}
|
|
702
|
+
assignments.forEach((_assignment, _index) => {
|
|
703
|
+
// role validation removed
|
|
705
704
|
});
|
|
706
705
|
}),
|
|
707
706
|
}),
|
|
@@ -756,18 +755,32 @@ export function ProjectFormScreen({
|
|
|
756
755
|
fetchOperations<OperationsContract[]>(request, '/operations/contracts'),
|
|
757
756
|
});
|
|
758
757
|
|
|
759
|
-
const { data:
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
758
|
+
const { data: contactTypes = [] } = useQuery<ContactTypeOption[]>({
|
|
759
|
+
queryKey: ['contact-person-contact-types', currentLocaleCode],
|
|
760
|
+
enabled: personSheetOpen,
|
|
761
|
+
queryFn: async () => {
|
|
762
|
+
const response = await request<{ data: ContactTypeOption[] }>({
|
|
763
|
+
url: '/person-contact-type?pageSize=100',
|
|
764
|
+
method: 'GET',
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
return response.data.data || [];
|
|
768
|
+
},
|
|
769
|
+
placeholderData: (previous) => previous ?? [],
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
const { data: documentTypes = [] } = useQuery<DocumentTypeOption[]>({
|
|
773
|
+
queryKey: ['contact-person-document-types', currentLocaleCode],
|
|
774
|
+
enabled: personSheetOpen,
|
|
775
|
+
queryFn: async () => {
|
|
776
|
+
const response = await request<{ data: DocumentTypeOption[] }>({
|
|
777
|
+
url: '/person-document-type?pageSize=100',
|
|
778
|
+
method: 'GET',
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
return response.data.data || [];
|
|
782
|
+
},
|
|
783
|
+
placeholderData: (previous) => previous ?? [],
|
|
771
784
|
});
|
|
772
785
|
|
|
773
786
|
const { data: project, isLoading: isLoadingProject } =
|
|
@@ -875,39 +888,6 @@ export function ProjectFormScreen({
|
|
|
875
888
|
[availableCollaborators]
|
|
876
889
|
);
|
|
877
890
|
|
|
878
|
-
const projectRoleOptions = useMemo(
|
|
879
|
-
() =>
|
|
880
|
-
projectRoles.map((role) => ({
|
|
881
|
-
id: role.id,
|
|
882
|
-
name: role.name,
|
|
883
|
-
code: role.code ?? null,
|
|
884
|
-
description: role.description ?? null,
|
|
885
|
-
})),
|
|
886
|
-
[projectRoles]
|
|
887
|
-
);
|
|
888
|
-
|
|
889
|
-
const createProjectRole = async (projectRoleName: string) => {
|
|
890
|
-
try {
|
|
891
|
-
const createdRole = await mutateOperations<OperationsProjectRole>(
|
|
892
|
-
request,
|
|
893
|
-
'/operations/project-roles',
|
|
894
|
-
'POST',
|
|
895
|
-
{
|
|
896
|
-
name: projectRoleName,
|
|
897
|
-
}
|
|
898
|
-
);
|
|
899
|
-
|
|
900
|
-
await refetchProjectRoles();
|
|
901
|
-
return createdRole;
|
|
902
|
-
} catch (error) {
|
|
903
|
-
showToastHandler?.(
|
|
904
|
-
'error',
|
|
905
|
-
getOperationsErrorMessage(error, t('messages.projectRoleSaveError'))
|
|
906
|
-
);
|
|
907
|
-
return null;
|
|
908
|
-
}
|
|
909
|
-
};
|
|
910
|
-
|
|
911
891
|
const selectedAssignmentsCount = useMemo(
|
|
912
892
|
() =>
|
|
913
893
|
form.teamAssignments.filter((assignment) => assignment.selected).length,
|
|
@@ -993,11 +973,6 @@ export function ProjectFormScreen({
|
|
|
993
973
|
.filter((assignment) => assignment.selected)
|
|
994
974
|
.map((assignment) => ({
|
|
995
975
|
collaboratorId: assignment.collaboratorId,
|
|
996
|
-
projectRoleId:
|
|
997
|
-
assignment.projectRoleId === 'none'
|
|
998
|
-
? null
|
|
999
|
-
: parseNumberInput(assignment.projectRoleId),
|
|
1000
|
-
roleLabel: trimToNull(assignment.roleLabel),
|
|
1001
976
|
weeklyHours: parseNumberInput(assignment.weeklyHours),
|
|
1002
977
|
allocationPercent: parseNumberInput(assignment.allocationPercent),
|
|
1003
978
|
status: assignment.status,
|
|
@@ -1047,6 +1022,44 @@ export function ProjectFormScreen({
|
|
|
1047
1022
|
showToastHandler?.('error', t('messages.requiredFields'));
|
|
1048
1023
|
};
|
|
1049
1024
|
|
|
1025
|
+
const resolvePersonDisplayName = (person?: Person | null) => {
|
|
1026
|
+
const tradeName = person?.trade_name?.trim();
|
|
1027
|
+
if (tradeName) {
|
|
1028
|
+
return tradeName;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
return person?.name?.trim() || '';
|
|
1032
|
+
};
|
|
1033
|
+
|
|
1034
|
+
const handleOpenPersonEditSheet = async (personId?: number | null) => {
|
|
1035
|
+
const targetPersonId =
|
|
1036
|
+
personId && personId > 0
|
|
1037
|
+
? personId
|
|
1038
|
+
: parseNumberInput(formMethods.getValues('clientPersonId'));
|
|
1039
|
+
|
|
1040
|
+
if (!targetPersonId) {
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
try {
|
|
1045
|
+
const response = await request<Person>({
|
|
1046
|
+
url: `/person/${targetPersonId}`,
|
|
1047
|
+
method: 'GET',
|
|
1048
|
+
});
|
|
1049
|
+
personSheetModeRef.current = 'edit';
|
|
1050
|
+
setPersonToEdit(response.data);
|
|
1051
|
+
setPersonSheetOpen(true);
|
|
1052
|
+
} catch {
|
|
1053
|
+
showToastHandler?.('error', t('messages.updateError'));
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
const handleOpenPersonCreateSheet = () => {
|
|
1058
|
+
personSheetModeRef.current = 'create';
|
|
1059
|
+
setPersonToEdit(null);
|
|
1060
|
+
setPersonSheetOpen(true);
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1050
1063
|
const noAccessState = (
|
|
1051
1064
|
<EmptyState
|
|
1052
1065
|
icon={<FolderKanban className="size-12" />}
|
|
@@ -1171,6 +1184,12 @@ export function ProjectFormScreen({
|
|
|
1171
1184
|
personId ? String(personId) : ''
|
|
1172
1185
|
);
|
|
1173
1186
|
}}
|
|
1187
|
+
showEditButton
|
|
1188
|
+
editAriaLabel={commonT('actions.editPersonCrm')}
|
|
1189
|
+
onEditSelection={(personId) =>
|
|
1190
|
+
void handleOpenPersonEditSheet(personId)
|
|
1191
|
+
}
|
|
1192
|
+
onCreateNew={() => handleOpenPersonCreateSheet()}
|
|
1174
1193
|
personTypeFilter="all"
|
|
1175
1194
|
createType="company"
|
|
1176
1195
|
/>
|
|
@@ -1296,9 +1315,6 @@ export function ProjectFormScreen({
|
|
|
1296
1315
|
<SelectItem value="active">
|
|
1297
1316
|
{t('options.statuses.active')}
|
|
1298
1317
|
</SelectItem>
|
|
1299
|
-
<SelectItem value="at_risk">
|
|
1300
|
-
{t('options.statuses.at_risk')}
|
|
1301
|
-
</SelectItem>
|
|
1302
1318
|
<SelectItem value="paused">
|
|
1303
1319
|
{t('options.statuses.paused')}
|
|
1304
1320
|
</SelectItem>
|
|
@@ -1355,321 +1371,316 @@ export function ProjectFormScreen({
|
|
|
1355
1371
|
|
|
1356
1372
|
{!isCreateMode ? (
|
|
1357
1373
|
<>
|
|
1358
|
-
<
|
|
1359
|
-
<
|
|
1360
|
-
<
|
|
1374
|
+
<Tabs defaultValue="financials" className="space-y-3">
|
|
1375
|
+
<TabsList className="grid w-full grid-cols-3">
|
|
1376
|
+
<TabsTrigger value="financials">
|
|
1361
1377
|
{t('sections.financials')}
|
|
1362
|
-
</
|
|
1363
|
-
<
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
<
|
|
1368
|
-
<div className="
|
|
1369
|
-
<
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
setForm((current) => ({
|
|
1376
|
-
...current,
|
|
1377
|
-
budgetAmount: value !== null ? String(value) : '',
|
|
1378
|
-
}))
|
|
1379
|
-
}
|
|
1380
|
-
/>
|
|
1381
|
-
</div>
|
|
1382
|
-
<div className="min-w-0 space-y-2">
|
|
1383
|
-
<FieldLabel
|
|
1384
|
-
label={commonT('labels.monthlyHourCap')}
|
|
1385
|
-
hint={t('hints.monthlyHourCap')}
|
|
1386
|
-
/>
|
|
1387
|
-
<Input
|
|
1388
|
-
type="text"
|
|
1389
|
-
inputMode="numeric"
|
|
1390
|
-
placeholder={t('placeholders.monthlyHourCap')}
|
|
1391
|
-
value={form.monthlyHourCap}
|
|
1392
|
-
onChange={(event) => {
|
|
1393
|
-
const raw = event.target.value.replace(/[^0-9]/g, '');
|
|
1394
|
-
setForm((current) => ({
|
|
1395
|
-
...current,
|
|
1396
|
-
monthlyHourCap: raw,
|
|
1397
|
-
}));
|
|
1398
|
-
}}
|
|
1399
|
-
/>
|
|
1400
|
-
</div>
|
|
1401
|
-
<div className="min-w-0 space-y-2">
|
|
1402
|
-
<FieldLabel label={commonT('labels.billingModel')} />
|
|
1403
|
-
<Select
|
|
1404
|
-
value={form.billingModel}
|
|
1405
|
-
onValueChange={(value) =>
|
|
1406
|
-
setForm((current) => ({
|
|
1407
|
-
...current,
|
|
1408
|
-
billingModel: value,
|
|
1409
|
-
}))
|
|
1410
|
-
}
|
|
1411
|
-
>
|
|
1412
|
-
<SelectTrigger className="w-full">
|
|
1413
|
-
<SelectValue />
|
|
1414
|
-
</SelectTrigger>
|
|
1415
|
-
<SelectContent>
|
|
1416
|
-
<SelectItem value="time_and_material">
|
|
1417
|
-
{t('options.billingModels.time_and_material')}
|
|
1418
|
-
</SelectItem>
|
|
1419
|
-
<SelectItem value="monthly_retainer">
|
|
1420
|
-
{t('options.billingModels.monthly_retainer')}
|
|
1421
|
-
</SelectItem>
|
|
1422
|
-
<SelectItem value="fixed_price">
|
|
1423
|
-
{t('options.billingModels.fixed_price')}
|
|
1424
|
-
</SelectItem>
|
|
1425
|
-
</SelectContent>
|
|
1426
|
-
</Select>
|
|
1378
|
+
</TabsTrigger>
|
|
1379
|
+
<TabsTrigger value="team">{t('sections.team')}</TabsTrigger>
|
|
1380
|
+
<TabsTrigger value="files">{t('sections.files')}</TabsTrigger>
|
|
1381
|
+
</TabsList>
|
|
1382
|
+
|
|
1383
|
+
<TabsContent value="financials" className="space-y-3">
|
|
1384
|
+
<div className="space-y-0.5">
|
|
1385
|
+
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
1386
|
+
{t('sections.financials')}
|
|
1387
|
+
</h3>
|
|
1388
|
+
<p className="text-[11px] text-muted-foreground/80">
|
|
1389
|
+
{t('sections.financialsDescription')}
|
|
1390
|
+
</p>
|
|
1427
1391
|
</div>
|
|
1428
|
-
<div className="min-w-0
|
|
1429
|
-
<
|
|
1430
|
-
label={commonT('labels.
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1392
|
+
<div className="grid min-w-0 gap-3 md:grid-cols-2 xl:grid-cols-4">
|
|
1393
|
+
<div className="min-w-0 space-y-2">
|
|
1394
|
+
<FieldLabel label={commonT('labels.budget')} />
|
|
1395
|
+
<InputMoney
|
|
1396
|
+
value={
|
|
1397
|
+
form.budgetAmount === ''
|
|
1398
|
+
? ''
|
|
1399
|
+
: Number(form.budgetAmount)
|
|
1400
|
+
}
|
|
1401
|
+
onValueChange={(value) =>
|
|
1402
|
+
setForm((current) => ({
|
|
1403
|
+
...current,
|
|
1404
|
+
budgetAmount: value !== null ? String(value) : '',
|
|
1405
|
+
}))
|
|
1406
|
+
}
|
|
1407
|
+
/>
|
|
1408
|
+
</div>
|
|
1409
|
+
<div className="min-w-0 space-y-2">
|
|
1410
|
+
<FieldLabel
|
|
1411
|
+
label={commonT('labels.monthlyHourCap')}
|
|
1412
|
+
hint={t('hints.monthlyHourCap')}
|
|
1413
|
+
/>
|
|
1414
|
+
<Input
|
|
1415
|
+
type="text"
|
|
1416
|
+
inputMode="numeric"
|
|
1417
|
+
placeholder={t('placeholders.monthlyHourCap')}
|
|
1418
|
+
value={form.monthlyHourCap}
|
|
1419
|
+
onChange={(event) => {
|
|
1420
|
+
const raw = event.target.value.replace(/[^0-9]/g, '');
|
|
1421
|
+
setForm((current) => ({
|
|
1422
|
+
...current,
|
|
1423
|
+
monthlyHourCap: raw,
|
|
1424
|
+
}));
|
|
1425
|
+
}}
|
|
1426
|
+
/>
|
|
1427
|
+
</div>
|
|
1428
|
+
<div className="min-w-0 space-y-2">
|
|
1429
|
+
<FieldLabel label={commonT('labels.billingModel')} />
|
|
1430
|
+
<Select
|
|
1431
|
+
value={form.billingModel}
|
|
1432
|
+
onValueChange={(value) =>
|
|
1433
|
+
setForm((current) => ({
|
|
1434
|
+
...current,
|
|
1435
|
+
billingModel: value,
|
|
1436
|
+
}))
|
|
1437
|
+
}
|
|
1438
|
+
>
|
|
1439
|
+
<SelectTrigger className="w-full">
|
|
1440
|
+
<SelectValue />
|
|
1441
|
+
</SelectTrigger>
|
|
1442
|
+
<SelectContent>
|
|
1443
|
+
<SelectItem value="time_and_material">
|
|
1444
|
+
{t('options.billingModels.time_and_material')}
|
|
1445
|
+
</SelectItem>
|
|
1446
|
+
<SelectItem value="monthly_retainer">
|
|
1447
|
+
{t('options.billingModels.monthly_retainer')}
|
|
1448
|
+
</SelectItem>
|
|
1449
|
+
<SelectItem value="fixed_price">
|
|
1450
|
+
{t('options.billingModels.fixed_price')}
|
|
1451
|
+
</SelectItem>
|
|
1452
|
+
</SelectContent>
|
|
1453
|
+
</Select>
|
|
1454
|
+
</div>
|
|
1455
|
+
<div className="min-w-0 space-y-2">
|
|
1456
|
+
<FieldLabel
|
|
1457
|
+
label={commonT('labels.contract')}
|
|
1458
|
+
hint={t('hints.contract')}
|
|
1459
|
+
/>
|
|
1460
|
+
<ContractSelectWithCreate
|
|
1461
|
+
label=""
|
|
1462
|
+
value={form.contractId}
|
|
1463
|
+
contracts={availableContracts}
|
|
1464
|
+
selectPlaceholder={commonT('labels.notAssigned')}
|
|
1465
|
+
searchPlaceholder={t('placeholders.contractSearch')}
|
|
1466
|
+
onChange={(value) =>
|
|
1467
|
+
setForm((current) => ({
|
|
1468
|
+
...current,
|
|
1469
|
+
contractId: value,
|
|
1470
|
+
}))
|
|
1471
|
+
}
|
|
1472
|
+
onCreated={async (contract) => {
|
|
1473
|
+
await refetchContracts();
|
|
1474
|
+
setForm((current) => ({
|
|
1475
|
+
...current,
|
|
1476
|
+
contractId: contract?.id
|
|
1477
|
+
? String(contract.id)
|
|
1478
|
+
: current.contractId,
|
|
1479
|
+
billingModel:
|
|
1480
|
+
contract?.billingModel ?? current.billingModel,
|
|
1481
|
+
monthlyHourCap:
|
|
1482
|
+
contract?.monthlyHourCap !== null &&
|
|
1483
|
+
contract?.monthlyHourCap !== undefined
|
|
1484
|
+
? String(contract.monthlyHourCap)
|
|
1485
|
+
: current.monthlyHourCap,
|
|
1486
|
+
}));
|
|
1487
|
+
}}
|
|
1488
|
+
initialValues={{
|
|
1489
|
+
code: form.code ? `PRJ-${form.code}` : '',
|
|
1490
|
+
name: form.name
|
|
1491
|
+
? `${form.name} Service Agreement`
|
|
1492
|
+
: '',
|
|
1493
|
+
clientName: form.clientName,
|
|
1494
|
+
contractCategory: 'client',
|
|
1495
|
+
contractType: 'service_agreement',
|
|
1496
|
+
signatureStatus: 'not_started',
|
|
1497
|
+
billingModel: form.billingModel,
|
|
1498
|
+
budgetAmount: form.budgetAmount,
|
|
1499
|
+
monthlyHourCap: form.monthlyHourCap,
|
|
1500
|
+
startDate: form.startDate,
|
|
1501
|
+
endDate: form.endDate,
|
|
1502
|
+
description: form.summary,
|
|
1503
|
+
contentHtml: '',
|
|
1504
|
+
}}
|
|
1505
|
+
/>
|
|
1506
|
+
</div>
|
|
1474
1507
|
</div>
|
|
1475
|
-
</
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
</p>
|
|
1488
|
-
</div>
|
|
1489
|
-
<div className="space-y-3">
|
|
1490
|
-
<div className="relative">
|
|
1491
|
-
<Search className="pointer-events-none absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
1492
|
-
<Input
|
|
1493
|
-
className="pl-9"
|
|
1494
|
-
value={assignmentSearch}
|
|
1495
|
-
placeholder={t('placeholders.assignmentSearch')}
|
|
1496
|
-
onChange={(event) =>
|
|
1497
|
-
setAssignmentSearch(event.target.value)
|
|
1498
|
-
}
|
|
1499
|
-
/>
|
|
1508
|
+
</TabsContent>
|
|
1509
|
+
|
|
1510
|
+
<TabsContent value="team" className="space-y-3">
|
|
1511
|
+
<div className="space-y-0.5">
|
|
1512
|
+
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
1513
|
+
{t('sections.team')}
|
|
1514
|
+
</h3>
|
|
1515
|
+
<p className="text-[11px] text-muted-foreground/80">
|
|
1516
|
+
{t('sections.teamDescription', {
|
|
1517
|
+
count: selectedAssignmentsCount,
|
|
1518
|
+
})}
|
|
1519
|
+
</p>
|
|
1500
1520
|
</div>
|
|
1521
|
+
<div className="space-y-3">
|
|
1522
|
+
<div className="relative">
|
|
1523
|
+
<Search className="pointer-events-none absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
1524
|
+
<Input
|
|
1525
|
+
className="pl-9"
|
|
1526
|
+
value={assignmentSearch}
|
|
1527
|
+
placeholder={t('placeholders.assignmentSearch')}
|
|
1528
|
+
onChange={(event) =>
|
|
1529
|
+
setAssignmentSearch(event.target.value)
|
|
1530
|
+
}
|
|
1531
|
+
/>
|
|
1532
|
+
</div>
|
|
1501
1533
|
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
const assignmentIndex = form.teamAssignments.findIndex(
|
|
1508
|
-
(item) =>
|
|
1509
|
-
item.collaboratorId === assignment.collaboratorId
|
|
1510
|
-
);
|
|
1511
|
-
const roleError =
|
|
1512
|
-
assignmentIndex >= 0
|
|
1513
|
-
? formMethods.formState.errors.teamAssignments?.[
|
|
1514
|
-
assignmentIndex
|
|
1515
|
-
]?.roleLabel
|
|
1516
|
-
: undefined;
|
|
1517
|
-
|
|
1518
|
-
if (!collaborator) {
|
|
1519
|
-
return null;
|
|
1520
|
-
}
|
|
1534
|
+
<div className="space-y-2">
|
|
1535
|
+
{filteredAssignments.map((assignment) => {
|
|
1536
|
+
const collaborator = availableCollaborators.find(
|
|
1537
|
+
(item) => item.id === assignment.collaboratorId
|
|
1538
|
+
);
|
|
1521
1539
|
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
<
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
/>
|
|
1536
|
-
<div className="min-w-0">
|
|
1537
|
-
<div className="truncate font-medium">
|
|
1538
|
-
{collaborator.displayName}
|
|
1539
|
-
</div>
|
|
1540
|
-
<div className="truncate text-xs text-muted-foreground">
|
|
1541
|
-
{[
|
|
1542
|
-
collaborator.department,
|
|
1543
|
-
collaborator.title,
|
|
1544
|
-
collaborator.code,
|
|
1545
|
-
]
|
|
1546
|
-
.filter(Boolean)
|
|
1547
|
-
.join(' • ') || commonT('labels.notAvailable')}
|
|
1548
|
-
</div>
|
|
1549
|
-
</div>
|
|
1550
|
-
</label>
|
|
1551
|
-
<div className="space-y-1">
|
|
1552
|
-
<DepartmentPicker
|
|
1553
|
-
label=""
|
|
1554
|
-
value={assignment.roleLabel}
|
|
1555
|
-
options={projectRoleOptions}
|
|
1556
|
-
disabled={!assignment.selected}
|
|
1557
|
-
selectPlaceholder={t('placeholders.roleLabel')}
|
|
1558
|
-
createDescription={t('fields.roleLabel')}
|
|
1559
|
-
createPlaceholder={t(
|
|
1560
|
-
'placeholders.roleLabelCreate'
|
|
1561
|
-
)}
|
|
1562
|
-
onChange={(role) =>
|
|
1563
|
-
updateAssignment(assignment.collaboratorId, {
|
|
1564
|
-
projectRoleId: role.id
|
|
1565
|
-
? String(role.id)
|
|
1566
|
-
: 'none',
|
|
1567
|
-
roleLabel: role.name,
|
|
1568
|
-
})
|
|
1569
|
-
}
|
|
1570
|
-
onCreate={createProjectRole}
|
|
1571
|
-
/>
|
|
1572
|
-
{roleError?.message ? (
|
|
1573
|
-
<p className="text-sm text-destructive">
|
|
1574
|
-
{String(roleError.message)}
|
|
1575
|
-
</p>
|
|
1576
|
-
) : null}
|
|
1577
|
-
</div>
|
|
1578
|
-
<Input
|
|
1579
|
-
className="h-9 mt-2 self-start"
|
|
1580
|
-
type="number"
|
|
1581
|
-
min="0"
|
|
1582
|
-
step="0.5"
|
|
1583
|
-
placeholder={t('fields.weeklyHours')}
|
|
1584
|
-
value={assignment.weeklyHours}
|
|
1585
|
-
disabled={!assignment.selected}
|
|
1586
|
-
onChange={(event) => {
|
|
1587
|
-
const hours = event.target.value;
|
|
1588
|
-
const updates: Partial<TeamAssignmentState> = {
|
|
1589
|
-
weeklyHours: hours,
|
|
1590
|
-
};
|
|
1591
|
-
const cap = collaborator.weeklyCapacityHours;
|
|
1592
|
-
if (cap && hours && Number(hours) > 0) {
|
|
1593
|
-
const pct = Math.round(
|
|
1594
|
-
(Number(hours) / cap) * 100
|
|
1595
|
-
);
|
|
1596
|
-
updates.allocationPercent = String(
|
|
1597
|
-
Math.min(pct, 100)
|
|
1598
|
-
);
|
|
1599
|
-
}
|
|
1600
|
-
updateAssignment(
|
|
1601
|
-
assignment.collaboratorId,
|
|
1602
|
-
updates
|
|
1603
|
-
);
|
|
1604
|
-
}}
|
|
1605
|
-
/>
|
|
1606
|
-
<Input
|
|
1607
|
-
className="h-9 mt-2 self-start"
|
|
1608
|
-
type="text"
|
|
1609
|
-
inputMode="decimal"
|
|
1610
|
-
placeholder={t('fields.allocationPercent')}
|
|
1611
|
-
value={assignment.allocationPercent}
|
|
1612
|
-
disabled={!assignment.selected}
|
|
1613
|
-
onChange={(event) => {
|
|
1614
|
-
const pct = normalizePercentInput(
|
|
1615
|
-
event.target.value
|
|
1616
|
-
);
|
|
1617
|
-
const updates: Partial<TeamAssignmentState> = {
|
|
1618
|
-
allocationPercent: pct,
|
|
1619
|
-
};
|
|
1620
|
-
const cap = collaborator.weeklyCapacityHours;
|
|
1621
|
-
if (cap && pct && Number(pct) > 0) {
|
|
1622
|
-
const hours = Math.round(
|
|
1623
|
-
(Number(pct) / 100) * cap
|
|
1624
|
-
);
|
|
1625
|
-
updates.weeklyHours = String(hours);
|
|
1626
|
-
}
|
|
1627
|
-
updateAssignment(
|
|
1628
|
-
assignment.collaboratorId,
|
|
1629
|
-
updates
|
|
1630
|
-
);
|
|
1631
|
-
}}
|
|
1632
|
-
/>
|
|
1633
|
-
<div className="flex items-center gap-2 xl:col-span-4">
|
|
1634
|
-
<div className="flex-1">
|
|
1635
|
-
<label className="text-xs text-muted-foreground">
|
|
1636
|
-
{t('fields.assignmentStartDate')}
|
|
1637
|
-
</label>
|
|
1638
|
-
<Input
|
|
1639
|
-
className="h-9"
|
|
1640
|
-
type="date"
|
|
1641
|
-
value={assignment.startDate}
|
|
1642
|
-
disabled={!assignment.selected}
|
|
1643
|
-
onChange={(event) =>
|
|
1540
|
+
if (!collaborator) {
|
|
1541
|
+
return null;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
return (
|
|
1545
|
+
<div
|
|
1546
|
+
key={assignment.collaboratorId}
|
|
1547
|
+
className="grid min-w-0 gap-2 rounded-lg border px-3 py-2 xl:grid-cols-[minmax(0,2fr)_110px_110px]"
|
|
1548
|
+
>
|
|
1549
|
+
<label className="flex cursor-pointer items-start gap-3 py-1">
|
|
1550
|
+
<Checkbox
|
|
1551
|
+
checked={assignment.selected}
|
|
1552
|
+
onCheckedChange={(checked) =>
|
|
1644
1553
|
updateAssignment(assignment.collaboratorId, {
|
|
1645
|
-
|
|
1554
|
+
selected: checked === true,
|
|
1646
1555
|
})
|
|
1647
1556
|
}
|
|
1648
1557
|
/>
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1558
|
+
<div className="min-w-0">
|
|
1559
|
+
<div className="truncate font-medium">
|
|
1560
|
+
{collaborator.displayName}
|
|
1561
|
+
</div>
|
|
1562
|
+
<div className="truncate text-xs text-muted-foreground">
|
|
1563
|
+
{[
|
|
1564
|
+
collaborator.department,
|
|
1565
|
+
collaborator.title,
|
|
1566
|
+
collaborator.code,
|
|
1567
|
+
]
|
|
1568
|
+
.filter(Boolean)
|
|
1569
|
+
.join(' • ') ||
|
|
1570
|
+
commonT('labels.notAvailable')}
|
|
1571
|
+
</div>
|
|
1572
|
+
</div>
|
|
1573
|
+
</label>
|
|
1574
|
+
<Input
|
|
1575
|
+
className="h-9 mt-2 self-start"
|
|
1576
|
+
type="number"
|
|
1577
|
+
min="0"
|
|
1578
|
+
step="0.5"
|
|
1579
|
+
placeholder={t('fields.weeklyHours')}
|
|
1580
|
+
value={assignment.weeklyHours}
|
|
1581
|
+
disabled={!assignment.selected}
|
|
1582
|
+
onChange={(event) => {
|
|
1583
|
+
const hours = event.target.value;
|
|
1584
|
+
const updates: Partial<TeamAssignmentState> = {
|
|
1585
|
+
weeklyHours: hours,
|
|
1586
|
+
};
|
|
1587
|
+
const cap = collaborator.weeklyCapacityHours;
|
|
1588
|
+
if (cap && hours && Number(hours) > 0) {
|
|
1589
|
+
const pct = Math.round(
|
|
1590
|
+
(Number(hours) / cap) * 100
|
|
1591
|
+
);
|
|
1592
|
+
updates.allocationPercent = String(
|
|
1593
|
+
Math.min(pct, 100)
|
|
1594
|
+
);
|
|
1663
1595
|
}
|
|
1664
|
-
|
|
1596
|
+
updateAssignment(
|
|
1597
|
+
assignment.collaboratorId,
|
|
1598
|
+
updates
|
|
1599
|
+
);
|
|
1600
|
+
}}
|
|
1601
|
+
/>
|
|
1602
|
+
<Input
|
|
1603
|
+
className="h-9 mt-2 self-start"
|
|
1604
|
+
type="text"
|
|
1605
|
+
inputMode="decimal"
|
|
1606
|
+
placeholder={t('fields.allocationPercent')}
|
|
1607
|
+
value={assignment.allocationPercent}
|
|
1608
|
+
disabled={!assignment.selected}
|
|
1609
|
+
onChange={(event) => {
|
|
1610
|
+
const pct = normalizePercentInput(
|
|
1611
|
+
event.target.value
|
|
1612
|
+
);
|
|
1613
|
+
const updates: Partial<TeamAssignmentState> = {
|
|
1614
|
+
allocationPercent: pct,
|
|
1615
|
+
};
|
|
1616
|
+
const cap = collaborator.weeklyCapacityHours;
|
|
1617
|
+
if (cap && pct && Number(pct) > 0) {
|
|
1618
|
+
const hours = Math.round(
|
|
1619
|
+
(Number(pct) / 100) * cap
|
|
1620
|
+
);
|
|
1621
|
+
updates.weeklyHours = String(hours);
|
|
1622
|
+
}
|
|
1623
|
+
updateAssignment(
|
|
1624
|
+
assignment.collaboratorId,
|
|
1625
|
+
updates
|
|
1626
|
+
);
|
|
1627
|
+
}}
|
|
1628
|
+
/>
|
|
1629
|
+
<div className="flex items-center gap-2 xl:col-span-4">
|
|
1630
|
+
<div className="flex-1">
|
|
1631
|
+
<label className="text-xs text-muted-foreground">
|
|
1632
|
+
{t('fields.assignmentStartDate')}
|
|
1633
|
+
</label>
|
|
1634
|
+
<Input
|
|
1635
|
+
className="h-9"
|
|
1636
|
+
type="date"
|
|
1637
|
+
value={assignment.startDate}
|
|
1638
|
+
disabled={!assignment.selected}
|
|
1639
|
+
onChange={(event) =>
|
|
1640
|
+
updateAssignment(assignment.collaboratorId, {
|
|
1641
|
+
startDate: event.target.value,
|
|
1642
|
+
})
|
|
1643
|
+
}
|
|
1644
|
+
/>
|
|
1645
|
+
</div>
|
|
1646
|
+
<div className="flex-1">
|
|
1647
|
+
<label className="text-xs text-muted-foreground">
|
|
1648
|
+
{t('fields.assignmentEndDate')}
|
|
1649
|
+
</label>
|
|
1650
|
+
<Input
|
|
1651
|
+
className="h-9"
|
|
1652
|
+
type="date"
|
|
1653
|
+
value={assignment.endDate}
|
|
1654
|
+
disabled={!assignment.selected}
|
|
1655
|
+
onChange={(event) =>
|
|
1656
|
+
updateAssignment(assignment.collaboratorId, {
|
|
1657
|
+
endDate: event.target.value,
|
|
1658
|
+
})
|
|
1659
|
+
}
|
|
1660
|
+
/>
|
|
1661
|
+
</div>
|
|
1665
1662
|
</div>
|
|
1666
1663
|
</div>
|
|
1667
|
-
|
|
1668
|
-
)
|
|
1669
|
-
|
|
1664
|
+
);
|
|
1665
|
+
})}
|
|
1666
|
+
</div>
|
|
1670
1667
|
</div>
|
|
1671
|
-
</
|
|
1672
|
-
|
|
1668
|
+
</TabsContent>
|
|
1669
|
+
|
|
1670
|
+
<TabsContent value="files" className="space-y-3">
|
|
1671
|
+
<div className="space-y-0.5">
|
|
1672
|
+
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
1673
|
+
{t('sections.files')}
|
|
1674
|
+
</h3>
|
|
1675
|
+
<p className="text-[11px] text-muted-foreground/80">
|
|
1676
|
+
{t('sections.filesDescription')}
|
|
1677
|
+
</p>
|
|
1678
|
+
</div>
|
|
1679
|
+
{projectId ? (
|
|
1680
|
+
<ProjectFileAttachments projectId={projectId} />
|
|
1681
|
+
) : null}
|
|
1682
|
+
</TabsContent>
|
|
1683
|
+
</Tabs>
|
|
1673
1684
|
</>
|
|
1674
1685
|
) : (
|
|
1675
1686
|
<section className="space-y-3 rounded-lg border border-dashed bg-muted/20 px-4 py-4">
|
|
@@ -1701,6 +1712,43 @@ export function ProjectFormScreen({
|
|
|
1701
1712
|
</div>
|
|
1702
1713
|
) : null;
|
|
1703
1714
|
|
|
1715
|
+
const personSheet = (
|
|
1716
|
+
<PersonFormSheet
|
|
1717
|
+
open={personSheetOpen}
|
|
1718
|
+
person={personToEdit}
|
|
1719
|
+
contactTypes={contactTypes}
|
|
1720
|
+
documentTypes={documentTypes}
|
|
1721
|
+
onOpenChange={(nextOpen) => {
|
|
1722
|
+
setPersonSheetOpen(nextOpen);
|
|
1723
|
+
if (!nextOpen) {
|
|
1724
|
+
setPersonToEdit(null);
|
|
1725
|
+
}
|
|
1726
|
+
}}
|
|
1727
|
+
onSuccess={(person) => {
|
|
1728
|
+
const personLabel = resolvePersonDisplayName(person);
|
|
1729
|
+
|
|
1730
|
+
if (personSheetModeRef.current === 'create' && person?.id) {
|
|
1731
|
+
formMethods.setValue('clientPersonId', String(person.id), {
|
|
1732
|
+
shouldDirty: true,
|
|
1733
|
+
shouldTouch: true,
|
|
1734
|
+
shouldValidate: true,
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
if (personLabel) {
|
|
1739
|
+
formMethods.setValue('clientName', personLabel, {
|
|
1740
|
+
shouldDirty: true,
|
|
1741
|
+
shouldTouch: true,
|
|
1742
|
+
shouldValidate: true,
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
personSheetModeRef.current = 'edit';
|
|
1747
|
+
}}
|
|
1748
|
+
allowedTypes={['company']}
|
|
1749
|
+
/>
|
|
1750
|
+
);
|
|
1751
|
+
|
|
1704
1752
|
if (isSheetMode) {
|
|
1705
1753
|
return (
|
|
1706
1754
|
<div className="mt-6 space-y-4 pb-6">
|
|
@@ -1721,6 +1769,8 @@ export function ProjectFormScreen({
|
|
|
1721
1769
|
submitLabel={commonT('actions.save')}
|
|
1722
1770
|
submitSize="lg"
|
|
1723
1771
|
/>
|
|
1772
|
+
|
|
1773
|
+
{personSheet}
|
|
1724
1774
|
</div>
|
|
1725
1775
|
);
|
|
1726
1776
|
}
|
|
@@ -1764,6 +1814,8 @@ export function ProjectFormScreen({
|
|
|
1764
1814
|
{loadingState}
|
|
1765
1815
|
{contractStatusState}
|
|
1766
1816
|
</div>
|
|
1817
|
+
|
|
1818
|
+
{personSheet}
|
|
1767
1819
|
</Page>
|
|
1768
1820
|
);
|
|
1769
1821
|
}
|