@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,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,
|
|
@@ -87,6 +87,7 @@ import {
|
|
|
87
87
|
Gauge,
|
|
88
88
|
GitCommitHorizontal,
|
|
89
89
|
HeartPulse,
|
|
90
|
+
History,
|
|
90
91
|
LineChart as LineChartIcon,
|
|
91
92
|
Loader2,
|
|
92
93
|
MessageSquare,
|
|
@@ -95,6 +96,7 @@ import {
|
|
|
95
96
|
Plus,
|
|
96
97
|
Rocket,
|
|
97
98
|
Search,
|
|
99
|
+
Send,
|
|
98
100
|
SlidersHorizontal,
|
|
99
101
|
Timer,
|
|
100
102
|
Trash2,
|
|
@@ -125,7 +127,6 @@ import {
|
|
|
125
127
|
YAxis,
|
|
126
128
|
} from 'recharts';
|
|
127
129
|
import { fetchOperations, mutateOperations } from '../_lib/api';
|
|
128
|
-
import { useMentionItems } from '../_lib/hooks/use-mention-items';
|
|
129
130
|
import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
|
|
130
131
|
import {
|
|
131
132
|
MASKED_VALUE,
|
|
@@ -150,11 +151,13 @@ import { ProjectFormScreen } from './project-form-screen';
|
|
|
150
151
|
import { SectionCard } from './section-card';
|
|
151
152
|
import { StatusBadge } from './status-badge';
|
|
152
153
|
import {
|
|
153
|
-
TaskCommentsSection,
|
|
154
154
|
TaskDetailSheet,
|
|
155
155
|
type TaskDetailSheetData,
|
|
156
156
|
} from './task-detail-sheet';
|
|
157
|
-
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';
|
|
158
161
|
|
|
159
162
|
type BoardColumnId = 'todo' | 'doing' | 'review' | 'done';
|
|
160
163
|
|
|
@@ -175,6 +178,8 @@ type BoardTask = {
|
|
|
175
178
|
createdAt: string | null;
|
|
176
179
|
commentCount: number;
|
|
177
180
|
fileCount: number;
|
|
181
|
+
doingStartedAt: string | null;
|
|
182
|
+
totalDoingMinutes: number;
|
|
178
183
|
};
|
|
179
184
|
|
|
180
185
|
type ApiBoardTask = Partial<BoardTask> & {
|
|
@@ -184,28 +189,6 @@ type ApiBoardTask = Partial<BoardTask> & {
|
|
|
184
189
|
priority?: BoardTask['priority'] | null;
|
|
185
190
|
};
|
|
186
191
|
|
|
187
|
-
type TaskFormState = {
|
|
188
|
-
name: string;
|
|
189
|
-
description: string;
|
|
190
|
-
priority: 'low' | 'medium' | 'high';
|
|
191
|
-
status: BoardColumnId;
|
|
192
|
-
assigneeCollaboratorId: string;
|
|
193
|
-
dueDate: string;
|
|
194
|
-
estimateHours: string;
|
|
195
|
-
tags: string;
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const EMPTY_TASK_FORM: TaskFormState = {
|
|
199
|
-
name: '',
|
|
200
|
-
description: '',
|
|
201
|
-
priority: 'medium',
|
|
202
|
-
status: 'todo',
|
|
203
|
-
assigneeCollaboratorId: 'none',
|
|
204
|
-
dueDate: '',
|
|
205
|
-
estimateHours: '',
|
|
206
|
-
tags: '',
|
|
207
|
-
};
|
|
208
|
-
|
|
209
192
|
type BoardColumns = Record<BoardColumnId, BoardTask[]>;
|
|
210
193
|
|
|
211
194
|
type BoardState = {
|
|
@@ -213,6 +196,14 @@ type BoardState = {
|
|
|
213
196
|
columns: BoardColumns;
|
|
214
197
|
};
|
|
215
198
|
|
|
199
|
+
type TimesheetEntryPrefill = {
|
|
200
|
+
projectId: number;
|
|
201
|
+
projectAssignmentId?: number | null;
|
|
202
|
+
projectLabel: string;
|
|
203
|
+
taskId: number;
|
|
204
|
+
taskLabel: string;
|
|
205
|
+
};
|
|
206
|
+
|
|
216
207
|
const KANBAN_COLUMNS: Array<{ id: BoardColumnId; label: string }> = [
|
|
217
208
|
{ id: 'todo', label: 'Backlog' },
|
|
218
209
|
{ id: 'doing', label: 'Em execução' },
|
|
@@ -252,6 +243,8 @@ function apiTaskToBoardTask(row: ApiBoardTask): BoardTask {
|
|
|
252
243
|
0,
|
|
253
244
|
fileCount:
|
|
254
245
|
((row as BoardTask & Record<string, unknown>).fileCount as number) ?? 0,
|
|
246
|
+
doingStartedAt: row.doingStartedAt ?? null,
|
|
247
|
+
totalDoingMinutes: row.totalDoingMinutes ?? 0,
|
|
255
248
|
};
|
|
256
249
|
}
|
|
257
250
|
|
|
@@ -264,6 +257,23 @@ function splitTasksByColumn(tasks: BoardTask[]): BoardColumns {
|
|
|
264
257
|
};
|
|
265
258
|
}
|
|
266
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
|
+
|
|
267
277
|
const boardChartConfig = {
|
|
268
278
|
allocation: { label: 'Alocacao', color: 'hsl(201 96% 32%)' },
|
|
269
279
|
loggedHours: { label: 'Horas', color: 'hsl(166 72% 28%)' },
|
|
@@ -385,26 +395,6 @@ function getUserPhotoUrl(photoId?: number | null) {
|
|
|
385
395
|
: null;
|
|
386
396
|
}
|
|
387
397
|
|
|
388
|
-
function normalizeDateInputValue(value?: string | null) {
|
|
389
|
-
if (!value) {
|
|
390
|
-
return '';
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const normalizedValue = String(value).trim();
|
|
394
|
-
const directMatch = normalizedValue.match(/^\d{4}-\d{2}-\d{2}/);
|
|
395
|
-
|
|
396
|
-
if (directMatch?.[0]) {
|
|
397
|
-
return directMatch[0];
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const parsedDate = new Date(normalizedValue);
|
|
401
|
-
if (Number.isNaN(parsedDate.getTime())) {
|
|
402
|
-
return '';
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
return parsedDate.toISOString().slice(0, 10);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
398
|
function clampPercent(value?: number | null) {
|
|
409
399
|
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
410
400
|
return 0;
|
|
@@ -1104,7 +1094,6 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1104
1094
|
const contractT = useTranslations('operations.ContractFormPage');
|
|
1105
1095
|
const { request, currentLocaleCode, getSettingValue } = useApp();
|
|
1106
1096
|
const access = useOperationsAccess();
|
|
1107
|
-
const mentionItems = useMentionItems(request);
|
|
1108
1097
|
const isLimitedView = !access.isDirector && !access.isSupervisor;
|
|
1109
1098
|
const { valuesVisible, toggleValuesVisible } = useValuesVisibility();
|
|
1110
1099
|
const router = useRouter();
|
|
@@ -1281,11 +1270,13 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1281
1270
|
);
|
|
1282
1271
|
const [taskFormOpen, setTaskFormOpen] = useState(false);
|
|
1283
1272
|
const [editingTaskId, setEditingTaskId] = useState<number | null>(null);
|
|
1284
|
-
const [
|
|
1285
|
-
useState<
|
|
1286
|
-
const [taskFormLoading, setTaskFormLoading] = useState(false);
|
|
1273
|
+
const [createDefaultStatus, setCreateDefaultStatus] =
|
|
1274
|
+
useState<BoardColumnId>('todo');
|
|
1287
1275
|
const [deletePromptTask, setDeletePromptTask] =
|
|
1288
1276
|
useState<TaskDetailSheetData | null>(null);
|
|
1277
|
+
const [deleteProjectDialogOpen, setDeleteProjectDialogOpen] = useState(false);
|
|
1278
|
+
const [deleteProjectConfirmText, setDeleteProjectConfirmText] = useState('');
|
|
1279
|
+
const [deletingProject, setDeletingProject] = useState(false);
|
|
1289
1280
|
const [inlineCreateColumn, setInlineCreateColumn] =
|
|
1290
1281
|
useState<BoardColumnId | null>(null);
|
|
1291
1282
|
const [inlineCreateName, setInlineCreateName] = useState('');
|
|
@@ -1303,6 +1294,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1303
1294
|
const [restoringTaskId, setRestoringTaskId] = useState<number | null>(null);
|
|
1304
1295
|
const [deletingTaskId, setDeletingTaskId] = useState<number | null>(null);
|
|
1305
1296
|
const [activeDragTask, setActiveDragTask] = useState<BoardTask | null>(null);
|
|
1297
|
+
const [isTimesheetEntrySheetOpen, setIsTimesheetEntrySheetOpen] =
|
|
1298
|
+
useState(false);
|
|
1299
|
+
const [timesheetPrefill, setTimesheetPrefill] =
|
|
1300
|
+
useState<TimesheetEntryPrefill | null>(null);
|
|
1306
1301
|
|
|
1307
1302
|
const apiTasks = useMemo(() => rawTasks.map(apiTaskToBoardTask), [rawTasks]);
|
|
1308
1303
|
const archivedTasks = useMemo(
|
|
@@ -1320,6 +1315,29 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1320
1315
|
return splitTasksByColumn(apiTasks);
|
|
1321
1316
|
}, [project, boardState, apiTasks]);
|
|
1322
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
|
+
|
|
1323
1341
|
const filteredTaskColumns: BoardColumns = useMemo(() => {
|
|
1324
1342
|
const normalizedSearch = boardSearch.trim().toLocaleLowerCase();
|
|
1325
1343
|
const filterTask = (task: BoardTask) => {
|
|
@@ -1373,8 +1391,8 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1373
1391
|
|
|
1374
1392
|
const openCreateTaskForm = useCallback(
|
|
1375
1393
|
(defaultStatus: BoardColumnId = 'todo') => {
|
|
1394
|
+
setCreateDefaultStatus(defaultStatus);
|
|
1376
1395
|
setEditingTaskId(null);
|
|
1377
|
-
setTaskFormData({ ...EMPTY_TASK_FORM, status: defaultStatus });
|
|
1378
1396
|
setTaskFormOpen(true);
|
|
1379
1397
|
},
|
|
1380
1398
|
[]
|
|
@@ -1382,73 +1400,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1382
1400
|
|
|
1383
1401
|
const openEditTaskForm = useCallback((task: BoardTask) => {
|
|
1384
1402
|
setEditingTaskId(task.id);
|
|
1385
|
-
setTaskFormData({
|
|
1386
|
-
name: task.name,
|
|
1387
|
-
description: task.description ?? '',
|
|
1388
|
-
priority: task.priority,
|
|
1389
|
-
status: task.status,
|
|
1390
|
-
assigneeCollaboratorId: task.assigneeCollaboratorId
|
|
1391
|
-
? String(task.assigneeCollaboratorId)
|
|
1392
|
-
: 'none',
|
|
1393
|
-
dueDate: normalizeDateInputValue(task.dueDate),
|
|
1394
|
-
estimateHours:
|
|
1395
|
-
task.estimateHours != null ? String(task.estimateHours) : '',
|
|
1396
|
-
tags: task.tags ?? '',
|
|
1397
|
-
});
|
|
1398
1403
|
setSelectedTask(null);
|
|
1399
1404
|
setTaskFormOpen(true);
|
|
1400
1405
|
}, []);
|
|
1401
1406
|
|
|
1402
|
-
const handleTaskFormSubmit = useCallback(async () => {
|
|
1403
|
-
if (!taskFormData.name.trim()) return;
|
|
1404
|
-
setTaskFormLoading(true);
|
|
1405
|
-
try {
|
|
1406
|
-
const payload: Record<string, unknown> = {
|
|
1407
|
-
name: taskFormData.name.trim(),
|
|
1408
|
-
description: taskFormData.description || null,
|
|
1409
|
-
priority: taskFormData.priority,
|
|
1410
|
-
status: taskFormData.status,
|
|
1411
|
-
assigneeCollaboratorId:
|
|
1412
|
-
taskFormData.assigneeCollaboratorId !== 'none'
|
|
1413
|
-
? Number(taskFormData.assigneeCollaboratorId)
|
|
1414
|
-
: null,
|
|
1415
|
-
dueDate: taskFormData.dueDate || null,
|
|
1416
|
-
estimateHours: taskFormData.estimateHours
|
|
1417
|
-
? Number(taskFormData.estimateHours)
|
|
1418
|
-
: null,
|
|
1419
|
-
tags: taskFormData.tags || null,
|
|
1420
|
-
};
|
|
1421
|
-
if (editingTaskId) {
|
|
1422
|
-
await mutateOperations(
|
|
1423
|
-
request,
|
|
1424
|
-
`/operations/tasks/${editingTaskId}`,
|
|
1425
|
-
'PATCH',
|
|
1426
|
-
payload
|
|
1427
|
-
);
|
|
1428
|
-
} else {
|
|
1429
|
-
await mutateOperations(request, '/operations/tasks', 'POST', {
|
|
1430
|
-
projectId,
|
|
1431
|
-
...payload,
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
1434
|
-
setBoardState(null);
|
|
1435
|
-
await refetchTasks();
|
|
1436
|
-
await refetchArchivedTasks();
|
|
1437
|
-
setTaskFormOpen(false);
|
|
1438
|
-
setEditingTaskId(null);
|
|
1439
|
-
setTaskFormData(EMPTY_TASK_FORM);
|
|
1440
|
-
} finally {
|
|
1441
|
-
setTaskFormLoading(false);
|
|
1442
|
-
}
|
|
1443
|
-
}, [
|
|
1444
|
-
taskFormData,
|
|
1445
|
-
editingTaskId,
|
|
1446
|
-
projectId,
|
|
1447
|
-
request,
|
|
1448
|
-
refetchTasks,
|
|
1449
|
-
refetchArchivedTasks,
|
|
1450
|
-
]);
|
|
1451
|
-
|
|
1452
1407
|
const handleArchiveTask = useCallback(
|
|
1453
1408
|
async (taskId: number) => {
|
|
1454
1409
|
setArchivingTaskId(taskId);
|
|
@@ -1548,6 +1503,43 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1548
1503
|
[request, refetchTasks, refetchArchivedTasks]
|
|
1549
1504
|
);
|
|
1550
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
|
+
|
|
1551
1543
|
const allocationChartData = useMemo(() => {
|
|
1552
1544
|
if (projectStats?.allocationByCollaborator?.length) {
|
|
1553
1545
|
return projectStats.allocationByCollaborator;
|
|
@@ -1621,14 +1613,33 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1621
1613
|
},
|
|
1622
1614
|
});
|
|
1623
1615
|
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
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
|
+
});
|
|
1632
1643
|
},
|
|
1633
1644
|
[findColumnByTask, taskColumns, project, request, refetchTasks]
|
|
1634
1645
|
);
|
|
@@ -2109,6 +2120,17 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
2109
2120
|
{commonT('actions.edit')}
|
|
2110
2121
|
</Button>
|
|
2111
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}
|
|
2112
2134
|
</div>
|
|
2113
2135
|
</div>
|
|
2114
2136
|
|
|
@@ -2406,6 +2428,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
2406
2428
|
<TabsTrigger value="team">{t('tabs.team')}</TabsTrigger>
|
|
2407
2429
|
<TabsTrigger value="timeline">{t('tabs.timeline')}</TabsTrigger>
|
|
2408
2430
|
<TabsTrigger value="archive">{t('tabs.archive')}</TabsTrigger>
|
|
2431
|
+
<TabsTrigger value="files">{t('tabs.files')}</TabsTrigger>
|
|
2409
2432
|
<TabsTrigger value="costs">{t('tabs.costs')}</TabsTrigger>
|
|
2410
2433
|
</TabsList>
|
|
2411
2434
|
|
|
@@ -4152,6 +4175,16 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4152
4175
|
/>
|
|
4153
4176
|
</SectionCard>
|
|
4154
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} />
|
|
4186
|
+
</SectionCard>
|
|
4187
|
+
</TabsContent>
|
|
4155
4188
|
</Tabs>
|
|
4156
4189
|
|
|
4157
4190
|
<TaskDetailSheet
|
|
@@ -4168,13 +4201,13 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4168
4201
|
}
|
|
4169
4202
|
footer={
|
|
4170
4203
|
selectedTask && !isLimitedView ? (
|
|
4171
|
-
<div className="grid grid-cols-
|
|
4204
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
|
4172
4205
|
{archivedTasks.some((task) => task.id === selectedTask.id) ? (
|
|
4173
4206
|
<>
|
|
4174
4207
|
<Button
|
|
4175
4208
|
variant="outline"
|
|
4176
4209
|
size="sm"
|
|
4177
|
-
className="h-10 gap-2"
|
|
4210
|
+
className="h-10 gap-2 sm:col-span-2"
|
|
4178
4211
|
disabled={restoringTaskId === selectedTask.id}
|
|
4179
4212
|
onClick={() => void handleRestoreTask(selectedTask.id)}
|
|
4180
4213
|
>
|
|
@@ -4196,26 +4229,67 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4196
4229
|
</Button>
|
|
4197
4230
|
</>
|
|
4198
4231
|
) : (
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
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
|
+
</>
|
|
4213
4257
|
)}
|
|
4214
4258
|
</div>
|
|
4215
4259
|
) : null
|
|
4216
4260
|
}
|
|
4217
4261
|
/>
|
|
4218
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
|
+
|
|
4219
4293
|
{!isLimitedView ? (
|
|
4220
4294
|
<Sheet
|
|
4221
4295
|
open={isEditSheetOpen}
|
|
@@ -4244,284 +4318,36 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4244
4318
|
) : null}
|
|
4245
4319
|
|
|
4246
4320
|
{!isLimitedView ? (
|
|
4247
|
-
<
|
|
4321
|
+
<TaskFormSheet
|
|
4248
4322
|
open={taskFormOpen}
|
|
4249
4323
|
onOpenChange={(open) => {
|
|
4250
4324
|
if (!open) {
|
|
4251
4325
|
setTaskFormOpen(false);
|
|
4252
4326
|
setEditingTaskId(null);
|
|
4253
|
-
setTaskFormData(EMPTY_TASK_FORM);
|
|
4254
4327
|
}
|
|
4255
4328
|
}}
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
>
|
|
4279
|
-
<div className="flex-1 space-y-4 overflow-y-auto px-4 py-2">
|
|
4280
|
-
<div className="space-y-1.5">
|
|
4281
|
-
<Label htmlFor="task-name">
|
|
4282
|
-
{t('taskForm.nameLabel')} *
|
|
4283
|
-
</Label>
|
|
4284
|
-
<Input
|
|
4285
|
-
id="task-name"
|
|
4286
|
-
placeholder={t('taskForm.namePlaceholder')}
|
|
4287
|
-
value={taskFormData.name}
|
|
4288
|
-
onChange={(e) =>
|
|
4289
|
-
setTaskFormData((prev) => ({
|
|
4290
|
-
...prev,
|
|
4291
|
-
name: e.target.value,
|
|
4292
|
-
}))
|
|
4293
|
-
}
|
|
4294
|
-
/>
|
|
4295
|
-
</div>
|
|
4296
|
-
|
|
4297
|
-
<div className="space-y-1.5">
|
|
4298
|
-
<Label htmlFor="task-description">
|
|
4299
|
-
{t('taskForm.descriptionLabel')}
|
|
4300
|
-
</Label>
|
|
4301
|
-
<RichTextEditor
|
|
4302
|
-
value={taskFormData.description}
|
|
4303
|
-
onChange={(val) =>
|
|
4304
|
-
setTaskFormData((prev) => ({
|
|
4305
|
-
...prev,
|
|
4306
|
-
description: val,
|
|
4307
|
-
}))
|
|
4308
|
-
}
|
|
4309
|
-
mentions={mentionItems}
|
|
4310
|
-
/>
|
|
4311
|
-
</div>
|
|
4312
|
-
|
|
4313
|
-
<div className="grid grid-cols-2 gap-3">
|
|
4314
|
-
<div className="space-y-1.5">
|
|
4315
|
-
<Label>{t('taskForm.priorityLabel')}</Label>
|
|
4316
|
-
<Select
|
|
4317
|
-
value={taskFormData.priority}
|
|
4318
|
-
onValueChange={(v) =>
|
|
4319
|
-
setTaskFormData((prev) => ({
|
|
4320
|
-
...prev,
|
|
4321
|
-
priority: v as TaskFormState['priority'],
|
|
4322
|
-
}))
|
|
4323
|
-
}
|
|
4324
|
-
>
|
|
4325
|
-
<SelectTrigger className="w-full">
|
|
4326
|
-
<SelectValue />
|
|
4327
|
-
</SelectTrigger>
|
|
4328
|
-
<SelectContent>
|
|
4329
|
-
<SelectItem value="low">
|
|
4330
|
-
{getTaskPriorityLabel('low')}
|
|
4331
|
-
</SelectItem>
|
|
4332
|
-
<SelectItem value="medium">
|
|
4333
|
-
{getTaskPriorityLabel('medium')}
|
|
4334
|
-
</SelectItem>
|
|
4335
|
-
<SelectItem value="high">
|
|
4336
|
-
{getTaskPriorityLabel('high')}
|
|
4337
|
-
</SelectItem>
|
|
4338
|
-
</SelectContent>
|
|
4339
|
-
</Select>
|
|
4340
|
-
</div>
|
|
4341
|
-
|
|
4342
|
-
<div className="space-y-1.5">
|
|
4343
|
-
<Label>{t('taskForm.columnLabel')}</Label>
|
|
4344
|
-
<Select
|
|
4345
|
-
value={taskFormData.status}
|
|
4346
|
-
onValueChange={(v) =>
|
|
4347
|
-
setTaskFormData((prev) => ({
|
|
4348
|
-
...prev,
|
|
4349
|
-
status: v as BoardColumnId,
|
|
4350
|
-
}))
|
|
4351
|
-
}
|
|
4352
|
-
>
|
|
4353
|
-
<SelectTrigger className="w-full">
|
|
4354
|
-
<SelectValue />
|
|
4355
|
-
</SelectTrigger>
|
|
4356
|
-
<SelectContent>
|
|
4357
|
-
{KANBAN_COLUMNS.map((col) => (
|
|
4358
|
-
<SelectItem key={col.id} value={col.id}>
|
|
4359
|
-
{col.label}
|
|
4360
|
-
</SelectItem>
|
|
4361
|
-
))}
|
|
4362
|
-
</SelectContent>
|
|
4363
|
-
</Select>
|
|
4364
|
-
</div>
|
|
4365
|
-
</div>
|
|
4366
|
-
|
|
4367
|
-
<div className="space-y-1.5">
|
|
4368
|
-
<Label>Responsável</Label>
|
|
4369
|
-
<Select
|
|
4370
|
-
value={taskFormData.assigneeCollaboratorId}
|
|
4371
|
-
onValueChange={(value) =>
|
|
4372
|
-
setTaskFormData((prev) => ({
|
|
4373
|
-
...prev,
|
|
4374
|
-
assigneeCollaboratorId: value,
|
|
4375
|
-
}))
|
|
4376
|
-
}
|
|
4377
|
-
>
|
|
4378
|
-
<SelectTrigger className="w-full">
|
|
4379
|
-
<SelectValue
|
|
4380
|
-
placeholder={commonT('labels.notAssigned')}
|
|
4381
|
-
/>
|
|
4382
|
-
</SelectTrigger>
|
|
4383
|
-
<SelectContent>
|
|
4384
|
-
<SelectItem value="none">
|
|
4385
|
-
{commonT('labels.notAssigned')}
|
|
4386
|
-
</SelectItem>
|
|
4387
|
-
{taskAssigneeOptions.map((option) => (
|
|
4388
|
-
<SelectItem key={option.id} value={option.id}>
|
|
4389
|
-
{option.label}
|
|
4390
|
-
</SelectItem>
|
|
4391
|
-
))}
|
|
4392
|
-
</SelectContent>
|
|
4393
|
-
</Select>
|
|
4394
|
-
</div>
|
|
4395
|
-
|
|
4396
|
-
<div className="grid grid-cols-2 gap-3">
|
|
4397
|
-
<div className="space-y-1.5">
|
|
4398
|
-
<Label htmlFor="task-due-date">
|
|
4399
|
-
{t('taskForm.deadlineLabel')}
|
|
4400
|
-
</Label>
|
|
4401
|
-
<Input
|
|
4402
|
-
id="task-due-date"
|
|
4403
|
-
type="date"
|
|
4404
|
-
value={taskFormData.dueDate}
|
|
4405
|
-
onChange={(e) =>
|
|
4406
|
-
setTaskFormData((prev) => ({
|
|
4407
|
-
...prev,
|
|
4408
|
-
dueDate: e.target.value,
|
|
4409
|
-
}))
|
|
4410
|
-
}
|
|
4411
|
-
/>
|
|
4412
|
-
</div>
|
|
4413
|
-
|
|
4414
|
-
<div className="space-y-1.5">
|
|
4415
|
-
<Label htmlFor="task-estimate">
|
|
4416
|
-
{t('taskForm.estimateLabel')}
|
|
4417
|
-
</Label>
|
|
4418
|
-
<Input
|
|
4419
|
-
id="task-estimate"
|
|
4420
|
-
type="number"
|
|
4421
|
-
min="0"
|
|
4422
|
-
step="0.5"
|
|
4423
|
-
placeholder="0"
|
|
4424
|
-
value={taskFormData.estimateHours}
|
|
4425
|
-
onChange={(e) =>
|
|
4426
|
-
setTaskFormData((prev) => ({
|
|
4427
|
-
...prev,
|
|
4428
|
-
estimateHours: e.target.value,
|
|
4429
|
-
}))
|
|
4430
|
-
}
|
|
4431
|
-
/>
|
|
4432
|
-
</div>
|
|
4433
|
-
</div>
|
|
4434
|
-
|
|
4435
|
-
<div className="space-y-1.5">
|
|
4436
|
-
<Label htmlFor="task-tags">{t('taskForm.tagsLabel')}</Label>
|
|
4437
|
-
<Input
|
|
4438
|
-
id="task-tags"
|
|
4439
|
-
placeholder={t('taskForm.tagsPlaceholder')}
|
|
4440
|
-
value={taskFormData.tags}
|
|
4441
|
-
onChange={(e) =>
|
|
4442
|
-
setTaskFormData((prev) => ({
|
|
4443
|
-
...prev,
|
|
4444
|
-
tags: e.target.value,
|
|
4445
|
-
}))
|
|
4446
|
-
}
|
|
4447
|
-
/>
|
|
4448
|
-
</div>
|
|
4449
|
-
|
|
4450
|
-
{editingTaskId ? (
|
|
4451
|
-
<div className="space-y-1.5">
|
|
4452
|
-
<Label className="flex items-center gap-1.5">
|
|
4453
|
-
<Paperclip className="size-3.5" />
|
|
4454
|
-
{t('taskForm.attachmentsLabel')}
|
|
4455
|
-
</Label>
|
|
4456
|
-
<TaskFileAttachments taskId={editingTaskId} />
|
|
4457
|
-
</div>
|
|
4458
|
-
) : null}
|
|
4459
|
-
</div>
|
|
4460
|
-
|
|
4461
|
-
<div className="mt-4 flex flex-wrap items-center justify-between gap-2 border-t px-4 pb-4 pt-4">
|
|
4462
|
-
<div className="flex gap-2">
|
|
4463
|
-
{editingTaskId ? (
|
|
4464
|
-
<Button
|
|
4465
|
-
type="button"
|
|
4466
|
-
variant="outline"
|
|
4467
|
-
disabled={
|
|
4468
|
-
taskFormLoading || archivingTaskId === editingTaskId
|
|
4469
|
-
}
|
|
4470
|
-
onClick={() => {
|
|
4471
|
-
if (!editingTaskId) return;
|
|
4472
|
-
const id = editingTaskId;
|
|
4473
|
-
setTaskFormOpen(false);
|
|
4474
|
-
setEditingTaskId(null);
|
|
4475
|
-
setTaskFormData(EMPTY_TASK_FORM);
|
|
4476
|
-
void handleArchiveTask(id);
|
|
4477
|
-
}}
|
|
4478
|
-
>
|
|
4479
|
-
{archivingTaskId === editingTaskId ? (
|
|
4480
|
-
<Loader2 className="mr-2 size-4 animate-spin" />
|
|
4481
|
-
) : (
|
|
4482
|
-
<Archive className="mr-2 size-4" />
|
|
4483
|
-
)}
|
|
4484
|
-
{commonT('actions.archive')}
|
|
4485
|
-
</Button>
|
|
4486
|
-
) : null}
|
|
4487
|
-
</div>
|
|
4488
|
-
<div className="flex gap-2">
|
|
4489
|
-
<Button
|
|
4490
|
-
variant="outline"
|
|
4491
|
-
onClick={() => {
|
|
4492
|
-
setTaskFormOpen(false);
|
|
4493
|
-
setEditingTaskId(null);
|
|
4494
|
-
setTaskFormData(EMPTY_TASK_FORM);
|
|
4495
|
-
}}
|
|
4496
|
-
disabled={taskFormLoading}
|
|
4497
|
-
>
|
|
4498
|
-
{commonT('actions.cancel')}
|
|
4499
|
-
</Button>
|
|
4500
|
-
<Button
|
|
4501
|
-
onClick={() => void handleTaskFormSubmit()}
|
|
4502
|
-
disabled={taskFormLoading || !taskFormData.name.trim()}
|
|
4503
|
-
>
|
|
4504
|
-
{taskFormLoading
|
|
4505
|
-
? t('taskForm.saving')
|
|
4506
|
-
: editingTaskId
|
|
4507
|
-
? commonT('actions.save')
|
|
4508
|
-
: commonT('actions.create')}
|
|
4509
|
-
</Button>
|
|
4510
|
-
</div>
|
|
4511
|
-
</div>
|
|
4512
|
-
</TabsContent>
|
|
4513
|
-
|
|
4514
|
-
{editingTaskId ? (
|
|
4515
|
-
<TabsContent
|
|
4516
|
-
value="comments"
|
|
4517
|
-
className="min-h-0 flex-1 overflow-y-auto px-4 py-2 data-[state=inactive]:hidden"
|
|
4518
|
-
>
|
|
4519
|
-
<TaskCommentsSection taskId={editingTaskId} />
|
|
4520
|
-
</TabsContent>
|
|
4521
|
-
) : null}
|
|
4522
|
-
</Tabs>
|
|
4523
|
-
</SheetContent>
|
|
4524
|
-
</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
|
+
/>
|
|
4525
4351
|
) : null}
|
|
4526
4352
|
|
|
4527
4353
|
{!isLimitedView ? (
|
|
@@ -4535,10 +4361,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4535
4361
|
<DialogContent className="sm:max-w-sm">
|
|
4536
4362
|
<DialogHeader>
|
|
4537
4363
|
<DialogTitle>{t('dialogs.deleteTitle')}</DialogTitle>
|
|
4364
|
+
<DialogDescription>
|
|
4365
|
+
{t('dialogs.deleteDescription')}
|
|
4366
|
+
</DialogDescription>
|
|
4538
4367
|
</DialogHeader>
|
|
4539
|
-
<p className="text-sm text-muted-foreground">
|
|
4540
|
-
{t('dialogs.deleteDescription')}
|
|
4541
|
-
</p>
|
|
4542
4368
|
<DialogFooter className="mt-4">
|
|
4543
4369
|
<Button
|
|
4544
4370
|
variant="outline"
|
|
@@ -4561,6 +4387,57 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4561
4387
|
</DialogFooter>
|
|
4562
4388
|
</DialogContent>
|
|
4563
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>
|
|
4564
4441
|
</>
|
|
4565
4442
|
) : null}
|
|
4566
4443
|
</Page>
|