@hed-hog/operations 0.0.329 → 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 +30 -5
- 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 +178 -264
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +2170 -1340
- 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_component_role.yaml +8 -8
- package/hedhog/data/dashboard_item.yaml +25 -25
- package/hedhog/data/dashboard_role.yaml +1 -1
- package/hedhog/data/menu.yaml +6 -16
- package/hedhog/data/role.yaml +1 -1
- package/hedhog/data/route.yaml +116 -55
- package/hedhog/frontend/app/_components/async-options-combobox.tsx.ejs +15 -9
- 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-costs-section.tsx.ejs +51 -81
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +328 -423
- package/hedhog/frontend/app/_components/project-file-attachments.tsx.ejs +371 -0
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +446 -377
- package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +803 -581
- package/hedhog/frontend/app/_components/task-file-attachments.tsx.ejs +14 -9
- 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/hooks/use-values-visibility.ts.ejs +61 -0
- 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 +11 -2
- package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +127 -42
- 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 +59 -16
- package/hedhog/frontend/app/my-tasks/page.tsx.ejs +329 -106
- 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 +436 -35
- package/hedhog/frontend/app/reports/collaborators/page.tsx.ejs +65 -52
- package/hedhog/frontend/app/reports/projects/page.tsx.ejs +80 -82
- package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +13 -2
- 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 +460 -61
- package/hedhog/frontend/messages/operations/en.json +61 -52
- package/hedhog/frontend/messages/operations/pt.json +59 -43
- package/hedhog/frontend/messages/pt.json +460 -61
- 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 +7 -6
- 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 +4300 -2538
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { EmptyState, Page } from '@/components/entity-list';
|
|
4
|
-
import { RichTextEditor } from '@/components/rich-text-editor';
|
|
5
4
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
6
5
|
import { Button } from '@/components/ui/button';
|
|
7
6
|
import { Card, CardContent } from '@/components/ui/card';
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
import {
|
|
15
14
|
Dialog,
|
|
16
15
|
DialogContent,
|
|
16
|
+
DialogDescription,
|
|
17
17
|
DialogFooter,
|
|
18
18
|
DialogHeader,
|
|
19
19
|
DialogTitle,
|
|
@@ -80,11 +80,14 @@ import {
|
|
|
80
80
|
CheckCircle2,
|
|
81
81
|
ChevronRight,
|
|
82
82
|
ClipboardList,
|
|
83
|
+
Eye,
|
|
84
|
+
EyeOff,
|
|
83
85
|
FileText,
|
|
84
86
|
FolderKanban,
|
|
85
87
|
Gauge,
|
|
86
88
|
GitCommitHorizontal,
|
|
87
89
|
HeartPulse,
|
|
90
|
+
History,
|
|
88
91
|
LineChart as LineChartIcon,
|
|
89
92
|
Loader2,
|
|
90
93
|
MessageSquare,
|
|
@@ -93,6 +96,7 @@ import {
|
|
|
93
96
|
Plus,
|
|
94
97
|
Rocket,
|
|
95
98
|
Search,
|
|
99
|
+
Send,
|
|
96
100
|
SlidersHorizontal,
|
|
97
101
|
Timer,
|
|
98
102
|
Trash2,
|
|
@@ -123,8 +127,11 @@ import {
|
|
|
123
127
|
YAxis,
|
|
124
128
|
} from 'recharts';
|
|
125
129
|
import { fetchOperations, mutateOperations } from '../_lib/api';
|
|
126
|
-
import { useMentionItems } from '../_lib/hooks/use-mention-items';
|
|
127
130
|
import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
|
|
131
|
+
import {
|
|
132
|
+
MASKED_VALUE,
|
|
133
|
+
useValuesVisibility,
|
|
134
|
+
} from '../_lib/hooks/use-values-visibility';
|
|
128
135
|
import type {
|
|
129
136
|
OperationsProjectDetails,
|
|
130
137
|
OperationsTaskOption,
|
|
@@ -144,11 +151,13 @@ import { ProjectFormScreen } from './project-form-screen';
|
|
|
144
151
|
import { SectionCard } from './section-card';
|
|
145
152
|
import { StatusBadge } from './status-badge';
|
|
146
153
|
import {
|
|
147
|
-
TaskCommentsSection,
|
|
148
154
|
TaskDetailSheet,
|
|
149
155
|
type TaskDetailSheetData,
|
|
150
156
|
} from './task-detail-sheet';
|
|
151
|
-
import {
|
|
157
|
+
import { ProjectFileAttachments } from './project-file-attachments';
|
|
158
|
+
import { TaskFileAttachments } from './task-file-attachments';
|
|
159
|
+
import { TaskFormSheet } from './task-form-sheet';
|
|
160
|
+
import { TimesheetEntryCreateSheet } from './timesheet-entry-create-sheet';
|
|
152
161
|
|
|
153
162
|
type BoardColumnId = 'todo' | 'doing' | 'review' | 'done';
|
|
154
163
|
|
|
@@ -169,6 +178,8 @@ type BoardTask = {
|
|
|
169
178
|
createdAt: string | null;
|
|
170
179
|
commentCount: number;
|
|
171
180
|
fileCount: number;
|
|
181
|
+
doingStartedAt: string | null;
|
|
182
|
+
totalDoingMinutes: number;
|
|
172
183
|
};
|
|
173
184
|
|
|
174
185
|
type ApiBoardTask = Partial<BoardTask> & {
|
|
@@ -178,28 +189,6 @@ type ApiBoardTask = Partial<BoardTask> & {
|
|
|
178
189
|
priority?: BoardTask['priority'] | null;
|
|
179
190
|
};
|
|
180
191
|
|
|
181
|
-
type TaskFormState = {
|
|
182
|
-
name: string;
|
|
183
|
-
description: string;
|
|
184
|
-
priority: 'low' | 'medium' | 'high';
|
|
185
|
-
status: BoardColumnId;
|
|
186
|
-
assigneeCollaboratorId: string;
|
|
187
|
-
dueDate: string;
|
|
188
|
-
estimateHours: string;
|
|
189
|
-
tags: string;
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const EMPTY_TASK_FORM: TaskFormState = {
|
|
193
|
-
name: '',
|
|
194
|
-
description: '',
|
|
195
|
-
priority: 'medium',
|
|
196
|
-
status: 'todo',
|
|
197
|
-
assigneeCollaboratorId: 'none',
|
|
198
|
-
dueDate: '',
|
|
199
|
-
estimateHours: '',
|
|
200
|
-
tags: '',
|
|
201
|
-
};
|
|
202
|
-
|
|
203
192
|
type BoardColumns = Record<BoardColumnId, BoardTask[]>;
|
|
204
193
|
|
|
205
194
|
type BoardState = {
|
|
@@ -207,6 +196,14 @@ type BoardState = {
|
|
|
207
196
|
columns: BoardColumns;
|
|
208
197
|
};
|
|
209
198
|
|
|
199
|
+
type TimesheetEntryPrefill = {
|
|
200
|
+
projectId: number;
|
|
201
|
+
projectAssignmentId?: number | null;
|
|
202
|
+
projectLabel: string;
|
|
203
|
+
taskId: number;
|
|
204
|
+
taskLabel: string;
|
|
205
|
+
};
|
|
206
|
+
|
|
210
207
|
const KANBAN_COLUMNS: Array<{ id: BoardColumnId; label: string }> = [
|
|
211
208
|
{ id: 'todo', label: 'Backlog' },
|
|
212
209
|
{ id: 'doing', label: 'Em execução' },
|
|
@@ -246,6 +243,8 @@ function apiTaskToBoardTask(row: ApiBoardTask): BoardTask {
|
|
|
246
243
|
0,
|
|
247
244
|
fileCount:
|
|
248
245
|
((row as BoardTask & Record<string, unknown>).fileCount as number) ?? 0,
|
|
246
|
+
doingStartedAt: row.doingStartedAt ?? null,
|
|
247
|
+
totalDoingMinutes: row.totalDoingMinutes ?? 0,
|
|
249
248
|
};
|
|
250
249
|
}
|
|
251
250
|
|
|
@@ -258,6 +257,23 @@ function splitTasksByColumn(tasks: BoardTask[]): BoardColumns {
|
|
|
258
257
|
};
|
|
259
258
|
}
|
|
260
259
|
|
|
260
|
+
function replaceTaskInColumns(
|
|
261
|
+
columns: BoardColumns,
|
|
262
|
+
task: BoardTask
|
|
263
|
+
): BoardColumns {
|
|
264
|
+
const nextColumns = {
|
|
265
|
+
todo: columns.todo.filter((item) => item.id !== task.id),
|
|
266
|
+
doing: columns.doing.filter((item) => item.id !== task.id),
|
|
267
|
+
review: columns.review.filter((item) => item.id !== task.id),
|
|
268
|
+
done: columns.done.filter((item) => item.id !== task.id),
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
...nextColumns,
|
|
273
|
+
[task.status]: [task, ...nextColumns[task.status]],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
261
277
|
const boardChartConfig = {
|
|
262
278
|
allocation: { label: 'Alocacao', color: 'hsl(201 96% 32%)' },
|
|
263
279
|
loggedHours: { label: 'Horas', color: 'hsl(166 72% 28%)' },
|
|
@@ -379,26 +395,6 @@ function getUserPhotoUrl(photoId?: number | null) {
|
|
|
379
395
|
: null;
|
|
380
396
|
}
|
|
381
397
|
|
|
382
|
-
function normalizeDateInputValue(value?: string | null) {
|
|
383
|
-
if (!value) {
|
|
384
|
-
return '';
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const normalizedValue = String(value).trim();
|
|
388
|
-
const directMatch = normalizedValue.match(/^\d{4}-\d{2}-\d{2}/);
|
|
389
|
-
|
|
390
|
-
if (directMatch?.[0]) {
|
|
391
|
-
return directMatch[0];
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const parsedDate = new Date(normalizedValue);
|
|
395
|
-
if (Number.isNaN(parsedDate.getTime())) {
|
|
396
|
-
return '';
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
return parsedDate.toISOString().slice(0, 10);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
398
|
function clampPercent(value?: number | null) {
|
|
403
399
|
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
404
400
|
return 0;
|
|
@@ -1098,8 +1094,8 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1098
1094
|
const contractT = useTranslations('operations.ContractFormPage');
|
|
1099
1095
|
const { request, currentLocaleCode, getSettingValue } = useApp();
|
|
1100
1096
|
const access = useOperationsAccess();
|
|
1101
|
-
const mentionItems = useMentionItems(request);
|
|
1102
1097
|
const isLimitedView = !access.isDirector && !access.isSupervisor;
|
|
1098
|
+
const { valuesVisible, toggleValuesVisible } = useValuesVisibility();
|
|
1103
1099
|
const router = useRouter();
|
|
1104
1100
|
const pathname = usePathname();
|
|
1105
1101
|
const searchParams = useSearchParams();
|
|
@@ -1274,11 +1270,13 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1274
1270
|
);
|
|
1275
1271
|
const [taskFormOpen, setTaskFormOpen] = useState(false);
|
|
1276
1272
|
const [editingTaskId, setEditingTaskId] = useState<number | null>(null);
|
|
1277
|
-
const [
|
|
1278
|
-
useState<
|
|
1279
|
-
const [taskFormLoading, setTaskFormLoading] = useState(false);
|
|
1273
|
+
const [createDefaultStatus, setCreateDefaultStatus] =
|
|
1274
|
+
useState<BoardColumnId>('todo');
|
|
1280
1275
|
const [deletePromptTask, setDeletePromptTask] =
|
|
1281
1276
|
useState<TaskDetailSheetData | null>(null);
|
|
1277
|
+
const [deleteProjectDialogOpen, setDeleteProjectDialogOpen] = useState(false);
|
|
1278
|
+
const [deleteProjectConfirmText, setDeleteProjectConfirmText] = useState('');
|
|
1279
|
+
const [deletingProject, setDeletingProject] = useState(false);
|
|
1282
1280
|
const [inlineCreateColumn, setInlineCreateColumn] =
|
|
1283
1281
|
useState<BoardColumnId | null>(null);
|
|
1284
1282
|
const [inlineCreateName, setInlineCreateName] = useState('');
|
|
@@ -1296,6 +1294,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1296
1294
|
const [restoringTaskId, setRestoringTaskId] = useState<number | null>(null);
|
|
1297
1295
|
const [deletingTaskId, setDeletingTaskId] = useState<number | null>(null);
|
|
1298
1296
|
const [activeDragTask, setActiveDragTask] = useState<BoardTask | null>(null);
|
|
1297
|
+
const [isTimesheetEntrySheetOpen, setIsTimesheetEntrySheetOpen] =
|
|
1298
|
+
useState(false);
|
|
1299
|
+
const [timesheetPrefill, setTimesheetPrefill] =
|
|
1300
|
+
useState<TimesheetEntryPrefill | null>(null);
|
|
1299
1301
|
|
|
1300
1302
|
const apiTasks = useMemo(() => rawTasks.map(apiTaskToBoardTask), [rawTasks]);
|
|
1301
1303
|
const archivedTasks = useMemo(
|
|
@@ -1313,6 +1315,29 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1313
1315
|
return splitTasksByColumn(apiTasks);
|
|
1314
1316
|
}, [project, boardState, apiTasks]);
|
|
1315
1317
|
|
|
1318
|
+
const editingTask = useMemo(() => {
|
|
1319
|
+
if (!editingTaskId) return null;
|
|
1320
|
+
return (
|
|
1321
|
+
Object.values(taskColumns)
|
|
1322
|
+
.flat()
|
|
1323
|
+
.find((task) => task.id === editingTaskId) ??
|
|
1324
|
+
apiTasks.find((task) => task.id === editingTaskId) ??
|
|
1325
|
+
archivedTasks.find((task) => task.id === editingTaskId) ??
|
|
1326
|
+
null
|
|
1327
|
+
);
|
|
1328
|
+
}, [apiTasks, archivedTasks, editingTaskId, taskColumns]);
|
|
1329
|
+
|
|
1330
|
+
const editingTaskAsOption = useMemo(() => {
|
|
1331
|
+
if (!editingTask) return null;
|
|
1332
|
+
return {
|
|
1333
|
+
...editingTask,
|
|
1334
|
+
label: editingTask.name,
|
|
1335
|
+
projectId,
|
|
1336
|
+
projectName: project?.name ?? '',
|
|
1337
|
+
projectCode: project?.code,
|
|
1338
|
+
};
|
|
1339
|
+
}, [editingTask, projectId, project]);
|
|
1340
|
+
|
|
1316
1341
|
const filteredTaskColumns: BoardColumns = useMemo(() => {
|
|
1317
1342
|
const normalizedSearch = boardSearch.trim().toLocaleLowerCase();
|
|
1318
1343
|
const filterTask = (task: BoardTask) => {
|
|
@@ -1366,8 +1391,8 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1366
1391
|
|
|
1367
1392
|
const openCreateTaskForm = useCallback(
|
|
1368
1393
|
(defaultStatus: BoardColumnId = 'todo') => {
|
|
1394
|
+
setCreateDefaultStatus(defaultStatus);
|
|
1369
1395
|
setEditingTaskId(null);
|
|
1370
|
-
setTaskFormData({ ...EMPTY_TASK_FORM, status: defaultStatus });
|
|
1371
1396
|
setTaskFormOpen(true);
|
|
1372
1397
|
},
|
|
1373
1398
|
[]
|
|
@@ -1375,73 +1400,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1375
1400
|
|
|
1376
1401
|
const openEditTaskForm = useCallback((task: BoardTask) => {
|
|
1377
1402
|
setEditingTaskId(task.id);
|
|
1378
|
-
setTaskFormData({
|
|
1379
|
-
name: task.name,
|
|
1380
|
-
description: task.description ?? '',
|
|
1381
|
-
priority: task.priority,
|
|
1382
|
-
status: task.status,
|
|
1383
|
-
assigneeCollaboratorId: task.assigneeCollaboratorId
|
|
1384
|
-
? String(task.assigneeCollaboratorId)
|
|
1385
|
-
: 'none',
|
|
1386
|
-
dueDate: normalizeDateInputValue(task.dueDate),
|
|
1387
|
-
estimateHours:
|
|
1388
|
-
task.estimateHours != null ? String(task.estimateHours) : '',
|
|
1389
|
-
tags: task.tags ?? '',
|
|
1390
|
-
});
|
|
1391
1403
|
setSelectedTask(null);
|
|
1392
1404
|
setTaskFormOpen(true);
|
|
1393
1405
|
}, []);
|
|
1394
1406
|
|
|
1395
|
-
const handleTaskFormSubmit = useCallback(async () => {
|
|
1396
|
-
if (!taskFormData.name.trim()) return;
|
|
1397
|
-
setTaskFormLoading(true);
|
|
1398
|
-
try {
|
|
1399
|
-
const payload: Record<string, unknown> = {
|
|
1400
|
-
name: taskFormData.name.trim(),
|
|
1401
|
-
description: taskFormData.description || null,
|
|
1402
|
-
priority: taskFormData.priority,
|
|
1403
|
-
status: taskFormData.status,
|
|
1404
|
-
assigneeCollaboratorId:
|
|
1405
|
-
taskFormData.assigneeCollaboratorId !== 'none'
|
|
1406
|
-
? Number(taskFormData.assigneeCollaboratorId)
|
|
1407
|
-
: null,
|
|
1408
|
-
dueDate: taskFormData.dueDate || null,
|
|
1409
|
-
estimateHours: taskFormData.estimateHours
|
|
1410
|
-
? Number(taskFormData.estimateHours)
|
|
1411
|
-
: null,
|
|
1412
|
-
tags: taskFormData.tags || null,
|
|
1413
|
-
};
|
|
1414
|
-
if (editingTaskId) {
|
|
1415
|
-
await mutateOperations(
|
|
1416
|
-
request,
|
|
1417
|
-
`/operations/tasks/${editingTaskId}`,
|
|
1418
|
-
'PATCH',
|
|
1419
|
-
payload
|
|
1420
|
-
);
|
|
1421
|
-
} else {
|
|
1422
|
-
await mutateOperations(request, '/operations/tasks', 'POST', {
|
|
1423
|
-
projectId,
|
|
1424
|
-
...payload,
|
|
1425
|
-
});
|
|
1426
|
-
}
|
|
1427
|
-
setBoardState(null);
|
|
1428
|
-
await refetchTasks();
|
|
1429
|
-
await refetchArchivedTasks();
|
|
1430
|
-
setTaskFormOpen(false);
|
|
1431
|
-
setEditingTaskId(null);
|
|
1432
|
-
setTaskFormData(EMPTY_TASK_FORM);
|
|
1433
|
-
} finally {
|
|
1434
|
-
setTaskFormLoading(false);
|
|
1435
|
-
}
|
|
1436
|
-
}, [
|
|
1437
|
-
taskFormData,
|
|
1438
|
-
editingTaskId,
|
|
1439
|
-
projectId,
|
|
1440
|
-
request,
|
|
1441
|
-
refetchTasks,
|
|
1442
|
-
refetchArchivedTasks,
|
|
1443
|
-
]);
|
|
1444
|
-
|
|
1445
1407
|
const handleArchiveTask = useCallback(
|
|
1446
1408
|
async (taskId: number) => {
|
|
1447
1409
|
setArchivingTaskId(taskId);
|
|
@@ -1541,6 +1503,43 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1541
1503
|
[request, refetchTasks, refetchArchivedTasks]
|
|
1542
1504
|
);
|
|
1543
1505
|
|
|
1506
|
+
const handleDeleteProject = useCallback(async () => {
|
|
1507
|
+
setDeletingProject(true);
|
|
1508
|
+
try {
|
|
1509
|
+
await mutateOperations(request, `/operations/projects/${projectId}`, 'DELETE');
|
|
1510
|
+
router.push('/operations/projects');
|
|
1511
|
+
} catch {
|
|
1512
|
+
// ignore
|
|
1513
|
+
} finally {
|
|
1514
|
+
setDeletingProject(false);
|
|
1515
|
+
setDeleteProjectDialogOpen(false);
|
|
1516
|
+
setDeleteProjectConfirmText('');
|
|
1517
|
+
}
|
|
1518
|
+
}, [request, projectId, router]);
|
|
1519
|
+
|
|
1520
|
+
const openTimesheetEntrySheet = useCallback(
|
|
1521
|
+
(task: TaskDetailSheetData) => {
|
|
1522
|
+
if (!project) {
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
const projectLabel = [project.name, project.code]
|
|
1527
|
+
.filter(Boolean)
|
|
1528
|
+
.join(' • ');
|
|
1529
|
+
|
|
1530
|
+
setTimesheetPrefill({
|
|
1531
|
+
projectId,
|
|
1532
|
+
projectAssignmentId: task.projectAssignmentId ?? null,
|
|
1533
|
+
projectLabel: projectLabel || commonT('labels.notAssigned'),
|
|
1534
|
+
taskId: task.id,
|
|
1535
|
+
taskLabel: task.name,
|
|
1536
|
+
});
|
|
1537
|
+
setSelectedTask(null);
|
|
1538
|
+
setIsTimesheetEntrySheetOpen(true);
|
|
1539
|
+
},
|
|
1540
|
+
[commonT, project, projectId]
|
|
1541
|
+
);
|
|
1542
|
+
|
|
1544
1543
|
const allocationChartData = useMemo(() => {
|
|
1545
1544
|
if (projectStats?.allocationByCollaborator?.length) {
|
|
1546
1545
|
return projectStats.allocationByCollaborator;
|
|
@@ -1614,14 +1613,33 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1614
1613
|
},
|
|
1615
1614
|
});
|
|
1616
1615
|
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1616
|
+
mutateOperations<ApiBoardTask>(
|
|
1617
|
+
request,
|
|
1618
|
+
`/operations/tasks/${taskId}`,
|
|
1619
|
+
'PATCH',
|
|
1620
|
+
{
|
|
1621
|
+
status: targetColumn,
|
|
1622
|
+
}
|
|
1623
|
+
)
|
|
1624
|
+
.then((updatedTask) => {
|
|
1625
|
+
if (!updatedTask) {
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
const updatedBoardTask = apiTaskToBoardTask(updatedTask);
|
|
1630
|
+
setBoardState((current) => ({
|
|
1631
|
+
projectId: current?.projectId ?? project.id,
|
|
1632
|
+
columns: replaceTaskInColumns(
|
|
1633
|
+
current?.columns ?? taskColumns,
|
|
1634
|
+
updatedBoardTask
|
|
1635
|
+
),
|
|
1636
|
+
}));
|
|
1637
|
+
})
|
|
1638
|
+
.catch(() => {
|
|
1639
|
+
// Rollback optimistic update on error
|
|
1640
|
+
setBoardState(null);
|
|
1641
|
+
void refetchTasks();
|
|
1642
|
+
});
|
|
1625
1643
|
},
|
|
1626
1644
|
[findColumnByTask, taskColumns, project, request, refetchTasks]
|
|
1627
1645
|
);
|
|
@@ -2078,6 +2096,20 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
2078
2096
|
</div>
|
|
2079
2097
|
</div>
|
|
2080
2098
|
<div className="flex shrink-0 flex-wrap gap-1.5">
|
|
2099
|
+
<Button
|
|
2100
|
+
size="icon"
|
|
2101
|
+
variant="ghost"
|
|
2102
|
+
onClick={toggleValuesVisible}
|
|
2103
|
+
title={commonT(
|
|
2104
|
+
valuesVisible ? 'actions.hideValues' : 'actions.showValues'
|
|
2105
|
+
)}
|
|
2106
|
+
>
|
|
2107
|
+
{valuesVisible ? (
|
|
2108
|
+
<EyeOff className="h-4 w-4" />
|
|
2109
|
+
) : (
|
|
2110
|
+
<Eye className="h-4 w-4" />
|
|
2111
|
+
)}
|
|
2112
|
+
</Button>
|
|
2081
2113
|
{access.isDirector ? (
|
|
2082
2114
|
<Button
|
|
2083
2115
|
size="sm"
|
|
@@ -2088,6 +2120,17 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
2088
2120
|
{commonT('actions.edit')}
|
|
2089
2121
|
</Button>
|
|
2090
2122
|
) : null}
|
|
2123
|
+
{access.isDirector && project?.status === 'archived' ? (
|
|
2124
|
+
<Button
|
|
2125
|
+
size="sm"
|
|
2126
|
+
variant="destructive"
|
|
2127
|
+
className="cursor-pointer gap-1.5"
|
|
2128
|
+
onClick={() => setDeleteProjectDialogOpen(true)}
|
|
2129
|
+
>
|
|
2130
|
+
<Trash2 className="size-3.5" />
|
|
2131
|
+
{t('deleteProjectButton')}
|
|
2132
|
+
</Button>
|
|
2133
|
+
) : null}
|
|
2091
2134
|
</div>
|
|
2092
2135
|
</div>
|
|
2093
2136
|
|
|
@@ -2385,6 +2428,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
2385
2428
|
<TabsTrigger value="team">{t('tabs.team')}</TabsTrigger>
|
|
2386
2429
|
<TabsTrigger value="timeline">{t('tabs.timeline')}</TabsTrigger>
|
|
2387
2430
|
<TabsTrigger value="archive">{t('tabs.archive')}</TabsTrigger>
|
|
2431
|
+
<TabsTrigger value="files">{t('tabs.files')}</TabsTrigger>
|
|
2388
2432
|
<TabsTrigger value="costs">{t('tabs.costs')}</TabsTrigger>
|
|
2389
2433
|
</TabsList>
|
|
2390
2434
|
|
|
@@ -2492,11 +2536,13 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
2492
2536
|
</dt>
|
|
2493
2537
|
<dd className="font-medium">
|
|
2494
2538
|
{project.budgetAmount
|
|
2495
|
-
?
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2539
|
+
? valuesVisible
|
|
2540
|
+
? formatCurrency(
|
|
2541
|
+
project.budgetAmount,
|
|
2542
|
+
getSettingValue,
|
|
2543
|
+
currentLocaleCode
|
|
2544
|
+
)
|
|
2545
|
+
: MASKED_VALUE
|
|
2500
2546
|
: commonT('labels.notAvailable')}
|
|
2501
2547
|
</dd>
|
|
2502
2548
|
</div>
|
|
@@ -2657,11 +2703,13 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
2657
2703
|
</dt>
|
|
2658
2704
|
<dd className="font-medium">
|
|
2659
2705
|
{project.relatedContract.budgetAmount
|
|
2660
|
-
?
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2706
|
+
? valuesVisible
|
|
2707
|
+
? formatCurrency(
|
|
2708
|
+
project.relatedContract.budgetAmount,
|
|
2709
|
+
getSettingValue,
|
|
2710
|
+
currentLocaleCode
|
|
2711
|
+
)
|
|
2712
|
+
: MASKED_VALUE
|
|
2665
2713
|
: commonT('labels.notAvailable')}
|
|
2666
2714
|
</dd>
|
|
2667
2715
|
</div>
|
|
@@ -4121,7 +4169,20 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4121
4169
|
description={t('sections.costsDescription')}
|
|
4122
4170
|
className="rounded-2xl border bg-card p-4 shadow-sm"
|
|
4123
4171
|
>
|
|
4124
|
-
<ProjectCostsSection
|
|
4172
|
+
<ProjectCostsSection
|
|
4173
|
+
projectId={projectId}
|
|
4174
|
+
valuesVisible={valuesVisible}
|
|
4175
|
+
/>
|
|
4176
|
+
</SectionCard>
|
|
4177
|
+
</TabsContent>
|
|
4178
|
+
|
|
4179
|
+
<TabsContent value="files">
|
|
4180
|
+
<SectionCard
|
|
4181
|
+
title={t('sections.files')}
|
|
4182
|
+
description={t('sections.filesDescription')}
|
|
4183
|
+
className="rounded-2xl border bg-card p-4 shadow-sm"
|
|
4184
|
+
>
|
|
4185
|
+
<ProjectFileAttachments projectId={projectId} />
|
|
4125
4186
|
</SectionCard>
|
|
4126
4187
|
</TabsContent>
|
|
4127
4188
|
</Tabs>
|
|
@@ -4140,13 +4201,13 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4140
4201
|
}
|
|
4141
4202
|
footer={
|
|
4142
4203
|
selectedTask && !isLimitedView ? (
|
|
4143
|
-
<div className="grid grid-cols-
|
|
4204
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
|
4144
4205
|
{archivedTasks.some((task) => task.id === selectedTask.id) ? (
|
|
4145
4206
|
<>
|
|
4146
4207
|
<Button
|
|
4147
4208
|
variant="outline"
|
|
4148
4209
|
size="sm"
|
|
4149
|
-
className="h-10 gap-2"
|
|
4210
|
+
className="h-10 gap-2 sm:col-span-2"
|
|
4150
4211
|
disabled={restoringTaskId === selectedTask.id}
|
|
4151
4212
|
onClick={() => void handleRestoreTask(selectedTask.id)}
|
|
4152
4213
|
>
|
|
@@ -4168,26 +4229,67 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4168
4229
|
</Button>
|
|
4169
4230
|
</>
|
|
4170
4231
|
) : (
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4232
|
+
<>
|
|
4233
|
+
<Button
|
|
4234
|
+
variant="outline"
|
|
4235
|
+
size="sm"
|
|
4236
|
+
className="h-10 gap-2"
|
|
4237
|
+
onClick={() => openTimesheetEntrySheet(selectedTask)}
|
|
4238
|
+
>
|
|
4239
|
+
<Send className="size-3.5" />
|
|
4240
|
+
{commonT('actions.logHours')}
|
|
4241
|
+
</Button>
|
|
4242
|
+
<Button
|
|
4243
|
+
variant="outline"
|
|
4244
|
+
size="sm"
|
|
4245
|
+
className="h-10 gap-2 sm:col-span-2"
|
|
4246
|
+
disabled={archivingTaskId === selectedTask.id}
|
|
4247
|
+
onClick={() => void handleArchiveTask(selectedTask.id)}
|
|
4248
|
+
>
|
|
4249
|
+
{archivingTaskId === selectedTask.id ? (
|
|
4250
|
+
<Loader2 className="size-3.5 animate-spin" />
|
|
4251
|
+
) : (
|
|
4252
|
+
<Archive className="size-3.5" />
|
|
4253
|
+
)}
|
|
4254
|
+
{commonT('actions.archive')}
|
|
4255
|
+
</Button>
|
|
4256
|
+
</>
|
|
4185
4257
|
)}
|
|
4186
4258
|
</div>
|
|
4187
4259
|
) : null
|
|
4188
4260
|
}
|
|
4189
4261
|
/>
|
|
4190
4262
|
|
|
4263
|
+
<TimesheetEntryCreateSheet
|
|
4264
|
+
open={isTimesheetEntrySheetOpen}
|
|
4265
|
+
onOpenChange={(open) => {
|
|
4266
|
+
setIsTimesheetEntrySheetOpen(open);
|
|
4267
|
+
if (!open) {
|
|
4268
|
+
setTimesheetPrefill(null);
|
|
4269
|
+
}
|
|
4270
|
+
}}
|
|
4271
|
+
project={
|
|
4272
|
+
timesheetPrefill
|
|
4273
|
+
? {
|
|
4274
|
+
id: timesheetPrefill.projectId,
|
|
4275
|
+
label: timesheetPrefill.projectLabel,
|
|
4276
|
+
projectAssignmentId: timesheetPrefill.projectAssignmentId,
|
|
4277
|
+
}
|
|
4278
|
+
: null
|
|
4279
|
+
}
|
|
4280
|
+
task={
|
|
4281
|
+
timesheetPrefill
|
|
4282
|
+
? {
|
|
4283
|
+
id: timesheetPrefill.taskId,
|
|
4284
|
+
label: timesheetPrefill.taskLabel,
|
|
4285
|
+
}
|
|
4286
|
+
: null
|
|
4287
|
+
}
|
|
4288
|
+
onCreated={() => {
|
|
4289
|
+
void refetchTasks();
|
|
4290
|
+
}}
|
|
4291
|
+
/>
|
|
4292
|
+
|
|
4191
4293
|
{!isLimitedView ? (
|
|
4192
4294
|
<Sheet
|
|
4193
4295
|
open={isEditSheetOpen}
|
|
@@ -4216,284 +4318,36 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4216
4318
|
) : null}
|
|
4217
4319
|
|
|
4218
4320
|
{!isLimitedView ? (
|
|
4219
|
-
<
|
|
4321
|
+
<TaskFormSheet
|
|
4220
4322
|
open={taskFormOpen}
|
|
4221
4323
|
onOpenChange={(open) => {
|
|
4222
4324
|
if (!open) {
|
|
4223
4325
|
setTaskFormOpen(false);
|
|
4224
4326
|
setEditingTaskId(null);
|
|
4225
|
-
setTaskFormData(EMPTY_TASK_FORM);
|
|
4226
4327
|
}
|
|
4227
4328
|
}}
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
>
|
|
4251
|
-
<div className="flex-1 space-y-4 overflow-y-auto px-4 py-2">
|
|
4252
|
-
<div className="space-y-1.5">
|
|
4253
|
-
<Label htmlFor="task-name">
|
|
4254
|
-
{t('taskForm.nameLabel')} *
|
|
4255
|
-
</Label>
|
|
4256
|
-
<Input
|
|
4257
|
-
id="task-name"
|
|
4258
|
-
placeholder={t('taskForm.namePlaceholder')}
|
|
4259
|
-
value={taskFormData.name}
|
|
4260
|
-
onChange={(e) =>
|
|
4261
|
-
setTaskFormData((prev) => ({
|
|
4262
|
-
...prev,
|
|
4263
|
-
name: e.target.value,
|
|
4264
|
-
}))
|
|
4265
|
-
}
|
|
4266
|
-
/>
|
|
4267
|
-
</div>
|
|
4268
|
-
|
|
4269
|
-
<div className="space-y-1.5">
|
|
4270
|
-
<Label htmlFor="task-description">
|
|
4271
|
-
{t('taskForm.descriptionLabel')}
|
|
4272
|
-
</Label>
|
|
4273
|
-
<RichTextEditor
|
|
4274
|
-
value={taskFormData.description}
|
|
4275
|
-
onChange={(val) =>
|
|
4276
|
-
setTaskFormData((prev) => ({
|
|
4277
|
-
...prev,
|
|
4278
|
-
description: val,
|
|
4279
|
-
}))
|
|
4280
|
-
}
|
|
4281
|
-
mentions={mentionItems}
|
|
4282
|
-
/>
|
|
4283
|
-
</div>
|
|
4284
|
-
|
|
4285
|
-
<div className="grid grid-cols-2 gap-3">
|
|
4286
|
-
<div className="space-y-1.5">
|
|
4287
|
-
<Label>{t('taskForm.priorityLabel')}</Label>
|
|
4288
|
-
<Select
|
|
4289
|
-
value={taskFormData.priority}
|
|
4290
|
-
onValueChange={(v) =>
|
|
4291
|
-
setTaskFormData((prev) => ({
|
|
4292
|
-
...prev,
|
|
4293
|
-
priority: v as TaskFormState['priority'],
|
|
4294
|
-
}))
|
|
4295
|
-
}
|
|
4296
|
-
>
|
|
4297
|
-
<SelectTrigger className="w-full">
|
|
4298
|
-
<SelectValue />
|
|
4299
|
-
</SelectTrigger>
|
|
4300
|
-
<SelectContent>
|
|
4301
|
-
<SelectItem value="low">
|
|
4302
|
-
{getTaskPriorityLabel('low')}
|
|
4303
|
-
</SelectItem>
|
|
4304
|
-
<SelectItem value="medium">
|
|
4305
|
-
{getTaskPriorityLabel('medium')}
|
|
4306
|
-
</SelectItem>
|
|
4307
|
-
<SelectItem value="high">
|
|
4308
|
-
{getTaskPriorityLabel('high')}
|
|
4309
|
-
</SelectItem>
|
|
4310
|
-
</SelectContent>
|
|
4311
|
-
</Select>
|
|
4312
|
-
</div>
|
|
4313
|
-
|
|
4314
|
-
<div className="space-y-1.5">
|
|
4315
|
-
<Label>{t('taskForm.columnLabel')}</Label>
|
|
4316
|
-
<Select
|
|
4317
|
-
value={taskFormData.status}
|
|
4318
|
-
onValueChange={(v) =>
|
|
4319
|
-
setTaskFormData((prev) => ({
|
|
4320
|
-
...prev,
|
|
4321
|
-
status: v as BoardColumnId,
|
|
4322
|
-
}))
|
|
4323
|
-
}
|
|
4324
|
-
>
|
|
4325
|
-
<SelectTrigger className="w-full">
|
|
4326
|
-
<SelectValue />
|
|
4327
|
-
</SelectTrigger>
|
|
4328
|
-
<SelectContent>
|
|
4329
|
-
{KANBAN_COLUMNS.map((col) => (
|
|
4330
|
-
<SelectItem key={col.id} value={col.id}>
|
|
4331
|
-
{col.label}
|
|
4332
|
-
</SelectItem>
|
|
4333
|
-
))}
|
|
4334
|
-
</SelectContent>
|
|
4335
|
-
</Select>
|
|
4336
|
-
</div>
|
|
4337
|
-
</div>
|
|
4338
|
-
|
|
4339
|
-
<div className="space-y-1.5">
|
|
4340
|
-
<Label>Responsável</Label>
|
|
4341
|
-
<Select
|
|
4342
|
-
value={taskFormData.assigneeCollaboratorId}
|
|
4343
|
-
onValueChange={(value) =>
|
|
4344
|
-
setTaskFormData((prev) => ({
|
|
4345
|
-
...prev,
|
|
4346
|
-
assigneeCollaboratorId: value,
|
|
4347
|
-
}))
|
|
4348
|
-
}
|
|
4349
|
-
>
|
|
4350
|
-
<SelectTrigger className="w-full">
|
|
4351
|
-
<SelectValue
|
|
4352
|
-
placeholder={commonT('labels.notAssigned')}
|
|
4353
|
-
/>
|
|
4354
|
-
</SelectTrigger>
|
|
4355
|
-
<SelectContent>
|
|
4356
|
-
<SelectItem value="none">
|
|
4357
|
-
{commonT('labels.notAssigned')}
|
|
4358
|
-
</SelectItem>
|
|
4359
|
-
{taskAssigneeOptions.map((option) => (
|
|
4360
|
-
<SelectItem key={option.id} value={option.id}>
|
|
4361
|
-
{option.label}
|
|
4362
|
-
</SelectItem>
|
|
4363
|
-
))}
|
|
4364
|
-
</SelectContent>
|
|
4365
|
-
</Select>
|
|
4366
|
-
</div>
|
|
4367
|
-
|
|
4368
|
-
<div className="grid grid-cols-2 gap-3">
|
|
4369
|
-
<div className="space-y-1.5">
|
|
4370
|
-
<Label htmlFor="task-due-date">
|
|
4371
|
-
{t('taskForm.deadlineLabel')}
|
|
4372
|
-
</Label>
|
|
4373
|
-
<Input
|
|
4374
|
-
id="task-due-date"
|
|
4375
|
-
type="date"
|
|
4376
|
-
value={taskFormData.dueDate}
|
|
4377
|
-
onChange={(e) =>
|
|
4378
|
-
setTaskFormData((prev) => ({
|
|
4379
|
-
...prev,
|
|
4380
|
-
dueDate: e.target.value,
|
|
4381
|
-
}))
|
|
4382
|
-
}
|
|
4383
|
-
/>
|
|
4384
|
-
</div>
|
|
4385
|
-
|
|
4386
|
-
<div className="space-y-1.5">
|
|
4387
|
-
<Label htmlFor="task-estimate">
|
|
4388
|
-
{t('taskForm.estimateLabel')}
|
|
4389
|
-
</Label>
|
|
4390
|
-
<Input
|
|
4391
|
-
id="task-estimate"
|
|
4392
|
-
type="number"
|
|
4393
|
-
min="0"
|
|
4394
|
-
step="0.5"
|
|
4395
|
-
placeholder="0"
|
|
4396
|
-
value={taskFormData.estimateHours}
|
|
4397
|
-
onChange={(e) =>
|
|
4398
|
-
setTaskFormData((prev) => ({
|
|
4399
|
-
...prev,
|
|
4400
|
-
estimateHours: e.target.value,
|
|
4401
|
-
}))
|
|
4402
|
-
}
|
|
4403
|
-
/>
|
|
4404
|
-
</div>
|
|
4405
|
-
</div>
|
|
4406
|
-
|
|
4407
|
-
<div className="space-y-1.5">
|
|
4408
|
-
<Label htmlFor="task-tags">{t('taskForm.tagsLabel')}</Label>
|
|
4409
|
-
<Input
|
|
4410
|
-
id="task-tags"
|
|
4411
|
-
placeholder={t('taskForm.tagsPlaceholder')}
|
|
4412
|
-
value={taskFormData.tags}
|
|
4413
|
-
onChange={(e) =>
|
|
4414
|
-
setTaskFormData((prev) => ({
|
|
4415
|
-
...prev,
|
|
4416
|
-
tags: e.target.value,
|
|
4417
|
-
}))
|
|
4418
|
-
}
|
|
4419
|
-
/>
|
|
4420
|
-
</div>
|
|
4421
|
-
|
|
4422
|
-
{editingTaskId ? (
|
|
4423
|
-
<div className="space-y-1.5">
|
|
4424
|
-
<Label className="flex items-center gap-1.5">
|
|
4425
|
-
<Paperclip className="size-3.5" />
|
|
4426
|
-
{t('taskForm.attachmentsLabel')}
|
|
4427
|
-
</Label>
|
|
4428
|
-
<TaskFileAttachments taskId={editingTaskId} />
|
|
4429
|
-
</div>
|
|
4430
|
-
) : null}
|
|
4431
|
-
</div>
|
|
4432
|
-
|
|
4433
|
-
<div className="mt-4 flex flex-wrap items-center justify-between gap-2 border-t px-4 pb-4 pt-4">
|
|
4434
|
-
<div className="flex gap-2">
|
|
4435
|
-
{editingTaskId ? (
|
|
4436
|
-
<Button
|
|
4437
|
-
type="button"
|
|
4438
|
-
variant="outline"
|
|
4439
|
-
disabled={
|
|
4440
|
-
taskFormLoading || archivingTaskId === editingTaskId
|
|
4441
|
-
}
|
|
4442
|
-
onClick={() => {
|
|
4443
|
-
if (!editingTaskId) return;
|
|
4444
|
-
const id = editingTaskId;
|
|
4445
|
-
setTaskFormOpen(false);
|
|
4446
|
-
setEditingTaskId(null);
|
|
4447
|
-
setTaskFormData(EMPTY_TASK_FORM);
|
|
4448
|
-
void handleArchiveTask(id);
|
|
4449
|
-
}}
|
|
4450
|
-
>
|
|
4451
|
-
{archivingTaskId === editingTaskId ? (
|
|
4452
|
-
<Loader2 className="mr-2 size-4 animate-spin" />
|
|
4453
|
-
) : (
|
|
4454
|
-
<Archive className="mr-2 size-4" />
|
|
4455
|
-
)}
|
|
4456
|
-
{commonT('actions.archive')}
|
|
4457
|
-
</Button>
|
|
4458
|
-
) : null}
|
|
4459
|
-
</div>
|
|
4460
|
-
<div className="flex gap-2">
|
|
4461
|
-
<Button
|
|
4462
|
-
variant="outline"
|
|
4463
|
-
onClick={() => {
|
|
4464
|
-
setTaskFormOpen(false);
|
|
4465
|
-
setEditingTaskId(null);
|
|
4466
|
-
setTaskFormData(EMPTY_TASK_FORM);
|
|
4467
|
-
}}
|
|
4468
|
-
disabled={taskFormLoading}
|
|
4469
|
-
>
|
|
4470
|
-
{commonT('actions.cancel')}
|
|
4471
|
-
</Button>
|
|
4472
|
-
<Button
|
|
4473
|
-
onClick={() => void handleTaskFormSubmit()}
|
|
4474
|
-
disabled={taskFormLoading || !taskFormData.name.trim()}
|
|
4475
|
-
>
|
|
4476
|
-
{taskFormLoading
|
|
4477
|
-
? t('taskForm.saving')
|
|
4478
|
-
: editingTaskId
|
|
4479
|
-
? commonT('actions.save')
|
|
4480
|
-
: commonT('actions.create')}
|
|
4481
|
-
</Button>
|
|
4482
|
-
</div>
|
|
4483
|
-
</div>
|
|
4484
|
-
</TabsContent>
|
|
4485
|
-
|
|
4486
|
-
{editingTaskId ? (
|
|
4487
|
-
<TabsContent
|
|
4488
|
-
value="comments"
|
|
4489
|
-
className="min-h-0 flex-1 overflow-y-auto px-4 py-2 data-[state=inactive]:hidden"
|
|
4490
|
-
>
|
|
4491
|
-
<TaskCommentsSection taskId={editingTaskId} />
|
|
4492
|
-
</TabsContent>
|
|
4493
|
-
) : null}
|
|
4494
|
-
</Tabs>
|
|
4495
|
-
</SheetContent>
|
|
4496
|
-
</Sheet>
|
|
4329
|
+
request={request}
|
|
4330
|
+
editingTask={editingTaskAsOption}
|
|
4331
|
+
showProject={false}
|
|
4332
|
+
defaultProjectId={projectId}
|
|
4333
|
+
defaultStatus={createDefaultStatus}
|
|
4334
|
+
assigneeOptions={taskAssigneeOptions}
|
|
4335
|
+
onArchive={handleArchiveTask}
|
|
4336
|
+
onLogHours={
|
|
4337
|
+
editingTask
|
|
4338
|
+
? () =>
|
|
4339
|
+
openTimesheetEntrySheet(
|
|
4340
|
+
editingTask as unknown as TaskDetailSheetData
|
|
4341
|
+
)
|
|
4342
|
+
: undefined
|
|
4343
|
+
}
|
|
4344
|
+
onCountChanged={() => { setBoardState(null); void refetchTasks(); }}
|
|
4345
|
+
onSaved={() => {
|
|
4346
|
+
setBoardState(null);
|
|
4347
|
+
void refetchTasks();
|
|
4348
|
+
void refetchArchivedTasks();
|
|
4349
|
+
}}
|
|
4350
|
+
/>
|
|
4497
4351
|
) : null}
|
|
4498
4352
|
|
|
4499
4353
|
{!isLimitedView ? (
|
|
@@ -4507,10 +4361,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4507
4361
|
<DialogContent className="sm:max-w-sm">
|
|
4508
4362
|
<DialogHeader>
|
|
4509
4363
|
<DialogTitle>{t('dialogs.deleteTitle')}</DialogTitle>
|
|
4364
|
+
<DialogDescription>
|
|
4365
|
+
{t('dialogs.deleteDescription')}
|
|
4366
|
+
</DialogDescription>
|
|
4510
4367
|
</DialogHeader>
|
|
4511
|
-
<p className="text-sm text-muted-foreground">
|
|
4512
|
-
{t('dialogs.deleteDescription')}
|
|
4513
|
-
</p>
|
|
4514
4368
|
<DialogFooter className="mt-4">
|
|
4515
4369
|
<Button
|
|
4516
4370
|
variant="outline"
|
|
@@ -4533,6 +4387,57 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4533
4387
|
</DialogFooter>
|
|
4534
4388
|
</DialogContent>
|
|
4535
4389
|
</Dialog>
|
|
4390
|
+
|
|
4391
|
+
<Dialog
|
|
4392
|
+
open={deleteProjectDialogOpen}
|
|
4393
|
+
onOpenChange={(open) => {
|
|
4394
|
+
if (!open) {
|
|
4395
|
+
setDeleteProjectDialogOpen(false);
|
|
4396
|
+
setDeleteProjectConfirmText('');
|
|
4397
|
+
}
|
|
4398
|
+
}}
|
|
4399
|
+
>
|
|
4400
|
+
<DialogContent className="sm:max-w-md">
|
|
4401
|
+
<DialogHeader>
|
|
4402
|
+
<DialogTitle>{t('deleteConfirmTitle')}</DialogTitle>
|
|
4403
|
+
</DialogHeader>
|
|
4404
|
+
<p className="text-sm text-muted-foreground">
|
|
4405
|
+
{t('deleteConfirmDescription')}
|
|
4406
|
+
</p>
|
|
4407
|
+
<div className="space-y-1.5">
|
|
4408
|
+
<Label className="text-xs">{t('deleteConfirmLabel')}</Label>
|
|
4409
|
+
<Input
|
|
4410
|
+
value={deleteProjectConfirmText}
|
|
4411
|
+
onChange={(e) => setDeleteProjectConfirmText(e.target.value)}
|
|
4412
|
+
placeholder={project?.name ?? ''}
|
|
4413
|
+
/>
|
|
4414
|
+
</div>
|
|
4415
|
+
<DialogFooter className="mt-2">
|
|
4416
|
+
<Button
|
|
4417
|
+
variant="outline"
|
|
4418
|
+
onClick={() => {
|
|
4419
|
+
setDeleteProjectDialogOpen(false);
|
|
4420
|
+
setDeleteProjectConfirmText('');
|
|
4421
|
+
}}
|
|
4422
|
+
>
|
|
4423
|
+
{commonT('actions.cancel')}
|
|
4424
|
+
</Button>
|
|
4425
|
+
<Button
|
|
4426
|
+
variant="destructive"
|
|
4427
|
+
disabled={
|
|
4428
|
+
deletingProject ||
|
|
4429
|
+
deleteProjectConfirmText !== (project?.name ?? '')
|
|
4430
|
+
}
|
|
4431
|
+
onClick={() => void handleDeleteProject()}
|
|
4432
|
+
>
|
|
4433
|
+
{deletingProject ? (
|
|
4434
|
+
<Loader2 className="mr-2 size-3.5 animate-spin" />
|
|
4435
|
+
) : null}
|
|
4436
|
+
{t('deleteConfirmButton')}
|
|
4437
|
+
</Button>
|
|
4438
|
+
</DialogFooter>
|
|
4439
|
+
</DialogContent>
|
|
4440
|
+
</Dialog>
|
|
4536
4441
|
</>
|
|
4537
4442
|
) : null}
|
|
4538
4443
|
</Page>
|