@hed-hog/operations 0.0.332 → 0.0.338
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/operations-collaborators.controller.d.ts +0 -54
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
- package/dist/controllers/operations-collaborators.controller.js +0 -100
- package/dist/controllers/operations-collaborators.controller.js.map +1 -1
- package/dist/controllers/operations-contracts.controller.d.ts +12 -12
- package/dist/operations.service.d.ts +0 -76
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +7 -230
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +6 -0
- package/dist/operations.service.spec.js.map +1 -1
- package/hedhog/data/menu.yaml +8 -27
- package/hedhog/data/route.yaml +0 -72
- package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +476 -0
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +3 -39
- package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +261 -0
- package/hedhog/frontend/app/_components/collaborator-tasks-tab.tsx.ejs +358 -358
- package/hedhog/frontend/app/_components/collaborator-timesheets-tab.tsx.ejs +6 -6
- package/hedhog/frontend/app/_components/contract-content-editor.tsx.ejs +258 -0
- package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +5 -4
- package/hedhog/frontend/app/_components/person-select-with-create.tsx.ejs +1 -0
- package/hedhog/frontend/app/_components/project-assignments-tab.tsx.ejs +10 -218
- package/hedhog/frontend/app/_components/project-cost-report-screen.tsx.ejs +23 -23
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +24 -708
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +38 -158
- package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +1 -5
- package/hedhog/frontend/app/_components/task-form-sheet.tsx.ejs +629 -629
- package/hedhog/frontend/app/_lib/api.ts.ejs +0 -151
- package/hedhog/frontend/app/_lib/types.ts.ejs +0 -1
- package/hedhog/frontend/app/_lib/utils/task-ui.ts.ejs +0 -18
- package/hedhog/frontend/app/my-projects/page.tsx.ejs +2 -16
- package/hedhog/frontend/app/my-tasks/page.tsx.ejs +86 -24
- package/hedhog/frontend/app/projects/page.tsx.ejs +6 -42
- package/hedhog/frontend/messages/en.json +2 -96
- package/hedhog/frontend/messages/operations/operations/en.json +2100 -0
- package/hedhog/frontend/messages/operations/operations/pt.json +2111 -0
- package/hedhog/frontend/messages/pt.json +2 -96
- package/hedhog/frontend/widgets/capacity-distribution.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/effort-by-project.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/headcount-by-area.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/index.ts.ejs +25 -25
- package/hedhog/frontend/widgets/managed-projects-status.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-hours-period-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-open-requests-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-pending-requests-list.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-project-allocations-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-quick-actions.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-relevant-deadlines.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-timesheet-status-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-weekly-journey.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/portfolio-costs-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/portfolio-effort-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/portfolio-projects-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/portfolio-risk-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/project-status-overview.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/shared-operations-widget.tsx.ejs +169 -169
- package/hedhog/frontend/widgets/strategic-deadlines.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/team-approval-queue.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/team-capacity-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/team-headcount-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/team-hours-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/team-pending-approvals-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/team-utilization-overview.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/team-workload-alerts.tsx.ejs +16 -16
- package/hedhog/table/operations_collaborator.yaml +8 -8
- package/hedhog/table/operations_task.yaml +76 -76
- package/hedhog/table/operations_task_activity.yaml +51 -51
- package/package.json +6 -6
- package/src/controllers/operations-collaborators.controller.ts +8 -117
- package/src/controllers/operations-tasks.controller.ts +156 -156
- package/src/dashboard/widgets/MyQuickActions.tsx +22 -22
- package/src/dto/create-collaborator.dto.ts +4 -4
- package/src/operations.service.spec.ts +1006 -988
- package/src/operations.service.ts +7 -323
- package/dist/dto/create-collaborator-invoice.dto.d.ts +0 -11
- package/dist/dto/create-collaborator-invoice.dto.d.ts.map +0 -1
- package/dist/dto/create-collaborator-invoice.dto.js +0 -55
- package/dist/dto/create-collaborator-invoice.dto.js.map +0 -1
- package/dist/dto/create-collaborator-payment.dto.d.ts +0 -10
- package/dist/dto/create-collaborator-payment.dto.d.ts.map +0 -1
- package/dist/dto/create-collaborator-payment.dto.js +0 -50
- package/dist/dto/create-collaborator-payment.dto.js.map +0 -1
- package/dist/dto/list-collaborator-invoice.dto.d.ts +0 -4
- package/dist/dto/list-collaborator-invoice.dto.d.ts.map +0 -1
- package/dist/dto/list-collaborator-invoice.dto.js +0 -8
- package/dist/dto/list-collaborator-invoice.dto.js.map +0 -1
- package/dist/dto/list-collaborator-payment.dto.d.ts +0 -4
- package/dist/dto/list-collaborator-payment.dto.d.ts.map +0 -1
- package/dist/dto/list-collaborator-payment.dto.js +0 -8
- package/dist/dto/list-collaborator-payment.dto.js.map +0 -1
- package/dist/dto/update-collaborator-invoice.dto.d.ts +0 -6
- package/dist/dto/update-collaborator-invoice.dto.d.ts.map +0 -1
- package/dist/dto/update-collaborator-invoice.dto.js +0 -9
- package/dist/dto/update-collaborator-invoice.dto.js.map +0 -1
- package/dist/dto/update-collaborator-payment.dto.d.ts +0 -6
- package/dist/dto/update-collaborator-payment.dto.d.ts.map +0 -1
- package/dist/dto/update-collaborator-payment.dto.js +0 -9
- package/dist/dto/update-collaborator-payment.dto.js.map +0 -1
- package/hedhog/frontend/app/_components/collaborator-invoices-tab.tsx.ejs +0 -443
- package/hedhog/frontend/app/_components/collaborator-payment-history-tab.tsx.ejs +0 -429
- package/hedhog/frontend/app/tasks-gantt/page.tsx.ejs +0 -953
- package/hedhog/table/operations_collaborator_invoice.yaml +0 -35
- package/hedhog/table/operations_collaborator_payment.yaml +0 -32
- package/src/dto/create-collaborator-invoice.dto.ts +0 -39
- package/src/dto/create-collaborator-payment.dto.ts +0 -35
- package/src/dto/list-collaborator-invoice.dto.ts +0 -3
- package/src/dto/list-collaborator-payment.dto.ts +0 -3
- package/src/dto/update-collaborator-invoice.dto.ts +0 -6
- package/src/dto/update-collaborator-payment.dto.ts +0 -6
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
DialogHeader,
|
|
19
19
|
DialogTitle,
|
|
20
20
|
} from '@/components/ui/dialog';
|
|
21
|
-
import { EntityPicker } from '@/components/ui/entity-picker';
|
|
22
21
|
import { Input } from '@/components/ui/input';
|
|
23
22
|
import { Label } from '@/components/ui/label';
|
|
24
23
|
import { Progress } from '@/components/ui/progress';
|
|
@@ -88,6 +87,7 @@ import {
|
|
|
88
87
|
Gauge,
|
|
89
88
|
GitCommitHorizontal,
|
|
90
89
|
HeartPulse,
|
|
90
|
+
History,
|
|
91
91
|
LineChart as LineChartIcon,
|
|
92
92
|
Loader2,
|
|
93
93
|
MessageSquare,
|
|
@@ -101,7 +101,6 @@ import {
|
|
|
101
101
|
Timer,
|
|
102
102
|
Trash2,
|
|
103
103
|
TrendingUp,
|
|
104
|
-
UserPlus,
|
|
105
104
|
Users,
|
|
106
105
|
type LucideIcon,
|
|
107
106
|
} from 'lucide-react';
|
|
@@ -134,11 +133,8 @@ import {
|
|
|
134
133
|
useValuesVisibility,
|
|
135
134
|
} from '../_lib/hooks/use-values-visibility';
|
|
136
135
|
import type {
|
|
137
|
-
OperationsCollaborator,
|
|
138
|
-
OperationsCollaboratorDetails,
|
|
139
136
|
OperationsProjectDetails,
|
|
140
137
|
OperationsTaskOption,
|
|
141
|
-
PaginatedResponse,
|
|
142
138
|
} from '../_lib/types';
|
|
143
139
|
import {
|
|
144
140
|
formatCurrency,
|
|
@@ -149,14 +145,17 @@ import {
|
|
|
149
145
|
formatPercent,
|
|
150
146
|
getStatusBadgeClass,
|
|
151
147
|
} from '../_lib/utils/format';
|
|
152
|
-
import { parseNumberInput } from '../_lib/utils/forms';
|
|
153
148
|
import { OperationsHeader } from './operations-header';
|
|
154
149
|
import { ProjectCostsSection } from './project-costs-section';
|
|
155
|
-
import { ProjectFileAttachments } from './project-file-attachments';
|
|
156
150
|
import { ProjectFormScreen } from './project-form-screen';
|
|
157
151
|
import { SectionCard } from './section-card';
|
|
158
152
|
import { StatusBadge } from './status-badge';
|
|
159
|
-
import {
|
|
153
|
+
import {
|
|
154
|
+
TaskDetailSheet,
|
|
155
|
+
type TaskDetailSheetData,
|
|
156
|
+
} from './task-detail-sheet';
|
|
157
|
+
import { ProjectFileAttachments } from './project-file-attachments';
|
|
158
|
+
import { TaskFileAttachments } from './task-file-attachments';
|
|
160
159
|
import { TaskFormSheet } from './task-form-sheet';
|
|
161
160
|
import { TimesheetEntryCreateSheet } from './timesheet-entry-create-sheet';
|
|
162
161
|
|
|
@@ -205,15 +204,6 @@ type TimesheetEntryPrefill = {
|
|
|
205
204
|
taskLabel: string;
|
|
206
205
|
};
|
|
207
206
|
|
|
208
|
-
function formatAssignmentNumericValue(value: number) {
|
|
209
|
-
if (!Number.isFinite(value)) {
|
|
210
|
-
return '';
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const roundedValue = Math.round(value * 100) / 100;
|
|
214
|
-
return String(roundedValue);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
207
|
const KANBAN_COLUMNS: Array<{ id: BoardColumnId; label: string }> = [
|
|
218
208
|
{ id: 'todo', label: 'Backlog' },
|
|
219
209
|
{ id: 'doing', label: 'Em execução' },
|
|
@@ -396,7 +386,7 @@ function getInitials(value?: string | null) {
|
|
|
396
386
|
function getPersonAvatarUrl(avatarId?: number | null) {
|
|
397
387
|
return typeof avatarId === 'number' && avatarId > 0
|
|
398
388
|
? `${process.env.NEXT_PUBLIC_API_BASE_URL}/person/avatar/${avatarId}`
|
|
399
|
-
:
|
|
389
|
+
: '/placeholder.png';
|
|
400
390
|
}
|
|
401
391
|
|
|
402
392
|
function getUserPhotoUrl(photoId?: number | null) {
|
|
@@ -1102,7 +1092,6 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1102
1092
|
const commonT = useTranslations('operations.Common');
|
|
1103
1093
|
const formT = useTranslations('operations.ProjectFormPage');
|
|
1104
1094
|
const contractT = useTranslations('operations.ContractFormPage');
|
|
1105
|
-
const collaboratorFormT = useTranslations('operations.CollaboratorFormPage');
|
|
1106
1095
|
const { request, currentLocaleCode, getSettingValue } = useApp();
|
|
1107
1096
|
const access = useOperationsAccess();
|
|
1108
1097
|
const isLimitedView = !access.isDirector && !access.isSupervisor;
|
|
@@ -1310,26 +1299,6 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1310
1299
|
const [timesheetPrefill, setTimesheetPrefill] =
|
|
1311
1300
|
useState<TimesheetEntryPrefill | null>(null);
|
|
1312
1301
|
|
|
1313
|
-
// Assignment management state
|
|
1314
|
-
const [assignmentSheetOpen, setAssignmentSheetOpen] = useState(false);
|
|
1315
|
-
const [editingAssignment, setEditingAssignment] = useState<
|
|
1316
|
-
OperationsProjectDetails['assignments'][0] | null
|
|
1317
|
-
>(null);
|
|
1318
|
-
const [assignmentFormData, setAssignmentFormData] = useState({
|
|
1319
|
-
collaboratorId: '',
|
|
1320
|
-
weeklyHours: '',
|
|
1321
|
-
allocationPercent: '',
|
|
1322
|
-
status: 'active',
|
|
1323
|
-
startDate: '',
|
|
1324
|
-
endDate: '',
|
|
1325
|
-
});
|
|
1326
|
-
const [selectedAssignmentCollaborator, setSelectedAssignmentCollaborator] =
|
|
1327
|
-
useState<OperationsCollaborator | null>(null);
|
|
1328
|
-
const [savingAssignment, setSavingAssignment] = useState(false);
|
|
1329
|
-
const [removingAssignmentId, setRemovingAssignmentId] = useState<
|
|
1330
|
-
number | null
|
|
1331
|
-
>(null);
|
|
1332
|
-
|
|
1333
1302
|
const apiTasks = useMemo(() => rawTasks.map(apiTaskToBoardTask), [rawTasks]);
|
|
1334
1303
|
const archivedTasks = useMemo(
|
|
1335
1304
|
() =>
|
|
@@ -1537,11 +1506,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1537
1506
|
const handleDeleteProject = useCallback(async () => {
|
|
1538
1507
|
setDeletingProject(true);
|
|
1539
1508
|
try {
|
|
1540
|
-
await mutateOperations(
|
|
1541
|
-
request,
|
|
1542
|
-
`/operations/projects/${projectId}`,
|
|
1543
|
-
'DELETE'
|
|
1544
|
-
);
|
|
1509
|
+
await mutateOperations(request, `/operations/projects/${projectId}`, 'DELETE');
|
|
1545
1510
|
router.push('/operations/projects');
|
|
1546
1511
|
} catch {
|
|
1547
1512
|
// ignore
|
|
@@ -1575,245 +1540,6 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1575
1540
|
[commonT, project, projectId]
|
|
1576
1541
|
);
|
|
1577
1542
|
|
|
1578
|
-
const loadCollaboratorOptions = useCallback(
|
|
1579
|
-
async ({
|
|
1580
|
-
page,
|
|
1581
|
-
pageSize,
|
|
1582
|
-
search,
|
|
1583
|
-
}: {
|
|
1584
|
-
page: number;
|
|
1585
|
-
pageSize: number;
|
|
1586
|
-
search: string;
|
|
1587
|
-
}) => {
|
|
1588
|
-
const params = new URLSearchParams({
|
|
1589
|
-
page: String(page),
|
|
1590
|
-
pageSize: String(pageSize),
|
|
1591
|
-
sortField: 'displayName',
|
|
1592
|
-
sortOrder: 'asc',
|
|
1593
|
-
});
|
|
1594
|
-
if (search.trim()) params.set('search', search.trim());
|
|
1595
|
-
const result = await fetchOperations<
|
|
1596
|
-
PaginatedResponse<OperationsCollaborator>
|
|
1597
|
-
>(request, `/operations/collaborators?${params.toString()}`);
|
|
1598
|
-
const items = result?.data ?? [];
|
|
1599
|
-
const total = result?.total ?? 0;
|
|
1600
|
-
return { items, hasMore: page * pageSize < total };
|
|
1601
|
-
},
|
|
1602
|
-
[request]
|
|
1603
|
-
);
|
|
1604
|
-
|
|
1605
|
-
const loadAssignmentCollaboratorById = useCallback(
|
|
1606
|
-
async (collaboratorId: number) => {
|
|
1607
|
-
const collaborator = await fetchOperations<OperationsCollaboratorDetails>(
|
|
1608
|
-
request,
|
|
1609
|
-
`/operations/collaborators/${collaboratorId}`
|
|
1610
|
-
);
|
|
1611
|
-
setSelectedAssignmentCollaborator(collaborator);
|
|
1612
|
-
return collaborator;
|
|
1613
|
-
},
|
|
1614
|
-
[request]
|
|
1615
|
-
);
|
|
1616
|
-
|
|
1617
|
-
const createAssignmentCollaborator = useCallback(
|
|
1618
|
-
async (values: Record<string, string>) => {
|
|
1619
|
-
const displayName = values.displayName?.trim() ?? '';
|
|
1620
|
-
const weeklyCapacityHours = parseNumberInput(
|
|
1621
|
-
values.weeklyCapacityHours ?? ''
|
|
1622
|
-
);
|
|
1623
|
-
|
|
1624
|
-
if (!displayName) {
|
|
1625
|
-
return null;
|
|
1626
|
-
}
|
|
1627
|
-
|
|
1628
|
-
return mutateOperations<OperationsCollaborator>(
|
|
1629
|
-
request,
|
|
1630
|
-
'/operations/collaborators',
|
|
1631
|
-
'POST',
|
|
1632
|
-
{
|
|
1633
|
-
displayName,
|
|
1634
|
-
weeklyCapacityHours,
|
|
1635
|
-
status: 'active',
|
|
1636
|
-
autoGenerateContractDraft: false,
|
|
1637
|
-
}
|
|
1638
|
-
);
|
|
1639
|
-
},
|
|
1640
|
-
[request]
|
|
1641
|
-
);
|
|
1642
|
-
|
|
1643
|
-
const syncAssignmentFromWeeklyHours = useCallback(
|
|
1644
|
-
(
|
|
1645
|
-
weeklyHours: string,
|
|
1646
|
-
currentFormData: typeof assignmentFormData,
|
|
1647
|
-
collaborator?: OperationsCollaborator | null
|
|
1648
|
-
) => {
|
|
1649
|
-
const capacity = collaborator?.weeklyCapacityHours;
|
|
1650
|
-
if (!capacity || capacity <= 0) {
|
|
1651
|
-
return {
|
|
1652
|
-
weeklyHours,
|
|
1653
|
-
allocationPercent: currentFormData.allocationPercent,
|
|
1654
|
-
};
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
const parsedHours = parseNumberInput(weeklyHours);
|
|
1658
|
-
if (parsedHours == null) {
|
|
1659
|
-
return { weeklyHours, allocationPercent: '' };
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
return {
|
|
1663
|
-
weeklyHours,
|
|
1664
|
-
allocationPercent: formatAssignmentNumericValue(
|
|
1665
|
-
(parsedHours / capacity) * 100
|
|
1666
|
-
),
|
|
1667
|
-
};
|
|
1668
|
-
},
|
|
1669
|
-
[]
|
|
1670
|
-
);
|
|
1671
|
-
|
|
1672
|
-
const syncAssignmentFromAllocation = useCallback(
|
|
1673
|
-
(
|
|
1674
|
-
allocationPercent: string,
|
|
1675
|
-
currentFormData: typeof assignmentFormData,
|
|
1676
|
-
collaborator?: OperationsCollaborator | null
|
|
1677
|
-
) => {
|
|
1678
|
-
const capacity = collaborator?.weeklyCapacityHours;
|
|
1679
|
-
if (!capacity || capacity <= 0) {
|
|
1680
|
-
return {
|
|
1681
|
-
weeklyHours: currentFormData.weeklyHours,
|
|
1682
|
-
allocationPercent,
|
|
1683
|
-
};
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
const parsedPercent = parseNumberInput(allocationPercent);
|
|
1687
|
-
if (parsedPercent == null) {
|
|
1688
|
-
return { weeklyHours: '', allocationPercent };
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
return {
|
|
1692
|
-
weeklyHours: formatAssignmentNumericValue(
|
|
1693
|
-
(parsedPercent / 100) * capacity
|
|
1694
|
-
),
|
|
1695
|
-
allocationPercent,
|
|
1696
|
-
};
|
|
1697
|
-
},
|
|
1698
|
-
[]
|
|
1699
|
-
);
|
|
1700
|
-
|
|
1701
|
-
const openAddAssignment = useCallback(() => {
|
|
1702
|
-
setEditingAssignment(null);
|
|
1703
|
-
setSelectedAssignmentCollaborator(null);
|
|
1704
|
-
setAssignmentFormData({
|
|
1705
|
-
collaboratorId: '',
|
|
1706
|
-
weeklyHours: '',
|
|
1707
|
-
allocationPercent: '',
|
|
1708
|
-
status: 'active',
|
|
1709
|
-
startDate: '',
|
|
1710
|
-
endDate: '',
|
|
1711
|
-
});
|
|
1712
|
-
setAssignmentSheetOpen(true);
|
|
1713
|
-
}, []);
|
|
1714
|
-
|
|
1715
|
-
const openEditAssignment = useCallback(
|
|
1716
|
-
(assignment: OperationsProjectDetails['assignments'][0]) => {
|
|
1717
|
-
setEditingAssignment(assignment);
|
|
1718
|
-
setSelectedAssignmentCollaborator(null);
|
|
1719
|
-
setAssignmentFormData({
|
|
1720
|
-
collaboratorId: String(assignment.collaboratorId),
|
|
1721
|
-
weeklyHours:
|
|
1722
|
-
assignment.weeklyHours != null ? String(assignment.weeklyHours) : '',
|
|
1723
|
-
allocationPercent:
|
|
1724
|
-
assignment.allocationPercent != null
|
|
1725
|
-
? String(assignment.allocationPercent)
|
|
1726
|
-
: '',
|
|
1727
|
-
status: assignment.status,
|
|
1728
|
-
startDate: assignment.startDate?.slice(0, 10) ?? '',
|
|
1729
|
-
endDate: assignment.endDate?.slice(0, 10) ?? '',
|
|
1730
|
-
});
|
|
1731
|
-
void loadAssignmentCollaboratorById(assignment.collaboratorId);
|
|
1732
|
-
setAssignmentSheetOpen(true);
|
|
1733
|
-
},
|
|
1734
|
-
[loadAssignmentCollaboratorById]
|
|
1735
|
-
);
|
|
1736
|
-
|
|
1737
|
-
const handleSaveAssignment = useCallback(async () => {
|
|
1738
|
-
if (!project) return;
|
|
1739
|
-
const collabId = Number(assignmentFormData.collaboratorId);
|
|
1740
|
-
if (!collabId) return;
|
|
1741
|
-
setSavingAssignment(true);
|
|
1742
|
-
const newEntry = {
|
|
1743
|
-
collaboratorId: collabId,
|
|
1744
|
-
weeklyHours: assignmentFormData.weeklyHours
|
|
1745
|
-
? Number(assignmentFormData.weeklyHours)
|
|
1746
|
-
: null,
|
|
1747
|
-
allocationPercent: assignmentFormData.allocationPercent
|
|
1748
|
-
? Number(assignmentFormData.allocationPercent)
|
|
1749
|
-
: null,
|
|
1750
|
-
status: assignmentFormData.status,
|
|
1751
|
-
startDate: assignmentFormData.startDate || null,
|
|
1752
|
-
endDate: assignmentFormData.endDate || null,
|
|
1753
|
-
};
|
|
1754
|
-
const existingMapped = project.assignments.map((a) => ({
|
|
1755
|
-
collaboratorId: a.collaboratorId,
|
|
1756
|
-
weeklyHours: a.weeklyHours ?? null,
|
|
1757
|
-
allocationPercent: a.allocationPercent ?? null,
|
|
1758
|
-
status: a.status,
|
|
1759
|
-
startDate: a.startDate ?? null,
|
|
1760
|
-
endDate: a.endDate ?? null,
|
|
1761
|
-
}));
|
|
1762
|
-
const updatedAssignments = editingAssignment
|
|
1763
|
-
? existingMapped.map((a) =>
|
|
1764
|
-
a.collaboratorId === editingAssignment.collaboratorId ? newEntry : a
|
|
1765
|
-
)
|
|
1766
|
-
: [...existingMapped, newEntry];
|
|
1767
|
-
try {
|
|
1768
|
-
await mutateOperations(
|
|
1769
|
-
request,
|
|
1770
|
-
`/operations/projects/${projectId}`,
|
|
1771
|
-
'PATCH',
|
|
1772
|
-
{ teamAssignments: updatedAssignments }
|
|
1773
|
-
);
|
|
1774
|
-
await refetch();
|
|
1775
|
-
setAssignmentSheetOpen(false);
|
|
1776
|
-
} catch {
|
|
1777
|
-
// ignore
|
|
1778
|
-
} finally {
|
|
1779
|
-
setSavingAssignment(false);
|
|
1780
|
-
}
|
|
1781
|
-
}, [
|
|
1782
|
-
project,
|
|
1783
|
-
assignmentFormData,
|
|
1784
|
-
editingAssignment,
|
|
1785
|
-
request,
|
|
1786
|
-
projectId,
|
|
1787
|
-
refetch,
|
|
1788
|
-
]);
|
|
1789
|
-
|
|
1790
|
-
const handleConfirmRemoveAssignment = useCallback(async () => {
|
|
1791
|
-
if (!project || removingAssignmentId === null) return;
|
|
1792
|
-
const updatedAssignments = project.assignments
|
|
1793
|
-
.filter((a) => a.collaboratorId !== removingAssignmentId)
|
|
1794
|
-
.map((a) => ({
|
|
1795
|
-
collaboratorId: a.collaboratorId,
|
|
1796
|
-
weeklyHours: a.weeklyHours ?? null,
|
|
1797
|
-
allocationPercent: a.allocationPercent ?? null,
|
|
1798
|
-
status: a.status,
|
|
1799
|
-
startDate: a.startDate ?? null,
|
|
1800
|
-
endDate: a.endDate ?? null,
|
|
1801
|
-
}));
|
|
1802
|
-
try {
|
|
1803
|
-
await mutateOperations(
|
|
1804
|
-
request,
|
|
1805
|
-
`/operations/projects/${projectId}`,
|
|
1806
|
-
'PATCH',
|
|
1807
|
-
{ teamAssignments: updatedAssignments }
|
|
1808
|
-
);
|
|
1809
|
-
await refetch();
|
|
1810
|
-
} catch {
|
|
1811
|
-
// ignore
|
|
1812
|
-
} finally {
|
|
1813
|
-
setRemovingAssignmentId(null);
|
|
1814
|
-
}
|
|
1815
|
-
}, [project, removingAssignmentId, request, projectId, refetch]);
|
|
1816
|
-
|
|
1817
1543
|
const allocationChartData = useMemo(() => {
|
|
1818
1544
|
if (projectStats?.allocationByCollaborator?.length) {
|
|
1819
1545
|
return projectStats.allocationByCollaborator;
|
|
@@ -2414,13 +2140,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
2414
2140
|
<TooltipTrigger asChild>
|
|
2415
2141
|
<div className="flex cursor-default items-center gap-1.5 border-r px-3 py-2 transition hover:bg-muted/30">
|
|
2416
2142
|
<Avatar className="size-5 shrink-0 border bg-muted">
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
}
|
|
2422
|
-
alt={project.clientName || ''}
|
|
2423
|
-
/>
|
|
2143
|
+
<AvatarImage
|
|
2144
|
+
src={getPersonAvatarUrl(project.clientAvatarId)}
|
|
2145
|
+
alt={project.clientName || ''}
|
|
2146
|
+
/>
|
|
2424
2147
|
<AvatarFallback className="text-[9px]">
|
|
2425
2148
|
{getInitials(project.clientName)}
|
|
2426
2149
|
</AvatarFallback>
|
|
@@ -2739,10 +2462,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
2739
2462
|
<div className="flex items-center gap-2">
|
|
2740
2463
|
<Avatar className="h-8 w-8 border border-border/60 bg-muted">
|
|
2741
2464
|
<AvatarImage
|
|
2742
|
-
src={
|
|
2743
|
-
getUserPhotoUrl(project.clientUserPhotoId) ||
|
|
2744
|
-
getPersonAvatarUrl(project.clientAvatarId)
|
|
2745
|
-
}
|
|
2465
|
+
src={getPersonAvatarUrl(project.clientAvatarId)}
|
|
2746
2466
|
alt={project.clientName || commonT('labels.client')}
|
|
2747
2467
|
/>
|
|
2748
2468
|
<AvatarFallback className="bg-muted text-xs font-semibold text-foreground">
|
|
@@ -2884,32 +2604,13 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
2884
2604
|
<div className="font-medium">
|
|
2885
2605
|
{project.relatedContract.name}
|
|
2886
2606
|
</div>
|
|
2887
|
-
<div className="
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
<Avatar className="h-4 w-4 shrink-0">
|
|
2895
|
-
<AvatarImage
|
|
2896
|
-
src={
|
|
2897
|
-
getUserPhotoUrl(project.clientUserPhotoId) ||
|
|
2898
|
-
getPersonAvatarUrl(project.clientAvatarId)
|
|
2899
|
-
}
|
|
2900
|
-
alt={project.relatedContract.clientName}
|
|
2901
|
-
/>
|
|
2902
|
-
<AvatarFallback className="text-[8px] font-medium">
|
|
2903
|
-
{getInitials(
|
|
2904
|
-
project.relatedContract.clientName
|
|
2905
|
-
)}
|
|
2906
|
-
</AvatarFallback>
|
|
2907
|
-
</Avatar>
|
|
2908
|
-
<span className="truncate">
|
|
2909
|
-
{project.relatedContract.clientName}
|
|
2910
|
-
</span>
|
|
2911
|
-
</>
|
|
2912
|
-
) : null}
|
|
2607
|
+
<div className="text-sm text-muted-foreground">
|
|
2608
|
+
{[
|
|
2609
|
+
project.relatedContract.code,
|
|
2610
|
+
project.relatedContract.clientName,
|
|
2611
|
+
]
|
|
2612
|
+
.filter(Boolean)
|
|
2613
|
+
.join(' • ') || commonT('labels.notAvailable')}
|
|
2913
2614
|
</div>
|
|
2914
2615
|
</div>
|
|
2915
2616
|
<div className="flex items-center gap-3">
|
|
@@ -3381,7 +3082,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
3381
3082
|
{(isOver) => (
|
|
3382
3083
|
<div
|
|
3383
3084
|
className={[
|
|
3384
|
-
'flex min-h-
|
|
3085
|
+
'flex min-h-128 flex-col overflow-hidden rounded-3xl border bg-linear-to-b p-3 transition-all',
|
|
3385
3086
|
getColumnClassName(column.id),
|
|
3386
3087
|
isOver
|
|
3387
3088
|
? 'border-primary shadow-lg ring-2 ring-primary/15'
|
|
@@ -3424,7 +3125,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
3424
3125
|
</div>
|
|
3425
3126
|
</div>
|
|
3426
3127
|
|
|
3427
|
-
<div className="flex
|
|
3128
|
+
<div className="flex flex-1 flex-col gap-2">
|
|
3428
3129
|
<AnimatePresence initial={false}>
|
|
3429
3130
|
{filteredTaskColumns[column.id].map((task) => {
|
|
3430
3131
|
const tags = getTaskTags(task);
|
|
@@ -4152,19 +3853,6 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4152
3853
|
'rounded-2xl border bg-card p-4 shadow-sm',
|
|
4153
3854
|
isLimitedView ? 'xl:col-span-12' : 'xl:col-span-8',
|
|
4154
3855
|
].join(' ')}
|
|
4155
|
-
actions={
|
|
4156
|
-
!isLimitedView ? (
|
|
4157
|
-
<Button
|
|
4158
|
-
size="sm"
|
|
4159
|
-
variant="outline"
|
|
4160
|
-
className="cursor-pointer"
|
|
4161
|
-
onClick={openAddAssignment}
|
|
4162
|
-
>
|
|
4163
|
-
<UserPlus className="mr-1.5 size-4" />
|
|
4164
|
-
{t('teamPanel.addCollaborator')}
|
|
4165
|
-
</Button>
|
|
4166
|
-
) : undefined
|
|
4167
|
-
}
|
|
4168
3856
|
>
|
|
4169
3857
|
{project.assignments.length > 0 ? (
|
|
4170
3858
|
<div className="space-y-4">
|
|
@@ -4272,34 +3960,6 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4272
3960
|
<ToneIcon className="size-3" />
|
|
4273
3961
|
{t(`teamPanel.status.${tone.labelKey}`)}
|
|
4274
3962
|
</span>
|
|
4275
|
-
{!isLimitedView ? (
|
|
4276
|
-
<div className="flex items-center gap-1">
|
|
4277
|
-
<Button
|
|
4278
|
-
type="button"
|
|
4279
|
-
variant="ghost"
|
|
4280
|
-
size="icon"
|
|
4281
|
-
className="size-7 cursor-pointer"
|
|
4282
|
-
onClick={() =>
|
|
4283
|
-
openEditAssignment(assignment)
|
|
4284
|
-
}
|
|
4285
|
-
>
|
|
4286
|
-
<Pencil className="size-3.5" />
|
|
4287
|
-
</Button>
|
|
4288
|
-
<Button
|
|
4289
|
-
type="button"
|
|
4290
|
-
variant="ghost"
|
|
4291
|
-
size="icon"
|
|
4292
|
-
className="size-7 cursor-pointer text-destructive hover:text-destructive"
|
|
4293
|
-
onClick={() =>
|
|
4294
|
-
setRemovingAssignmentId(
|
|
4295
|
-
assignment.collaboratorId
|
|
4296
|
-
)
|
|
4297
|
-
}
|
|
4298
|
-
>
|
|
4299
|
-
<Trash2 className="size-3.5" />
|
|
4300
|
-
</Button>
|
|
4301
|
-
</div>
|
|
4302
|
-
) : null}
|
|
4303
3963
|
</div>
|
|
4304
3964
|
</div>
|
|
4305
3965
|
</div>
|
|
@@ -4600,347 +4260,6 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
4600
4260
|
}
|
|
4601
4261
|
/>
|
|
4602
4262
|
|
|
4603
|
-
{/* Assignment Add/Edit Sheet */}
|
|
4604
|
-
<Sheet
|
|
4605
|
-
open={assignmentSheetOpen}
|
|
4606
|
-
onOpenChange={(open) => {
|
|
4607
|
-
if (!open) {
|
|
4608
|
-
setAssignmentSheetOpen(false);
|
|
4609
|
-
setSelectedAssignmentCollaborator(null);
|
|
4610
|
-
}
|
|
4611
|
-
}}
|
|
4612
|
-
>
|
|
4613
|
-
<SheetContent className="w-full overflow-y-auto sm:max-w-[min(92vw,28rem)]">
|
|
4614
|
-
<SheetHeader>
|
|
4615
|
-
<SheetTitle>
|
|
4616
|
-
{editingAssignment
|
|
4617
|
-
? t('teamPanel.editTitle')
|
|
4618
|
-
: t('teamPanel.addTitle')}
|
|
4619
|
-
</SheetTitle>
|
|
4620
|
-
<SheetDescription>
|
|
4621
|
-
{t('teamPanel.formDescription')}
|
|
4622
|
-
</SheetDescription>
|
|
4623
|
-
</SheetHeader>
|
|
4624
|
-
<div className="mt-6 space-y-4 px-1 sm:px-4">
|
|
4625
|
-
{!editingAssignment ? (
|
|
4626
|
-
<div className="space-y-2">
|
|
4627
|
-
<Label className="text-sm font-medium">
|
|
4628
|
-
{commonT('labels.collaborator')}
|
|
4629
|
-
</Label>
|
|
4630
|
-
<EntityPicker<OperationsCollaborator>
|
|
4631
|
-
value={
|
|
4632
|
-
assignmentFormData.collaboratorId
|
|
4633
|
-
? Number(assignmentFormData.collaboratorId)
|
|
4634
|
-
: null
|
|
4635
|
-
}
|
|
4636
|
-
onChange={(val, option) => {
|
|
4637
|
-
const collaborator = option ?? null;
|
|
4638
|
-
setSelectedAssignmentCollaborator(collaborator);
|
|
4639
|
-
setAssignmentFormData((prev) => {
|
|
4640
|
-
const collaboratorId = val ? String(val) : '';
|
|
4641
|
-
if (!collaboratorId) {
|
|
4642
|
-
return {
|
|
4643
|
-
...prev,
|
|
4644
|
-
collaboratorId: '',
|
|
4645
|
-
weeklyHours: '',
|
|
4646
|
-
allocationPercent: '',
|
|
4647
|
-
};
|
|
4648
|
-
}
|
|
4649
|
-
|
|
4650
|
-
const capacity = collaborator?.weeklyCapacityHours;
|
|
4651
|
-
if (!capacity || capacity <= 0) {
|
|
4652
|
-
return {
|
|
4653
|
-
...prev,
|
|
4654
|
-
collaboratorId,
|
|
4655
|
-
};
|
|
4656
|
-
}
|
|
4657
|
-
|
|
4658
|
-
const parsedHours = parseNumberInput(prev.weeklyHours);
|
|
4659
|
-
const parsedPercent = parseNumberInput(
|
|
4660
|
-
prev.allocationPercent
|
|
4661
|
-
);
|
|
4662
|
-
|
|
4663
|
-
if (parsedHours != null) {
|
|
4664
|
-
return {
|
|
4665
|
-
...prev,
|
|
4666
|
-
collaboratorId,
|
|
4667
|
-
...syncAssignmentFromWeeklyHours(
|
|
4668
|
-
prev.weeklyHours,
|
|
4669
|
-
prev,
|
|
4670
|
-
collaborator
|
|
4671
|
-
),
|
|
4672
|
-
};
|
|
4673
|
-
}
|
|
4674
|
-
|
|
4675
|
-
if (parsedPercent != null) {
|
|
4676
|
-
return {
|
|
4677
|
-
...prev,
|
|
4678
|
-
collaboratorId,
|
|
4679
|
-
...syncAssignmentFromAllocation(
|
|
4680
|
-
prev.allocationPercent,
|
|
4681
|
-
prev,
|
|
4682
|
-
collaborator
|
|
4683
|
-
),
|
|
4684
|
-
};
|
|
4685
|
-
}
|
|
4686
|
-
|
|
4687
|
-
return {
|
|
4688
|
-
...prev,
|
|
4689
|
-
collaboratorId,
|
|
4690
|
-
weeklyHours: formatAssignmentNumericValue(capacity),
|
|
4691
|
-
allocationPercent: '100',
|
|
4692
|
-
};
|
|
4693
|
-
});
|
|
4694
|
-
}}
|
|
4695
|
-
placeholder={commonT('labels.collaborator')}
|
|
4696
|
-
searchPlaceholder={commonT('labels.collaborator')}
|
|
4697
|
-
loadOptions={loadCollaboratorOptions}
|
|
4698
|
-
getOptionValue={(opt) => opt.id}
|
|
4699
|
-
getOptionLabel={(opt) => opt.displayName}
|
|
4700
|
-
renderOption={({ option }) => (
|
|
4701
|
-
<div className="flex min-w-0 items-center gap-2.5">
|
|
4702
|
-
<Avatar className="size-6 shrink-0">
|
|
4703
|
-
<AvatarImage
|
|
4704
|
-
src={
|
|
4705
|
-
getUserPhotoUrl(option.userPhotoId) ||
|
|
4706
|
-
getPersonAvatarUrl(option.personAvatarId)
|
|
4707
|
-
}
|
|
4708
|
-
alt={option.displayName}
|
|
4709
|
-
/>
|
|
4710
|
-
<AvatarFallback className="text-[9px]">
|
|
4711
|
-
{getInitials(option.displayName)}
|
|
4712
|
-
</AvatarFallback>
|
|
4713
|
-
</Avatar>
|
|
4714
|
-
<div className="min-w-0">
|
|
4715
|
-
<div className="truncate text-sm">
|
|
4716
|
-
{option.displayName}
|
|
4717
|
-
</div>
|
|
4718
|
-
{option.department ? (
|
|
4719
|
-
<div className="truncate text-xs text-muted-foreground">
|
|
4720
|
-
{option.department}
|
|
4721
|
-
</div>
|
|
4722
|
-
) : null}
|
|
4723
|
-
</div>
|
|
4724
|
-
</div>
|
|
4725
|
-
)}
|
|
4726
|
-
valueType="number"
|
|
4727
|
-
clearable
|
|
4728
|
-
allowEmptySelection
|
|
4729
|
-
showCreateButton
|
|
4730
|
-
entityLabel={commonT('labels.collaborator').toLowerCase()}
|
|
4731
|
-
createActionLabel={`${commonT('actions.create')} ${commonT(
|
|
4732
|
-
'labels.collaborator'
|
|
4733
|
-
).toLowerCase()}`}
|
|
4734
|
-
createTitle={`${commonT('actions.create')} ${commonT(
|
|
4735
|
-
'labels.collaborator'
|
|
4736
|
-
).toLowerCase()}`}
|
|
4737
|
-
createDescription={t('teamPanel.formDescription')}
|
|
4738
|
-
createFields={[
|
|
4739
|
-
{
|
|
4740
|
-
name: 'displayName',
|
|
4741
|
-
label: collaboratorFormT('fields.displayName'),
|
|
4742
|
-
placeholder: collaboratorFormT('fields.displayName'),
|
|
4743
|
-
required: true,
|
|
4744
|
-
},
|
|
4745
|
-
{
|
|
4746
|
-
name: 'weeklyCapacityHours',
|
|
4747
|
-
label: collaboratorFormT('fields.weeklyCapacityHours'),
|
|
4748
|
-
placeholder: '40',
|
|
4749
|
-
type: 'number',
|
|
4750
|
-
},
|
|
4751
|
-
]}
|
|
4752
|
-
mapSearchToCreateValues={(search) => ({
|
|
4753
|
-
displayName: search,
|
|
4754
|
-
weeklyCapacityHours: '40',
|
|
4755
|
-
})}
|
|
4756
|
-
onCreate={createAssignmentCollaborator}
|
|
4757
|
-
/>
|
|
4758
|
-
</div>
|
|
4759
|
-
) : (
|
|
4760
|
-
<div className="flex items-center gap-3 rounded-xl border bg-muted/30 p-3">
|
|
4761
|
-
<Avatar className="size-10 border bg-muted">
|
|
4762
|
-
<AvatarImage
|
|
4763
|
-
src={
|
|
4764
|
-
getUserPhotoUrl(editingAssignment.userPhotoId) ||
|
|
4765
|
-
getPersonAvatarUrl(editingAssignment.personAvatarId)
|
|
4766
|
-
}
|
|
4767
|
-
alt={editingAssignment.collaboratorName}
|
|
4768
|
-
/>
|
|
4769
|
-
<AvatarFallback className="text-xs">
|
|
4770
|
-
{getInitials(editingAssignment.collaboratorName)}
|
|
4771
|
-
</AvatarFallback>
|
|
4772
|
-
</Avatar>
|
|
4773
|
-
<div className="min-w-0">
|
|
4774
|
-
<div className="truncate text-sm font-semibold">
|
|
4775
|
-
{editingAssignment.collaboratorName}
|
|
4776
|
-
</div>
|
|
4777
|
-
{editingAssignment.roleLabel ? (
|
|
4778
|
-
<div className="truncate text-xs text-muted-foreground">
|
|
4779
|
-
{editingAssignment.roleLabel}
|
|
4780
|
-
</div>
|
|
4781
|
-
) : null}
|
|
4782
|
-
</div>
|
|
4783
|
-
</div>
|
|
4784
|
-
)}
|
|
4785
|
-
<div className="space-y-2">
|
|
4786
|
-
<Label className="text-sm font-medium">
|
|
4787
|
-
{commonT('labels.allocationPercent')}
|
|
4788
|
-
</Label>
|
|
4789
|
-
<Input
|
|
4790
|
-
type="number"
|
|
4791
|
-
min="0"
|
|
4792
|
-
max="200"
|
|
4793
|
-
value={assignmentFormData.allocationPercent}
|
|
4794
|
-
onChange={(e) =>
|
|
4795
|
-
setAssignmentFormData((prev) => ({
|
|
4796
|
-
...prev,
|
|
4797
|
-
...syncAssignmentFromAllocation(
|
|
4798
|
-
e.target.value,
|
|
4799
|
-
prev,
|
|
4800
|
-
selectedAssignmentCollaborator
|
|
4801
|
-
),
|
|
4802
|
-
}))
|
|
4803
|
-
}
|
|
4804
|
-
placeholder="100"
|
|
4805
|
-
/>
|
|
4806
|
-
</div>
|
|
4807
|
-
<div className="space-y-2">
|
|
4808
|
-
<Label className="text-sm font-medium">
|
|
4809
|
-
{commonT('labels.weeklyCapacity')}
|
|
4810
|
-
</Label>
|
|
4811
|
-
<Input
|
|
4812
|
-
type="number"
|
|
4813
|
-
min="0"
|
|
4814
|
-
value={assignmentFormData.weeklyHours}
|
|
4815
|
-
onChange={(e) =>
|
|
4816
|
-
setAssignmentFormData((prev) => ({
|
|
4817
|
-
...prev,
|
|
4818
|
-
...syncAssignmentFromWeeklyHours(
|
|
4819
|
-
e.target.value,
|
|
4820
|
-
prev,
|
|
4821
|
-
selectedAssignmentCollaborator
|
|
4822
|
-
),
|
|
4823
|
-
}))
|
|
4824
|
-
}
|
|
4825
|
-
placeholder="40"
|
|
4826
|
-
/>
|
|
4827
|
-
</div>
|
|
4828
|
-
<div className="space-y-2">
|
|
4829
|
-
<Label className="text-sm font-medium">
|
|
4830
|
-
{commonT('labels.status')}
|
|
4831
|
-
</Label>
|
|
4832
|
-
<Select
|
|
4833
|
-
value={assignmentFormData.status}
|
|
4834
|
-
onValueChange={(val) =>
|
|
4835
|
-
setAssignmentFormData((prev) => ({ ...prev, status: val }))
|
|
4836
|
-
}
|
|
4837
|
-
>
|
|
4838
|
-
<SelectTrigger className="cursor-pointer">
|
|
4839
|
-
<SelectValue />
|
|
4840
|
-
</SelectTrigger>
|
|
4841
|
-
<SelectContent>
|
|
4842
|
-
<SelectItem value="active">
|
|
4843
|
-
{formatEnumLabel('active')}
|
|
4844
|
-
</SelectItem>
|
|
4845
|
-
<SelectItem value="inactive">
|
|
4846
|
-
{formatEnumLabel('inactive')}
|
|
4847
|
-
</SelectItem>
|
|
4848
|
-
<SelectItem value="completed">
|
|
4849
|
-
{formatEnumLabel('completed')}
|
|
4850
|
-
</SelectItem>
|
|
4851
|
-
</SelectContent>
|
|
4852
|
-
</Select>
|
|
4853
|
-
</div>
|
|
4854
|
-
<div className="grid grid-cols-2 gap-3">
|
|
4855
|
-
<div className="space-y-2">
|
|
4856
|
-
<Label className="text-sm font-medium">
|
|
4857
|
-
{commonT('labels.startDate')}
|
|
4858
|
-
</Label>
|
|
4859
|
-
<Input
|
|
4860
|
-
type="date"
|
|
4861
|
-
value={assignmentFormData.startDate}
|
|
4862
|
-
onChange={(e) =>
|
|
4863
|
-
setAssignmentFormData((prev) => ({
|
|
4864
|
-
...prev,
|
|
4865
|
-
startDate: e.target.value,
|
|
4866
|
-
}))
|
|
4867
|
-
}
|
|
4868
|
-
/>
|
|
4869
|
-
</div>
|
|
4870
|
-
<div className="space-y-2">
|
|
4871
|
-
<Label className="text-sm font-medium">
|
|
4872
|
-
{commonT('labels.endDate')}
|
|
4873
|
-
</Label>
|
|
4874
|
-
<Input
|
|
4875
|
-
type="date"
|
|
4876
|
-
value={assignmentFormData.endDate}
|
|
4877
|
-
onChange={(e) =>
|
|
4878
|
-
setAssignmentFormData((prev) => ({
|
|
4879
|
-
...prev,
|
|
4880
|
-
endDate: e.target.value,
|
|
4881
|
-
}))
|
|
4882
|
-
}
|
|
4883
|
-
/>
|
|
4884
|
-
</div>
|
|
4885
|
-
</div>
|
|
4886
|
-
<div className="flex gap-2 pt-2">
|
|
4887
|
-
<Button
|
|
4888
|
-
variant="outline"
|
|
4889
|
-
className="flex-1 cursor-pointer"
|
|
4890
|
-
onClick={() => setAssignmentSheetOpen(false)}
|
|
4891
|
-
disabled={savingAssignment}
|
|
4892
|
-
>
|
|
4893
|
-
{commonT('actions.cancel')}
|
|
4894
|
-
</Button>
|
|
4895
|
-
<Button
|
|
4896
|
-
className="flex-1 cursor-pointer"
|
|
4897
|
-
disabled={
|
|
4898
|
-
savingAssignment ||
|
|
4899
|
-
(!editingAssignment && !assignmentFormData.collaboratorId)
|
|
4900
|
-
}
|
|
4901
|
-
onClick={() => void handleSaveAssignment()}
|
|
4902
|
-
>
|
|
4903
|
-
{savingAssignment ? (
|
|
4904
|
-
<Loader2 className="mr-2 size-4 animate-spin" />
|
|
4905
|
-
) : null}
|
|
4906
|
-
{commonT('actions.save')}
|
|
4907
|
-
</Button>
|
|
4908
|
-
</div>
|
|
4909
|
-
</div>
|
|
4910
|
-
</SheetContent>
|
|
4911
|
-
</Sheet>
|
|
4912
|
-
|
|
4913
|
-
{/* Remove Assignment Confirm Dialog */}
|
|
4914
|
-
<Dialog
|
|
4915
|
-
open={removingAssignmentId !== null}
|
|
4916
|
-
onOpenChange={(open) => {
|
|
4917
|
-
if (!open) setRemovingAssignmentId(null);
|
|
4918
|
-
}}
|
|
4919
|
-
>
|
|
4920
|
-
<DialogContent className="sm:max-w-sm">
|
|
4921
|
-
<DialogHeader>
|
|
4922
|
-
<DialogTitle>{t('teamPanel.removeTitle')}</DialogTitle>
|
|
4923
|
-
<DialogDescription>
|
|
4924
|
-
{t('teamPanel.removeDescription')}
|
|
4925
|
-
</DialogDescription>
|
|
4926
|
-
</DialogHeader>
|
|
4927
|
-
<DialogFooter className="mt-4">
|
|
4928
|
-
<Button
|
|
4929
|
-
variant="outline"
|
|
4930
|
-
onClick={() => setRemovingAssignmentId(null)}
|
|
4931
|
-
>
|
|
4932
|
-
{commonT('actions.cancel')}
|
|
4933
|
-
</Button>
|
|
4934
|
-
<Button
|
|
4935
|
-
variant="destructive"
|
|
4936
|
-
onClick={() => void handleConfirmRemoveAssignment()}
|
|
4937
|
-
>
|
|
4938
|
-
{commonT('actions.delete')}
|
|
4939
|
-
</Button>
|
|
4940
|
-
</DialogFooter>
|
|
4941
|
-
</DialogContent>
|
|
4942
|
-
</Dialog>
|
|
4943
|
-
|
|
4944
4263
|
<TimesheetEntryCreateSheet
|
|
4945
4264
|
open={isTimesheetEntrySheetOpen}
|
|
4946
4265
|
onOpenChange={(open) => {
|
|
@@ -5022,10 +4341,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
5022
4341
|
)
|
|
5023
4342
|
: undefined
|
|
5024
4343
|
}
|
|
5025
|
-
onCountChanged={() => {
|
|
5026
|
-
setBoardState(null);
|
|
5027
|
-
void refetchTasks();
|
|
5028
|
-
}}
|
|
4344
|
+
onCountChanged={() => { setBoardState(null); void refetchTasks(); }}
|
|
5029
4345
|
onSaved={() => {
|
|
5030
4346
|
setBoardState(null);
|
|
5031
4347
|
void refetchTasks();
|