@hed-hog/operations 0.0.318 → 0.0.319
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-collaborator-costs.controller.d.ts +144 -0
- package/dist/controllers/operations-collaborator-costs.controller.d.ts.map +1 -0
- package/dist/controllers/operations-collaborator-costs.controller.js +162 -0
- package/dist/controllers/operations-collaborator-costs.controller.js.map +1 -0
- package/dist/controllers/operations-collaborators.controller.d.ts +14 -0
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
- package/dist/controllers/operations-collaborators.controller.js +11 -0
- package/dist/controllers/operations-collaborators.controller.js.map +1 -1
- package/dist/controllers/operations-projects.controller.d.ts +31 -0
- package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
- package/dist/controllers/operations-projects.controller.js +23 -0
- package/dist/controllers/operations-projects.controller.js.map +1 -1
- package/dist/controllers/operations-reports.controller.d.ts +199 -0
- package/dist/controllers/operations-reports.controller.d.ts.map +1 -0
- package/dist/controllers/operations-reports.controller.js +53 -0
- package/dist/controllers/operations-reports.controller.js.map +1 -0
- package/dist/controllers/operations-tasks.controller.d.ts +41 -2
- package/dist/controllers/operations-tasks.controller.d.ts.map +1 -1
- package/dist/controllers/operations-tasks.controller.js +17 -5
- package/dist/controllers/operations-tasks.controller.js.map +1 -1
- package/dist/dto/create-collaborator-cost.dto.d.ts +16 -0
- package/dist/dto/create-collaborator-cost.dto.d.ts.map +1 -0
- package/dist/dto/create-collaborator-cost.dto.js +88 -0
- package/dist/dto/create-collaborator-cost.dto.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/dto/create-cost-type.dto.d.ts +13 -0
- package/dist/dto/create-cost-type.dto.d.ts.map +1 -0
- package/dist/dto/create-cost-type.dto.js +87 -0
- package/dist/dto/create-cost-type.dto.js.map +1 -0
- package/dist/dto/list-approvals.dto.d.ts +2 -0
- package/dist/dto/list-approvals.dto.d.ts.map +1 -1
- package/dist/dto/list-approvals.dto.js +10 -0
- package/dist/dto/list-approvals.dto.js.map +1 -1
- package/dist/dto/list-collaborator-costs.dto.d.ts +5 -0
- package/dist/dto/list-collaborator-costs.dto.d.ts.map +1 -0
- package/dist/dto/list-collaborator-costs.dto.js +23 -0
- package/dist/dto/list-collaborator-costs.dto.js.map +1 -0
- package/dist/dto/list-cost-types.dto.d.ts +6 -0
- package/dist/dto/list-cost-types.dto.d.ts.map +1 -0
- package/dist/dto/list-cost-types.dto.js +35 -0
- package/dist/dto/list-cost-types.dto.js.map +1 -0
- package/dist/dto/list-my-projects.dto.d.ts +5 -0
- package/dist/dto/list-my-projects.dto.d.ts.map +1 -0
- package/dist/dto/list-my-projects.dto.js +23 -0
- package/dist/dto/list-my-projects.dto.js.map +1 -0
- package/dist/dto/list-my-tasks.dto.d.ts +6 -0
- package/dist/dto/list-my-tasks.dto.d.ts.map +1 -0
- package/dist/dto/list-my-tasks.dto.js +33 -0
- package/dist/dto/list-my-tasks.dto.js.map +1 -0
- package/dist/dto/list-projects.dto.d.ts +1 -0
- package/dist/dto/list-projects.dto.d.ts.map +1 -1
- package/dist/dto/list-projects.dto.js +7 -0
- package/dist/dto/list-projects.dto.js.map +1 -1
- package/dist/dto/list-reports.dto.d.ts +16 -0
- package/dist/dto/list-reports.dto.d.ts.map +1 -0
- package/dist/dto/list-reports.dto.js +75 -0
- package/dist/dto/list-reports.dto.js.map +1 -0
- package/dist/dto/list-tasks.dto.d.ts +2 -0
- package/dist/dto/list-tasks.dto.d.ts.map +1 -1
- package/dist/dto/list-tasks.dto.js +12 -0
- package/dist/dto/list-tasks.dto.js.map +1 -1
- package/dist/dto/list-timesheets.dto.d.ts +2 -0
- package/dist/dto/list-timesheets.dto.d.ts.map +1 -1
- package/dist/dto/list-timesheets.dto.js +10 -0
- package/dist/dto/list-timesheets.dto.js.map +1 -1
- package/dist/dto/update-collaborator-cost.dto.d.ts +6 -0
- package/dist/dto/update-collaborator-cost.dto.d.ts.map +1 -0
- package/dist/dto/update-collaborator-cost.dto.js +9 -0
- package/dist/dto/update-collaborator-cost.dto.js.map +1 -0
- package/dist/dto/update-task.dto.d.ts +1 -0
- package/dist/dto/update-task.dto.d.ts.map +1 -1
- package/dist/dto/update-task.dto.js +6 -0
- package/dist/dto/update-task.dto.js.map +1 -1
- package/dist/operations.module.d.ts.map +1 -1
- package/dist/operations.module.js +4 -0
- package/dist/operations.module.js.map +1 -1
- package/dist/operations.service.d.ts +457 -3
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +1445 -208
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +31 -7
- package/dist/operations.service.spec.js.map +1 -1
- package/hedhog/data/menu.yaml +112 -7
- package/hedhog/data/operations_cost_type.yaml +166 -0
- package/hedhog/data/route.yaml +185 -0
- package/hedhog/frontend/app/_components/collaborator-costs-section.tsx.ejs +884 -0
- package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +94 -15
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +219 -94
- package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +21 -32
- package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +178 -89
- package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +1185 -0
- package/hedhog/frontend/app/_components/operations-calendar-view.tsx.ejs +306 -0
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +943 -782
- package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +223 -0
- package/hedhog/frontend/app/_lib/api.ts.ejs +162 -0
- package/hedhog/frontend/app/_lib/types.ts.ejs +229 -3
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +11 -3
- package/hedhog/frontend/app/approvals/page.tsx.ejs +191 -46
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +133 -25
- package/hedhog/frontend/app/my-projects/[id]/page.tsx.ejs +11 -0
- package/hedhog/frontend/app/my-projects/page.tsx.ejs +440 -0
- package/hedhog/frontend/app/my-tasks/page.tsx.ejs +1304 -0
- package/hedhog/frontend/app/reports/collaborators/page.tsx.ejs +771 -0
- package/hedhog/frontend/app/reports/projects/page.tsx.ejs +809 -0
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +322 -58
- package/hedhog/frontend/messages/en.json +234 -25
- package/hedhog/frontend/messages/pt.json +234 -25
- package/hedhog/table/operations_collaborator.yaml +0 -4
- package/hedhog/table/operations_collaborator_compensation_history.yaml +28 -0
- package/hedhog/table/operations_collaborator_cost.yaml +56 -0
- package/hedhog/table/operations_cost_type.yaml +38 -0
- package/package.json +6 -6
- package/src/controllers/operations-collaborator-costs.controller.ts +147 -0
- package/src/controllers/operations-collaborators.controller.ts +19 -8
- package/src/controllers/operations-projects.controller.ts +19 -8
- package/src/controllers/operations-reports.controller.ts +32 -0
- package/src/controllers/operations-tasks.controller.ts +32 -12
- package/src/dto/create-collaborator-cost.dto.ts +78 -0
- package/src/dto/create-collaborator.dto.ts +9 -14
- package/src/dto/create-cost-type.dto.ts +62 -0
- package/src/dto/list-approvals.dto.ts +8 -0
- package/src/dto/list-collaborator-costs.dto.ts +8 -0
- package/src/dto/list-cost-types.dto.ts +19 -0
- package/src/dto/list-my-projects.dto.ts +8 -0
- package/src/dto/list-my-tasks.dto.ts +17 -0
- package/src/dto/list-projects.dto.ts +7 -1
- package/src/dto/list-reports.dto.ts +51 -0
- package/src/dto/list-tasks.dto.ts +11 -1
- package/src/dto/list-timesheets.dto.ts +8 -0
- package/src/dto/update-collaborator-cost.dto.ts +4 -0
- package/src/dto/update-task.dto.ts +6 -0
- package/src/operations.module.ts +7 -3
- package/src/operations.service.spec.ts +45 -7
- package/src/operations.service.ts +1992 -225
|
@@ -57,6 +57,8 @@ import { CSS } from '@dnd-kit/utilities';
|
|
|
57
57
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
58
58
|
import {
|
|
59
59
|
AlarmClock,
|
|
60
|
+
Archive,
|
|
61
|
+
ArchiveRestore,
|
|
60
62
|
BarChart3,
|
|
61
63
|
FileText,
|
|
62
64
|
FolderKanban,
|
|
@@ -81,7 +83,10 @@ import {
|
|
|
81
83
|
} from 'recharts';
|
|
82
84
|
import { fetchOperations, mutateOperations } from '../_lib/api';
|
|
83
85
|
import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
|
|
84
|
-
import type {
|
|
86
|
+
import type {
|
|
87
|
+
OperationsProjectDetails,
|
|
88
|
+
OperationsTaskOption,
|
|
89
|
+
} from '../_lib/types';
|
|
85
90
|
import {
|
|
86
91
|
formatCurrency,
|
|
87
92
|
formatDate,
|
|
@@ -95,6 +100,7 @@ import { OperationsHeader } from './operations-header';
|
|
|
95
100
|
import { ProjectFormScreen } from './project-form-screen';
|
|
96
101
|
import { SectionCard } from './section-card';
|
|
97
102
|
import { StatusBadge } from './status-badge';
|
|
103
|
+
import { TaskDetailSheet, type TaskDetailSheetData } from './task-detail-sheet';
|
|
98
104
|
|
|
99
105
|
type BoardColumnId = 'todo' | 'doing' | 'review' | 'done';
|
|
100
106
|
|
|
@@ -237,11 +243,17 @@ function DroppableColumn({
|
|
|
237
243
|
|
|
238
244
|
function DraggableTaskCard({
|
|
239
245
|
task,
|
|
246
|
+
disabled = false,
|
|
240
247
|
children,
|
|
241
248
|
}: {
|
|
242
249
|
task: BoardTask;
|
|
250
|
+
disabled?: boolean;
|
|
243
251
|
children: (isDragging: boolean) => React.ReactNode;
|
|
244
252
|
}) {
|
|
253
|
+
if (disabled) {
|
|
254
|
+
return <div>{children(false)}</div>;
|
|
255
|
+
}
|
|
256
|
+
|
|
245
257
|
const { attributes, listeners, setNodeRef, transform, isDragging } =
|
|
246
258
|
useDraggable({ id: taskDragId(task.id) });
|
|
247
259
|
|
|
@@ -319,6 +331,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
319
331
|
const contractT = useTranslations('operations.ContractFormPage');
|
|
320
332
|
const { request, currentLocaleCode, getSettingValue } = useApp();
|
|
321
333
|
const access = useOperationsAccess();
|
|
334
|
+
const isLimitedView = !access.isDirector && !access.isSupervisor;
|
|
322
335
|
const router = useRouter();
|
|
323
336
|
const pathname = usePathname();
|
|
324
337
|
const searchParams = useSearchParams();
|
|
@@ -365,8 +378,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
365
378
|
};
|
|
366
379
|
|
|
367
380
|
const isEditSheetOpen = useMemo(
|
|
368
|
-
() =>
|
|
369
|
-
|
|
381
|
+
() =>
|
|
382
|
+
!isLimitedView &&
|
|
383
|
+
shouldOpenEditSheet(searchParams.get('edit'), projectId),
|
|
384
|
+
[isLimitedView, projectId, searchParams]
|
|
370
385
|
);
|
|
371
386
|
|
|
372
387
|
const updateSheetQuery = (open: boolean) => {
|
|
@@ -443,6 +458,17 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
443
458
|
enabled: Boolean(project),
|
|
444
459
|
});
|
|
445
460
|
|
|
461
|
+
const { data: archivedTasksResponse, refetch: refetchArchivedTasks } =
|
|
462
|
+
useQuery<{ data: OperationsTaskOption[] }>({
|
|
463
|
+
queryKey: ['operations-project-archived-tasks', projectId],
|
|
464
|
+
queryFn: () =>
|
|
465
|
+
fetchOperations<{ data: OperationsTaskOption[] }>(
|
|
466
|
+
request,
|
|
467
|
+
`/operations/tasks?projectId=${projectId}&pageSize=100&sortField=createdAt&sortOrder=desc&archived=true`
|
|
468
|
+
),
|
|
469
|
+
enabled: Boolean(project),
|
|
470
|
+
});
|
|
471
|
+
|
|
446
472
|
const { data: projectStats } = useQuery<{
|
|
447
473
|
weeklyVelocity: Array<{ weekLabel: string; loggedHours: number }>;
|
|
448
474
|
allocationByCollaborator: Array<{ name: string; allocation: number }>;
|
|
@@ -455,19 +481,29 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
455
481
|
queryKey: ['operations-project-stats', projectId],
|
|
456
482
|
queryFn: () =>
|
|
457
483
|
fetchOperations(request, `/operations/projects/${projectId}/stats`),
|
|
458
|
-
enabled: Boolean(project),
|
|
484
|
+
enabled: Boolean(project) && !isLimitedView,
|
|
459
485
|
});
|
|
460
486
|
|
|
461
487
|
const [boardState, setBoardState] = useState<BoardState | null>(null);
|
|
462
|
-
const [
|
|
488
|
+
const [selectedTask, setSelectedTask] = useState<TaskDetailSheetData | null>(
|
|
489
|
+
null
|
|
490
|
+
);
|
|
463
491
|
const [taskFormOpen, setTaskFormOpen] = useState(false);
|
|
464
492
|
const [editingTaskId, setEditingTaskId] = useState<number | null>(null);
|
|
465
493
|
const [taskFormData, setTaskFormData] =
|
|
466
494
|
useState<TaskFormState>(EMPTY_TASK_FORM);
|
|
467
495
|
const [taskFormLoading, setTaskFormLoading] = useState(false);
|
|
468
|
-
const [
|
|
496
|
+
const [deletePromptTask, setDeletePromptTask] =
|
|
497
|
+
useState<TaskDetailSheetData | null>(null);
|
|
469
498
|
|
|
470
499
|
const apiTasks = useMemo(() => rawTasks.map(apiTaskToBoardTask), [rawTasks]);
|
|
500
|
+
const archivedTasks = useMemo(
|
|
501
|
+
() =>
|
|
502
|
+
(archivedTasksResponse?.data ?? []).filter((task) =>
|
|
503
|
+
Boolean(task.deletedAt)
|
|
504
|
+
),
|
|
505
|
+
[archivedTasksResponse]
|
|
506
|
+
);
|
|
471
507
|
|
|
472
508
|
const taskColumns: BoardColumns = useMemo(() => {
|
|
473
509
|
if (project && boardState?.projectId === project.id) {
|
|
@@ -476,21 +512,6 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
476
512
|
return splitTasksByColumn(apiTasks);
|
|
477
513
|
}, [project, boardState, apiTasks]);
|
|
478
514
|
|
|
479
|
-
const allTasks = useMemo(
|
|
480
|
-
() => [
|
|
481
|
-
...taskColumns.todo,
|
|
482
|
-
...taskColumns.doing,
|
|
483
|
-
...taskColumns.review,
|
|
484
|
-
...taskColumns.done,
|
|
485
|
-
],
|
|
486
|
-
[taskColumns]
|
|
487
|
-
);
|
|
488
|
-
|
|
489
|
-
const selectedTask = useMemo(
|
|
490
|
-
() => allTasks.find((task) => task.id === selectedTaskId) ?? null,
|
|
491
|
-
[allTasks, selectedTaskId]
|
|
492
|
-
);
|
|
493
|
-
|
|
494
515
|
const taskAssigneeOptions = useMemo(() => {
|
|
495
516
|
const seen = new Set<number>();
|
|
496
517
|
return (
|
|
@@ -536,7 +557,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
536
557
|
task.estimateHours != null ? String(task.estimateHours) : '',
|
|
537
558
|
tags: task.tags ?? '',
|
|
538
559
|
});
|
|
539
|
-
|
|
560
|
+
setSelectedTask(null);
|
|
540
561
|
setTaskFormOpen(true);
|
|
541
562
|
}, []);
|
|
542
563
|
|
|
@@ -572,31 +593,83 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
572
593
|
}
|
|
573
594
|
setBoardState(null);
|
|
574
595
|
await refetchTasks();
|
|
596
|
+
await refetchArchivedTasks();
|
|
575
597
|
setTaskFormOpen(false);
|
|
576
598
|
setEditingTaskId(null);
|
|
577
599
|
setTaskFormData(EMPTY_TASK_FORM);
|
|
578
600
|
} finally {
|
|
579
601
|
setTaskFormLoading(false);
|
|
580
602
|
}
|
|
581
|
-
}, [
|
|
603
|
+
}, [
|
|
604
|
+
taskFormData,
|
|
605
|
+
editingTaskId,
|
|
606
|
+
projectId,
|
|
607
|
+
request,
|
|
608
|
+
refetchTasks,
|
|
609
|
+
refetchArchivedTasks,
|
|
610
|
+
]);
|
|
611
|
+
|
|
612
|
+
const handleArchiveTask = useCallback(
|
|
613
|
+
async (taskId: number) => {
|
|
614
|
+
try {
|
|
615
|
+
await mutateOperations(
|
|
616
|
+
request,
|
|
617
|
+
`/operations/tasks/${taskId}`,
|
|
618
|
+
'PATCH',
|
|
619
|
+
{
|
|
620
|
+
archived: true,
|
|
621
|
+
}
|
|
622
|
+
);
|
|
623
|
+
setBoardState(null);
|
|
624
|
+
setSelectedTask(null);
|
|
625
|
+
await refetchTasks();
|
|
626
|
+
await refetchArchivedTasks();
|
|
627
|
+
} catch {
|
|
628
|
+
// ignore
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
[request, refetchTasks, refetchArchivedTasks]
|
|
632
|
+
);
|
|
582
633
|
|
|
583
|
-
const
|
|
634
|
+
const handleRestoreTask = useCallback(
|
|
584
635
|
async (taskId: number) => {
|
|
585
636
|
try {
|
|
586
637
|
await mutateOperations(
|
|
587
638
|
request,
|
|
588
639
|
`/operations/tasks/${taskId}`,
|
|
640
|
+
'PATCH',
|
|
641
|
+
{
|
|
642
|
+
archived: false,
|
|
643
|
+
}
|
|
644
|
+
);
|
|
645
|
+
setSelectedTask(null);
|
|
646
|
+
await refetchTasks();
|
|
647
|
+
await refetchArchivedTasks();
|
|
648
|
+
} catch {
|
|
649
|
+
// ignore
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
[request, refetchTasks, refetchArchivedTasks]
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
const handleDeleteTask = useCallback(
|
|
656
|
+
async (taskId: number) => {
|
|
657
|
+
try {
|
|
658
|
+
await mutateOperations(
|
|
659
|
+
request,
|
|
660
|
+
`/operations/tasks/${taskId}?permanent=true`,
|
|
589
661
|
'DELETE'
|
|
590
662
|
);
|
|
591
663
|
setBoardState(null);
|
|
592
|
-
|
|
593
|
-
|
|
664
|
+
setSelectedTask(null);
|
|
665
|
+
setDeletePromptTask(null);
|
|
594
666
|
await refetchTasks();
|
|
667
|
+
await refetchArchivedTasks();
|
|
595
668
|
} catch {
|
|
596
669
|
// ignore
|
|
597
670
|
}
|
|
598
671
|
},
|
|
599
|
-
[request, refetchTasks]
|
|
672
|
+
[request, refetchTasks, refetchArchivedTasks]
|
|
600
673
|
);
|
|
601
674
|
|
|
602
675
|
const allocationChartData = useMemo(() => {
|
|
@@ -772,232 +845,91 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
772
845
|
}
|
|
773
846
|
/>
|
|
774
847
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
848
|
+
{!isLimitedView ? (
|
|
849
|
+
<>
|
|
850
|
+
<div className="rounded-xl border bg-linear-to-b from-muted/40 to-background p-3 sm:p-4">
|
|
851
|
+
<KpiCardsGrid items={cards} />
|
|
852
|
+
</div>
|
|
778
853
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
</div>
|
|
791
|
-
<div>
|
|
792
|
-
<dt className="text-muted-foreground">
|
|
793
|
-
{commonT('labels.code')}
|
|
794
|
-
</dt>
|
|
795
|
-
<dd className="font-medium">
|
|
796
|
-
{project.code || commonT('labels.notAvailable')}
|
|
797
|
-
</dd>
|
|
798
|
-
</div>
|
|
799
|
-
<div>
|
|
800
|
-
<dt className="text-muted-foreground">
|
|
801
|
-
{commonT('labels.client')}
|
|
802
|
-
</dt>
|
|
803
|
-
<dd className="font-medium">
|
|
804
|
-
<div className="flex items-center gap-2">
|
|
805
|
-
<Avatar className="h-8 w-8 border border-border/60 bg-muted">
|
|
806
|
-
<AvatarImage
|
|
807
|
-
src={getPersonAvatarUrl(project.clientAvatarId)}
|
|
808
|
-
alt={project.clientName || commonT('labels.client')}
|
|
809
|
-
/>
|
|
810
|
-
<AvatarFallback className="bg-muted text-xs font-semibold text-foreground">
|
|
811
|
-
{getInitials(
|
|
812
|
-
project.clientName || commonT('labels.client')
|
|
813
|
-
)}
|
|
814
|
-
</AvatarFallback>
|
|
815
|
-
</Avatar>
|
|
816
|
-
<span>
|
|
817
|
-
{project.clientName || commonT('labels.notAvailable')}
|
|
818
|
-
</span>
|
|
854
|
+
<div className="grid gap-4 xl:grid-cols-12">
|
|
855
|
+
<SectionCard
|
|
856
|
+
title={t('sections.overview')}
|
|
857
|
+
className="rounded-xl border bg-card p-4 shadow-sm xl:col-span-7"
|
|
858
|
+
>
|
|
859
|
+
<dl className="grid gap-3 text-sm sm:grid-cols-2 xl:grid-cols-3">
|
|
860
|
+
<div>
|
|
861
|
+
<dt className="text-muted-foreground">
|
|
862
|
+
{commonT('labels.project')}
|
|
863
|
+
</dt>
|
|
864
|
+
<dd className="font-medium">{project.name}</dd>
|
|
819
865
|
</div>
|
|
820
|
-
</dd>
|
|
821
|
-
</div>
|
|
822
|
-
<div>
|
|
823
|
-
<dt className="text-muted-foreground">
|
|
824
|
-
{commonT('labels.manager')}
|
|
825
|
-
</dt>
|
|
826
|
-
<dd className="font-medium">
|
|
827
|
-
{project.managerName || commonT('labels.notAssigned')}
|
|
828
|
-
</dd>
|
|
829
|
-
</div>
|
|
830
|
-
<div>
|
|
831
|
-
<dt className="text-muted-foreground">
|
|
832
|
-
{commonT('labels.status')}
|
|
833
|
-
</dt>
|
|
834
|
-
<dd className="font-medium">
|
|
835
|
-
<StatusBadge
|
|
836
|
-
label={getProjectStatusLabel(project.status)}
|
|
837
|
-
className={getStatusBadgeClass(project.status)}
|
|
838
|
-
/>
|
|
839
|
-
</dd>
|
|
840
|
-
</div>
|
|
841
|
-
<div>
|
|
842
|
-
<dt className="text-muted-foreground">
|
|
843
|
-
{commonT('labels.deliveryModel')}
|
|
844
|
-
</dt>
|
|
845
|
-
<dd className="font-medium">
|
|
846
|
-
{project.deliveryModel
|
|
847
|
-
? getDeliveryModelLabel(project.deliveryModel)
|
|
848
|
-
: commonT('labels.notAvailable')}
|
|
849
|
-
</dd>
|
|
850
|
-
</div>
|
|
851
|
-
<div>
|
|
852
|
-
<dt className="text-muted-foreground">
|
|
853
|
-
{commonT('labels.startDate')}
|
|
854
|
-
</dt>
|
|
855
|
-
<dd className="font-medium">
|
|
856
|
-
{formatDate(
|
|
857
|
-
project.startDate,
|
|
858
|
-
getSettingValue,
|
|
859
|
-
currentLocaleCode
|
|
860
|
-
)}
|
|
861
|
-
</dd>
|
|
862
|
-
</div>
|
|
863
|
-
<div>
|
|
864
|
-
<dt className="text-muted-foreground">
|
|
865
|
-
{commonT('labels.endDate')}
|
|
866
|
-
</dt>
|
|
867
|
-
<dd className="font-medium">
|
|
868
|
-
{formatDate(
|
|
869
|
-
project.endDate,
|
|
870
|
-
getSettingValue,
|
|
871
|
-
currentLocaleCode
|
|
872
|
-
)}
|
|
873
|
-
</dd>
|
|
874
|
-
</div>
|
|
875
|
-
<div>
|
|
876
|
-
<dt className="text-muted-foreground">
|
|
877
|
-
{commonT('labels.budget')}
|
|
878
|
-
</dt>
|
|
879
|
-
<dd className="font-medium">
|
|
880
|
-
{project.budgetAmount
|
|
881
|
-
? formatCurrency(
|
|
882
|
-
project.budgetAmount,
|
|
883
|
-
getSettingValue,
|
|
884
|
-
currentLocaleCode
|
|
885
|
-
)
|
|
886
|
-
: commonT('labels.notAvailable')}
|
|
887
|
-
</dd>
|
|
888
|
-
</div>
|
|
889
|
-
<div>
|
|
890
|
-
<dt className="text-muted-foreground">
|
|
891
|
-
{commonT('labels.progress')}
|
|
892
|
-
</dt>
|
|
893
|
-
<dd className="font-medium">
|
|
894
|
-
{formatPercent(project.progressPercent)}
|
|
895
|
-
</dd>
|
|
896
|
-
</div>
|
|
897
|
-
<div>
|
|
898
|
-
<dt className="text-muted-foreground">
|
|
899
|
-
{commonT('labels.timeline')}
|
|
900
|
-
</dt>
|
|
901
|
-
<dd className="font-medium">
|
|
902
|
-
{formatDateRange(
|
|
903
|
-
project.startDate,
|
|
904
|
-
project.endDate,
|
|
905
|
-
getSettingValue,
|
|
906
|
-
currentLocaleCode
|
|
907
|
-
)}
|
|
908
|
-
</dd>
|
|
909
|
-
</div>
|
|
910
|
-
<div>
|
|
911
|
-
<dt className="text-muted-foreground">
|
|
912
|
-
{commonT('labels.contractStatus')}
|
|
913
|
-
</dt>
|
|
914
|
-
<dd className="font-medium">
|
|
915
|
-
{project.contractStatus ? (
|
|
916
|
-
<StatusBadge
|
|
917
|
-
label={getContractStatusLabel(project.contractStatus)}
|
|
918
|
-
className={getStatusBadgeClass(project.contractStatus)}
|
|
919
|
-
/>
|
|
920
|
-
) : (
|
|
921
|
-
commonT('labels.notAssigned')
|
|
922
|
-
)}
|
|
923
|
-
</dd>
|
|
924
|
-
</div>
|
|
925
|
-
</dl>
|
|
926
|
-
{project.summary ? (
|
|
927
|
-
<div className="mt-4 rounded-lg border border-border/70 bg-muted/30 p-3 text-sm text-muted-foreground">
|
|
928
|
-
{project.summary}
|
|
929
|
-
</div>
|
|
930
|
-
) : null}
|
|
931
|
-
</SectionCard>
|
|
932
|
-
|
|
933
|
-
<SectionCard
|
|
934
|
-
title={t('sections.contract')}
|
|
935
|
-
className="rounded-xl border bg-card p-4 shadow-sm xl:col-span-5"
|
|
936
|
-
>
|
|
937
|
-
{project.relatedContract ? (
|
|
938
|
-
<div className="space-y-3">
|
|
939
|
-
<div className="flex items-center justify-between rounded-lg border bg-muted/20 px-4 py-3">
|
|
940
866
|
<div>
|
|
941
|
-
<
|
|
942
|
-
{
|
|
943
|
-
</
|
|
944
|
-
<
|
|
945
|
-
{
|
|
946
|
-
|
|
947
|
-
project.relatedContract.clientName,
|
|
948
|
-
]
|
|
949
|
-
.filter(Boolean)
|
|
950
|
-
.join(' • ') || commonT('labels.notAvailable')}
|
|
951
|
-
</div>
|
|
867
|
+
<dt className="text-muted-foreground">
|
|
868
|
+
{commonT('labels.code')}
|
|
869
|
+
</dt>
|
|
870
|
+
<dd className="font-medium">
|
|
871
|
+
{project.code || commonT('labels.notAvailable')}
|
|
872
|
+
</dd>
|
|
952
873
|
</div>
|
|
953
|
-
<StatusBadge
|
|
954
|
-
label={getContractStatusLabel(project.relatedContract.status)}
|
|
955
|
-
className={getStatusBadgeClass(
|
|
956
|
-
project.relatedContract.status
|
|
957
|
-
)}
|
|
958
|
-
/>
|
|
959
|
-
</div>
|
|
960
|
-
<dl className="grid gap-3 text-sm md:grid-cols-2">
|
|
961
874
|
<div>
|
|
962
875
|
<dt className="text-muted-foreground">
|
|
963
|
-
{commonT('labels.
|
|
876
|
+
{commonT('labels.client')}
|
|
964
877
|
</dt>
|
|
965
878
|
<dd className="font-medium">
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
879
|
+
<div className="flex items-center gap-2">
|
|
880
|
+
<Avatar className="h-8 w-8 border border-border/60 bg-muted">
|
|
881
|
+
<AvatarImage
|
|
882
|
+
src={getPersonAvatarUrl(project.clientAvatarId)}
|
|
883
|
+
alt={project.clientName || commonT('labels.client')}
|
|
884
|
+
/>
|
|
885
|
+
<AvatarFallback className="bg-muted text-xs font-semibold text-foreground">
|
|
886
|
+
{getInitials(
|
|
887
|
+
project.clientName || commonT('labels.client')
|
|
888
|
+
)}
|
|
889
|
+
</AvatarFallback>
|
|
890
|
+
</Avatar>
|
|
891
|
+
<span>
|
|
892
|
+
{project.clientName || commonT('labels.notAvailable')}
|
|
893
|
+
</span>
|
|
894
|
+
</div>
|
|
971
895
|
</dd>
|
|
972
896
|
</div>
|
|
973
897
|
<div>
|
|
974
898
|
<dt className="text-muted-foreground">
|
|
975
|
-
{commonT('labels.
|
|
899
|
+
{commonT('labels.manager')}
|
|
976
900
|
</dt>
|
|
977
901
|
<dd className="font-medium">
|
|
978
|
-
{project.
|
|
979
|
-
? getContractTypeLabel(
|
|
980
|
-
project.relatedContract.contractType
|
|
981
|
-
)
|
|
982
|
-
: commonT('labels.notAvailable')}
|
|
902
|
+
{project.managerName || commonT('labels.notAssigned')}
|
|
983
903
|
</dd>
|
|
984
904
|
</div>
|
|
985
905
|
<div>
|
|
986
906
|
<dt className="text-muted-foreground">
|
|
987
|
-
{commonT('labels.
|
|
907
|
+
{commonT('labels.status')}
|
|
988
908
|
</dt>
|
|
989
909
|
<dd className="font-medium">
|
|
990
|
-
|
|
910
|
+
<StatusBadge
|
|
911
|
+
label={getProjectStatusLabel(project.status)}
|
|
912
|
+
className={getStatusBadgeClass(project.status)}
|
|
913
|
+
/>
|
|
991
914
|
</dd>
|
|
992
915
|
</div>
|
|
993
916
|
<div>
|
|
994
917
|
<dt className="text-muted-foreground">
|
|
995
|
-
{commonT('labels.
|
|
918
|
+
{commonT('labels.deliveryModel')}
|
|
996
919
|
</dt>
|
|
997
920
|
<dd className="font-medium">
|
|
998
|
-
{
|
|
999
|
-
project.
|
|
1000
|
-
|
|
921
|
+
{project.deliveryModel
|
|
922
|
+
? getDeliveryModelLabel(project.deliveryModel)
|
|
923
|
+
: commonT('labels.notAvailable')}
|
|
924
|
+
</dd>
|
|
925
|
+
</div>
|
|
926
|
+
<div>
|
|
927
|
+
<dt className="text-muted-foreground">
|
|
928
|
+
{commonT('labels.startDate')}
|
|
929
|
+
</dt>
|
|
930
|
+
<dd className="font-medium">
|
|
931
|
+
{formatDate(
|
|
932
|
+
project.startDate,
|
|
1001
933
|
getSettingValue,
|
|
1002
934
|
currentLocaleCode
|
|
1003
935
|
)}
|
|
@@ -1005,14 +937,14 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1005
937
|
</div>
|
|
1006
938
|
<div>
|
|
1007
939
|
<dt className="text-muted-foreground">
|
|
1008
|
-
{commonT('labels.
|
|
940
|
+
{commonT('labels.endDate')}
|
|
1009
941
|
</dt>
|
|
1010
942
|
<dd className="font-medium">
|
|
1011
|
-
{
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
943
|
+
{formatDate(
|
|
944
|
+
project.endDate,
|
|
945
|
+
getSettingValue,
|
|
946
|
+
currentLocaleCode
|
|
947
|
+
)}
|
|
1016
948
|
</dd>
|
|
1017
949
|
</div>
|
|
1018
950
|
<div>
|
|
@@ -1020,148 +952,309 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1020
952
|
{commonT('labels.budget')}
|
|
1021
953
|
</dt>
|
|
1022
954
|
<dd className="font-medium">
|
|
1023
|
-
{project.
|
|
955
|
+
{project.budgetAmount
|
|
1024
956
|
? formatCurrency(
|
|
1025
|
-
project.
|
|
957
|
+
project.budgetAmount,
|
|
1026
958
|
getSettingValue,
|
|
1027
959
|
currentLocaleCode
|
|
1028
960
|
)
|
|
1029
961
|
: commonT('labels.notAvailable')}
|
|
1030
962
|
</dd>
|
|
1031
963
|
</div>
|
|
964
|
+
<div>
|
|
965
|
+
<dt className="text-muted-foreground">
|
|
966
|
+
{commonT('labels.progress')}
|
|
967
|
+
</dt>
|
|
968
|
+
<dd className="font-medium">
|
|
969
|
+
{formatPercent(project.progressPercent)}
|
|
970
|
+
</dd>
|
|
971
|
+
</div>
|
|
972
|
+
<div>
|
|
973
|
+
<dt className="text-muted-foreground">
|
|
974
|
+
{commonT('labels.timeline')}
|
|
975
|
+
</dt>
|
|
976
|
+
<dd className="font-medium">
|
|
977
|
+
{formatDateRange(
|
|
978
|
+
project.startDate,
|
|
979
|
+
project.endDate,
|
|
980
|
+
getSettingValue,
|
|
981
|
+
currentLocaleCode
|
|
982
|
+
)}
|
|
983
|
+
</dd>
|
|
984
|
+
</div>
|
|
985
|
+
<div>
|
|
986
|
+
<dt className="text-muted-foreground">
|
|
987
|
+
{commonT('labels.contractStatus')}
|
|
988
|
+
</dt>
|
|
989
|
+
<dd className="font-medium">
|
|
990
|
+
{project.contractStatus ? (
|
|
991
|
+
<StatusBadge
|
|
992
|
+
label={getContractStatusLabel(project.contractStatus)}
|
|
993
|
+
className={getStatusBadgeClass(project.contractStatus)}
|
|
994
|
+
/>
|
|
995
|
+
) : (
|
|
996
|
+
commonT('labels.notAssigned')
|
|
997
|
+
)}
|
|
998
|
+
</dd>
|
|
999
|
+
</div>
|
|
1032
1000
|
</dl>
|
|
1001
|
+
{project.summary ? (
|
|
1002
|
+
<div className="mt-4 rounded-lg border border-border/70 bg-muted/30 p-3 text-sm text-muted-foreground">
|
|
1003
|
+
{project.summary}
|
|
1004
|
+
</div>
|
|
1005
|
+
) : null}
|
|
1006
|
+
</SectionCard>
|
|
1033
1007
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1008
|
+
<SectionCard
|
|
1009
|
+
title={t('sections.contract')}
|
|
1010
|
+
className="rounded-xl border bg-card p-4 shadow-sm xl:col-span-5"
|
|
1011
|
+
>
|
|
1012
|
+
{project.relatedContract ? (
|
|
1013
|
+
<div className="space-y-3">
|
|
1014
|
+
<div className="flex items-center justify-between rounded-lg border bg-muted/20 px-4 py-3">
|
|
1015
|
+
<div>
|
|
1016
|
+
<div className="font-medium">
|
|
1017
|
+
{project.relatedContract.name}
|
|
1018
|
+
</div>
|
|
1019
|
+
<div className="text-sm text-muted-foreground">
|
|
1020
|
+
{[
|
|
1021
|
+
project.relatedContract.code,
|
|
1022
|
+
project.relatedContract.clientName,
|
|
1023
|
+
]
|
|
1024
|
+
.filter(Boolean)
|
|
1025
|
+
.join(' • ') || commonT('labels.notAvailable')}
|
|
1026
|
+
</div>
|
|
1027
|
+
</div>
|
|
1028
|
+
<StatusBadge
|
|
1029
|
+
label={getContractStatusLabel(
|
|
1030
|
+
project.relatedContract.status
|
|
1031
|
+
)}
|
|
1032
|
+
className={getStatusBadgeClass(
|
|
1033
|
+
project.relatedContract.status
|
|
1034
|
+
)}
|
|
1035
|
+
/>
|
|
1036
|
+
</div>
|
|
1037
|
+
<dl className="grid gap-3 text-sm md:grid-cols-2">
|
|
1038
|
+
<div>
|
|
1039
|
+
<dt className="text-muted-foreground">
|
|
1040
|
+
{commonT('labels.contractCategory')}
|
|
1041
|
+
</dt>
|
|
1042
|
+
<dd className="font-medium">
|
|
1043
|
+
{project.relatedContract.contractCategory
|
|
1044
|
+
? getContractCategoryLabel(
|
|
1045
|
+
project.relatedContract.contractCategory
|
|
1046
|
+
)
|
|
1047
|
+
: commonT('labels.notAvailable')}
|
|
1048
|
+
</dd>
|
|
1049
|
+
</div>
|
|
1050
|
+
<div>
|
|
1051
|
+
<dt className="text-muted-foreground">
|
|
1052
|
+
{commonT('labels.contractType')}
|
|
1053
|
+
</dt>
|
|
1054
|
+
<dd className="font-medium">
|
|
1055
|
+
{project.relatedContract.contractType
|
|
1056
|
+
? getContractTypeLabel(
|
|
1057
|
+
project.relatedContract.contractType
|
|
1058
|
+
)
|
|
1059
|
+
: commonT('labels.notAvailable')}
|
|
1060
|
+
</dd>
|
|
1061
|
+
</div>
|
|
1062
|
+
<div>
|
|
1063
|
+
<dt className="text-muted-foreground">
|
|
1064
|
+
{commonT('labels.billingModel')}
|
|
1065
|
+
</dt>
|
|
1066
|
+
<dd className="font-medium">
|
|
1067
|
+
{getBillingModelLabel(
|
|
1068
|
+
project.relatedContract.billingModel
|
|
1069
|
+
)}
|
|
1070
|
+
</dd>
|
|
1071
|
+
</div>
|
|
1072
|
+
<div>
|
|
1073
|
+
<dt className="text-muted-foreground">
|
|
1074
|
+
{commonT('labels.timeline')}
|
|
1075
|
+
</dt>
|
|
1076
|
+
<dd className="font-medium">
|
|
1077
|
+
{formatDateRange(
|
|
1078
|
+
project.relatedContract.startDate,
|
|
1079
|
+
project.relatedContract.endDate,
|
|
1080
|
+
getSettingValue,
|
|
1081
|
+
currentLocaleCode
|
|
1082
|
+
)}
|
|
1083
|
+
</dd>
|
|
1084
|
+
</div>
|
|
1085
|
+
<div>
|
|
1086
|
+
<dt className="text-muted-foreground">
|
|
1087
|
+
{commonT('labels.signatureStatus')}
|
|
1088
|
+
</dt>
|
|
1089
|
+
<dd className="font-medium">
|
|
1090
|
+
{project.relatedContract.signatureStatus
|
|
1091
|
+
? getSignatureStatusLabel(
|
|
1092
|
+
project.relatedContract.signatureStatus
|
|
1093
|
+
)
|
|
1094
|
+
: commonT('labels.notAvailable')}
|
|
1095
|
+
</dd>
|
|
1096
|
+
</div>
|
|
1097
|
+
<div>
|
|
1098
|
+
<dt className="text-muted-foreground">
|
|
1099
|
+
{commonT('labels.budget')}
|
|
1100
|
+
</dt>
|
|
1101
|
+
<dd className="font-medium">
|
|
1102
|
+
{project.relatedContract.budgetAmount
|
|
1103
|
+
? formatCurrency(
|
|
1104
|
+
project.relatedContract.budgetAmount,
|
|
1105
|
+
getSettingValue,
|
|
1106
|
+
currentLocaleCode
|
|
1107
|
+
)
|
|
1108
|
+
: commonT('labels.notAvailable')}
|
|
1109
|
+
</dd>
|
|
1110
|
+
</div>
|
|
1111
|
+
</dl>
|
|
1112
|
+
|
|
1113
|
+
<Button variant="outline" size="sm" asChild className="w-fit">
|
|
1114
|
+
<Link
|
|
1115
|
+
href={`/operations/contracts?edit=${project.relatedContract.id}`}
|
|
1116
|
+
>
|
|
1117
|
+
<FileText className="size-4" />
|
|
1118
|
+
{commonT('actions.openContract')}
|
|
1119
|
+
</Link>
|
|
1120
|
+
</Button>
|
|
1121
|
+
</div>
|
|
1122
|
+
) : (
|
|
1123
|
+
<p className="text-sm text-muted-foreground">
|
|
1124
|
+
{t('noContract')}
|
|
1125
|
+
</p>
|
|
1126
|
+
)}
|
|
1127
|
+
</SectionCard>
|
|
1128
|
+
</div>
|
|
1048
1129
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1130
|
+
<div className="grid gap-4 xl:grid-cols-12">
|
|
1131
|
+
<SectionCard
|
|
1132
|
+
title={t('sections.deliveryHealth')}
|
|
1133
|
+
description={t('sections.deliveryHealthDescription')}
|
|
1134
|
+
className="rounded-xl border bg-card p-4 shadow-sm xl:col-span-7"
|
|
1135
|
+
>
|
|
1136
|
+
<div className="grid gap-4 lg:grid-cols-2">
|
|
1137
|
+
<div className="rounded-lg border bg-muted/10 p-3">
|
|
1138
|
+
<div className="mb-2 flex items-center gap-2 text-sm font-medium">
|
|
1139
|
+
<BarChart3 className="size-4 text-sky-700" />
|
|
1140
|
+
{t('charts.allocationByCollaborator')}
|
|
1141
|
+
</div>
|
|
1142
|
+
<ChartContainer
|
|
1143
|
+
className="h-60 w-full"
|
|
1144
|
+
config={boardChartConfig}
|
|
1145
|
+
>
|
|
1146
|
+
<BarChart data={allocationChartData}>
|
|
1147
|
+
<CartesianGrid vertical={false} />
|
|
1148
|
+
<XAxis dataKey="name" tickLine={false} axisLine={false} />
|
|
1149
|
+
<YAxis tickLine={false} axisLine={false} width={28} />
|
|
1150
|
+
<ChartTooltip
|
|
1151
|
+
content={<ChartTooltipContent hideLabel />}
|
|
1152
|
+
/>
|
|
1153
|
+
<Bar
|
|
1154
|
+
dataKey="allocation"
|
|
1155
|
+
radius={6}
|
|
1156
|
+
fill="var(--color-allocation)"
|
|
1157
|
+
/>
|
|
1158
|
+
</BarChart>
|
|
1159
|
+
</ChartContainer>
|
|
1160
|
+
</div>
|
|
1075
1161
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1162
|
+
<div className="rounded-lg border bg-muted/10 p-3">
|
|
1163
|
+
<div className="mb-2 flex items-center gap-2 text-sm font-medium">
|
|
1164
|
+
<Rocket className="size-4 text-emerald-700" />
|
|
1165
|
+
{t('charts.weeklyVelocity')}
|
|
1166
|
+
</div>
|
|
1167
|
+
<ChartContainer
|
|
1168
|
+
className="h-60 w-full"
|
|
1169
|
+
config={boardChartConfig}
|
|
1170
|
+
>
|
|
1171
|
+
<LineChart data={velocityChartData}>
|
|
1172
|
+
<CartesianGrid vertical={false} />
|
|
1173
|
+
<XAxis dataKey="week" tickLine={false} axisLine={false} />
|
|
1174
|
+
<YAxis tickLine={false} axisLine={false} width={28} />
|
|
1175
|
+
<ChartTooltip content={<ChartTooltipContent />} />
|
|
1176
|
+
<Line
|
|
1177
|
+
type="monotone"
|
|
1178
|
+
dataKey="loggedHours"
|
|
1179
|
+
stroke="var(--color-loggedHours)"
|
|
1180
|
+
strokeWidth={2.5}
|
|
1181
|
+
dot={{ r: 3 }}
|
|
1182
|
+
/>
|
|
1183
|
+
<Line
|
|
1184
|
+
type="monotone"
|
|
1185
|
+
dataKey="completedTasks"
|
|
1186
|
+
stroke="var(--color-allocation)"
|
|
1187
|
+
strokeWidth={2}
|
|
1188
|
+
dot={{ r: 3 }}
|
|
1189
|
+
/>
|
|
1190
|
+
</LineChart>
|
|
1191
|
+
</ChartContainer>
|
|
1192
|
+
</div>
|
|
1080
1193
|
</div>
|
|
1081
|
-
|
|
1082
|
-
<LineChart data={velocityChartData}>
|
|
1083
|
-
<CartesianGrid vertical={false} />
|
|
1084
|
-
<XAxis dataKey="week" tickLine={false} axisLine={false} />
|
|
1085
|
-
<YAxis tickLine={false} axisLine={false} width={28} />
|
|
1086
|
-
<ChartTooltip content={<ChartTooltipContent />} />
|
|
1087
|
-
<Line
|
|
1088
|
-
type="monotone"
|
|
1089
|
-
dataKey="loggedHours"
|
|
1090
|
-
stroke="var(--color-loggedHours)"
|
|
1091
|
-
strokeWidth={2.5}
|
|
1092
|
-
dot={{ r: 3 }}
|
|
1093
|
-
/>
|
|
1094
|
-
<Line
|
|
1095
|
-
type="monotone"
|
|
1096
|
-
dataKey="completedTasks"
|
|
1097
|
-
stroke="var(--color-allocation)"
|
|
1098
|
-
strokeWidth={2}
|
|
1099
|
-
dot={{ r: 3 }}
|
|
1100
|
-
/>
|
|
1101
|
-
</LineChart>
|
|
1102
|
-
</ChartContainer>
|
|
1103
|
-
</div>
|
|
1104
|
-
</div>
|
|
1105
|
-
</SectionCard>
|
|
1194
|
+
</SectionCard>
|
|
1106
1195
|
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1196
|
+
<SectionCard
|
|
1197
|
+
title={t('sections.quickRadar')}
|
|
1198
|
+
description={t('sections.quickRadarDescription')}
|
|
1199
|
+
className="rounded-xl border bg-card p-4 shadow-sm xl:col-span-5"
|
|
1200
|
+
>
|
|
1201
|
+
<div className="space-y-3">
|
|
1202
|
+
<div className="rounded-lg border bg-emerald-50/50 p-3">
|
|
1203
|
+
<div className="flex items-center justify-between text-sm">
|
|
1204
|
+
<span className="text-muted-foreground">
|
|
1205
|
+
{t('quickRadar.activeAssignments')}
|
|
1206
|
+
</span>
|
|
1207
|
+
<span className="font-semibold text-emerald-700">
|
|
1208
|
+
{projectStats?.quickRadar?.activeAssignments ??
|
|
1209
|
+
project.operationalIndicators.activeAssignments}
|
|
1210
|
+
</span>
|
|
1211
|
+
</div>
|
|
1212
|
+
</div>
|
|
1213
|
+
<div className="rounded-lg border bg-amber-50/50 p-3">
|
|
1214
|
+
<div className="flex items-center justify-between text-sm">
|
|
1215
|
+
<span className="text-muted-foreground">
|
|
1216
|
+
{t('quickRadar.timesheetPendencies')}
|
|
1217
|
+
</span>
|
|
1218
|
+
<span className="font-semibold text-amber-700">
|
|
1219
|
+
{projectStats?.quickRadar?.pendingTimesheets ??
|
|
1220
|
+
project.timesheetSummary.pendingTimesheets}
|
|
1221
|
+
</span>
|
|
1222
|
+
</div>
|
|
1223
|
+
</div>
|
|
1224
|
+
<div className="rounded-lg border bg-sky-50/50 p-3">
|
|
1225
|
+
<div className="flex items-center justify-between text-sm">
|
|
1226
|
+
<span className="text-muted-foreground">
|
|
1227
|
+
{t('quickRadar.plannedWeeklyHours')}
|
|
1228
|
+
</span>
|
|
1229
|
+
<span className="font-semibold text-sky-700">
|
|
1230
|
+
{formatHours(
|
|
1231
|
+
projectStats?.quickRadar?.totalWeeklyHours ??
|
|
1232
|
+
project.operationalIndicators.totalWeeklyHours
|
|
1233
|
+
)}
|
|
1234
|
+
</span>
|
|
1235
|
+
</div>
|
|
1236
|
+
</div>
|
|
1146
1237
|
</div>
|
|
1147
|
-
</
|
|
1238
|
+
</SectionCard>
|
|
1148
1239
|
</div>
|
|
1149
|
-
|
|
1150
|
-
|
|
1240
|
+
</>
|
|
1241
|
+
) : null}
|
|
1151
1242
|
|
|
1152
1243
|
<SectionCard
|
|
1153
1244
|
title={t('sections.taskBoard')}
|
|
1154
1245
|
description={t('sections.taskBoardDescription')}
|
|
1155
1246
|
className="rounded-xl border bg-card p-4 shadow-sm"
|
|
1156
1247
|
actions={
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1248
|
+
!isLimitedView ? (
|
|
1249
|
+
<Button
|
|
1250
|
+
size="sm"
|
|
1251
|
+
variant="outline"
|
|
1252
|
+
onClick={() => openCreateTaskForm()}
|
|
1253
|
+
>
|
|
1254
|
+
<Plus className="size-4" />
|
|
1255
|
+
{t('taskForm.titleNew')}
|
|
1256
|
+
</Button>
|
|
1257
|
+
) : undefined
|
|
1165
1258
|
}
|
|
1166
1259
|
>
|
|
1167
1260
|
<DndContext
|
|
@@ -1193,34 +1286,65 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1193
1286
|
|
|
1194
1287
|
<div className="space-y-2">
|
|
1195
1288
|
{taskColumns[column.id].map((task) => (
|
|
1196
|
-
<DraggableTaskCard
|
|
1289
|
+
<DraggableTaskCard
|
|
1290
|
+
key={task.id}
|
|
1291
|
+
task={task}
|
|
1292
|
+
disabled={false}
|
|
1293
|
+
>
|
|
1197
1294
|
{(isDragging) => (
|
|
1198
|
-
<
|
|
1199
|
-
|
|
1295
|
+
<div
|
|
1296
|
+
role="button"
|
|
1297
|
+
tabIndex={0}
|
|
1200
1298
|
className={[
|
|
1201
1299
|
'w-full cursor-pointer rounded-lg border bg-card p-3 text-left shadow-xs transition',
|
|
1202
1300
|
isDragging
|
|
1203
1301
|
? 'border-primary/50 opacity-75'
|
|
1204
1302
|
: 'hover:border-primary/40 hover:shadow-sm',
|
|
1205
1303
|
].join(' ')}
|
|
1206
|
-
onClick={() =>
|
|
1304
|
+
onClick={() => setSelectedTask(task)}
|
|
1305
|
+
onKeyDown={(event) => {
|
|
1306
|
+
if (
|
|
1307
|
+
event.key === 'Enter' ||
|
|
1308
|
+
event.key === ' '
|
|
1309
|
+
) {
|
|
1310
|
+
event.preventDefault();
|
|
1311
|
+
setSelectedTask(task);
|
|
1312
|
+
}
|
|
1313
|
+
}}
|
|
1207
1314
|
>
|
|
1208
1315
|
<div className="mb-2 flex items-start justify-between gap-2">
|
|
1209
1316
|
<p className="text-sm font-medium leading-snug">
|
|
1210
1317
|
{task.name}
|
|
1211
1318
|
</p>
|
|
1212
|
-
<
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1319
|
+
<div className="flex items-start gap-2">
|
|
1320
|
+
<span
|
|
1321
|
+
className={[
|
|
1322
|
+
'shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide',
|
|
1323
|
+
task.priority === 'high'
|
|
1324
|
+
? 'bg-rose-100 text-rose-700'
|
|
1325
|
+
: task.priority === 'medium'
|
|
1326
|
+
? 'bg-amber-100 text-amber-700'
|
|
1327
|
+
: 'bg-emerald-100 text-emerald-700',
|
|
1328
|
+
].join(' ')}
|
|
1329
|
+
>
|
|
1330
|
+
{getTaskPriorityLabel(task.priority)}
|
|
1331
|
+
</span>
|
|
1332
|
+
<Button
|
|
1333
|
+
type="button"
|
|
1334
|
+
variant="ghost"
|
|
1335
|
+
size="icon"
|
|
1336
|
+
className="size-7 shrink-0 rounded-full"
|
|
1337
|
+
onPointerDown={(event) =>
|
|
1338
|
+
event.stopPropagation()
|
|
1339
|
+
}
|
|
1340
|
+
onClick={(event) => {
|
|
1341
|
+
event.stopPropagation();
|
|
1342
|
+
openEditTaskForm(task);
|
|
1343
|
+
}}
|
|
1344
|
+
>
|
|
1345
|
+
<Pencil className="size-3.5" />
|
|
1346
|
+
</Button>
|
|
1347
|
+
</div>
|
|
1224
1348
|
</div>
|
|
1225
1349
|
|
|
1226
1350
|
{task.tags ? (
|
|
@@ -1283,7 +1407,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1283
1407
|
</span>
|
|
1284
1408
|
</div>
|
|
1285
1409
|
) : null}
|
|
1286
|
-
</
|
|
1410
|
+
</div>
|
|
1287
1411
|
)}
|
|
1288
1412
|
</DraggableTaskCard>
|
|
1289
1413
|
))}
|
|
@@ -1296,11 +1420,106 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1296
1420
|
</DndContext>
|
|
1297
1421
|
</SectionCard>
|
|
1298
1422
|
|
|
1423
|
+
<SectionCard
|
|
1424
|
+
title={t('sections.archivedTasks')}
|
|
1425
|
+
description={t('sections.archivedTasksDescription')}
|
|
1426
|
+
className="rounded-xl border bg-card p-4 shadow-sm"
|
|
1427
|
+
>
|
|
1428
|
+
{archivedTasks.length > 0 ? (
|
|
1429
|
+
<div className="overflow-x-auto rounded-lg border bg-muted/10">
|
|
1430
|
+
<Table>
|
|
1431
|
+
<TableHeader>
|
|
1432
|
+
<TableRow>
|
|
1433
|
+
<TableHead>{commonT('labels.task')}</TableHead>
|
|
1434
|
+
<TableHead>{commonT('labels.status')}</TableHead>
|
|
1435
|
+
<TableHead>{t('taskForm.deadlineLabel')}</TableHead>
|
|
1436
|
+
<TableHead className="text-right">
|
|
1437
|
+
{commonT('labels.actions')}
|
|
1438
|
+
</TableHead>
|
|
1439
|
+
</TableRow>
|
|
1440
|
+
</TableHeader>
|
|
1441
|
+
<TableBody>
|
|
1442
|
+
{archivedTasks.map((task) => (
|
|
1443
|
+
<TableRow
|
|
1444
|
+
key={task.id}
|
|
1445
|
+
className="cursor-pointer hover:bg-muted/30"
|
|
1446
|
+
onClick={() => setSelectedTask(task)}
|
|
1447
|
+
>
|
|
1448
|
+
<TableCell>
|
|
1449
|
+
<div className="min-w-0">
|
|
1450
|
+
<div className="truncate font-medium">{task.name}</div>
|
|
1451
|
+
{task.description ? (
|
|
1452
|
+
<div className="truncate text-xs text-muted-foreground">
|
|
1453
|
+
{task.description}
|
|
1454
|
+
</div>
|
|
1455
|
+
) : null}
|
|
1456
|
+
</div>
|
|
1457
|
+
</TableCell>
|
|
1458
|
+
<TableCell>
|
|
1459
|
+
<StatusBadge
|
|
1460
|
+
label={
|
|
1461
|
+
KANBAN_COLUMNS.find(
|
|
1462
|
+
(column) => column.id === task.status
|
|
1463
|
+
)?.label ?? formatEnumLabel(task.status)
|
|
1464
|
+
}
|
|
1465
|
+
className={getStatusBadgeClass(task.status)}
|
|
1466
|
+
/>
|
|
1467
|
+
</TableCell>
|
|
1468
|
+
<TableCell>
|
|
1469
|
+
{formatDate(
|
|
1470
|
+
task.dueDate,
|
|
1471
|
+
getSettingValue,
|
|
1472
|
+
currentLocaleCode
|
|
1473
|
+
)}
|
|
1474
|
+
</TableCell>
|
|
1475
|
+
<TableCell>
|
|
1476
|
+
<div className="flex justify-end gap-2">
|
|
1477
|
+
<Button
|
|
1478
|
+
variant="outline"
|
|
1479
|
+
size="sm"
|
|
1480
|
+
className="gap-2"
|
|
1481
|
+
onClick={(event) => {
|
|
1482
|
+
event.stopPropagation();
|
|
1483
|
+
void handleRestoreTask(task.id);
|
|
1484
|
+
}}
|
|
1485
|
+
>
|
|
1486
|
+
<ArchiveRestore className="size-4" />
|
|
1487
|
+
{commonT('actions.unarchive')}
|
|
1488
|
+
</Button>
|
|
1489
|
+
<Button
|
|
1490
|
+
variant="destructive"
|
|
1491
|
+
size="sm"
|
|
1492
|
+
className="gap-2"
|
|
1493
|
+
onClick={(event) => {
|
|
1494
|
+
event.stopPropagation();
|
|
1495
|
+
setDeletePromptTask(task);
|
|
1496
|
+
}}
|
|
1497
|
+
>
|
|
1498
|
+
<Trash2 className="size-4" />
|
|
1499
|
+
{commonT('actions.delete')}
|
|
1500
|
+
</Button>
|
|
1501
|
+
</div>
|
|
1502
|
+
</TableCell>
|
|
1503
|
+
</TableRow>
|
|
1504
|
+
))}
|
|
1505
|
+
</TableBody>
|
|
1506
|
+
</Table>
|
|
1507
|
+
</div>
|
|
1508
|
+
) : (
|
|
1509
|
+
<p className="text-sm text-muted-foreground">
|
|
1510
|
+
{t('emptyArchivedDescription')}
|
|
1511
|
+
</p>
|
|
1512
|
+
)}
|
|
1513
|
+
</SectionCard>
|
|
1514
|
+
|
|
1299
1515
|
<div className="grid gap-4 xl:grid-cols-12">
|
|
1300
1516
|
<SectionCard
|
|
1301
1517
|
title={t('sections.team')}
|
|
1302
1518
|
description={t('sections.teamDescription')}
|
|
1303
|
-
className=
|
|
1519
|
+
className={[
|
|
1520
|
+
'rounded-xl border bg-card p-4 shadow-sm',
|
|
1521
|
+
isLimitedView ? 'xl:col-span-12' : 'xl:col-span-8',
|
|
1522
|
+
].join(' ')}
|
|
1304
1523
|
>
|
|
1305
1524
|
{project.assignments.length > 0 ? (
|
|
1306
1525
|
<div className="overflow-x-auto rounded-lg border bg-muted/10">
|
|
@@ -1376,190 +1595,118 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1376
1595
|
)}
|
|
1377
1596
|
</SectionCard>
|
|
1378
1597
|
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
<
|
|
1386
|
-
<
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
<
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
<
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1598
|
+
{!isLimitedView ? (
|
|
1599
|
+
<SectionCard
|
|
1600
|
+
title={t('sections.indicators')}
|
|
1601
|
+
description={t('sections.indicatorsDescription')}
|
|
1602
|
+
className="rounded-xl border bg-card p-4 shadow-sm xl:col-span-4"
|
|
1603
|
+
>
|
|
1604
|
+
<dl className="grid gap-3 text-sm sm:grid-cols-2 xl:grid-cols-1">
|
|
1605
|
+
<div>
|
|
1606
|
+
<dt className="text-muted-foreground">
|
|
1607
|
+
{t('indicators.activeAssignments')}
|
|
1608
|
+
</dt>
|
|
1609
|
+
<dd className="font-medium">
|
|
1610
|
+
{project.operationalIndicators.activeAssignments}
|
|
1611
|
+
</dd>
|
|
1612
|
+
</div>
|
|
1613
|
+
<div>
|
|
1614
|
+
<dt className="text-muted-foreground">
|
|
1615
|
+
{t('indicators.completedAssignments')}
|
|
1616
|
+
</dt>
|
|
1617
|
+
<dd className="font-medium">
|
|
1618
|
+
{project.operationalIndicators.completedAssignments}
|
|
1619
|
+
</dd>
|
|
1620
|
+
</div>
|
|
1621
|
+
<div>
|
|
1622
|
+
<dt className="text-muted-foreground">
|
|
1623
|
+
{t('indicators.averageAllocation')}
|
|
1624
|
+
</dt>
|
|
1625
|
+
<dd className="font-medium">
|
|
1626
|
+
{formatPercent(
|
|
1627
|
+
project.operationalIndicators.averageAllocation
|
|
1628
|
+
)}
|
|
1629
|
+
</dd>
|
|
1630
|
+
</div>
|
|
1631
|
+
<div>
|
|
1632
|
+
<dt className="text-muted-foreground">
|
|
1633
|
+
{t('indicators.totalWeeklyHours')}
|
|
1634
|
+
</dt>
|
|
1635
|
+
<dd className="font-medium">
|
|
1636
|
+
{formatHours(project.operationalIndicators.totalWeeklyHours)}
|
|
1637
|
+
</dd>
|
|
1638
|
+
</div>
|
|
1639
|
+
<div>
|
|
1640
|
+
<dt className="text-muted-foreground">
|
|
1641
|
+
{t('cards.timesheets')}
|
|
1642
|
+
</dt>
|
|
1643
|
+
<dd className="font-medium">
|
|
1644
|
+
{project.timesheetSummary.totalTimesheets}
|
|
1645
|
+
</dd>
|
|
1646
|
+
</div>
|
|
1647
|
+
<div>
|
|
1648
|
+
<dt className="text-muted-foreground">
|
|
1649
|
+
{commonT('labels.pending')}
|
|
1650
|
+
</dt>
|
|
1651
|
+
<dd className="font-medium">
|
|
1652
|
+
{project.timesheetSummary.pendingTimesheets}
|
|
1653
|
+
</dd>
|
|
1654
|
+
</div>
|
|
1655
|
+
<div>
|
|
1656
|
+
<dt className="text-muted-foreground">
|
|
1657
|
+
{t('cards.loggedHours')}
|
|
1658
|
+
</dt>
|
|
1659
|
+
<dd className="font-medium">
|
|
1660
|
+
{formatHours(project.timesheetSummary.totalHours)}
|
|
1661
|
+
</dd>
|
|
1662
|
+
</div>
|
|
1663
|
+
</dl>
|
|
1664
|
+
</SectionCard>
|
|
1665
|
+
) : null}
|
|
1441
1666
|
</div>
|
|
1442
1667
|
|
|
1443
|
-
<
|
|
1444
|
-
|
|
1668
|
+
<TaskDetailSheet
|
|
1669
|
+
task={selectedTask}
|
|
1670
|
+
open={selectedTask !== null}
|
|
1445
1671
|
onOpenChange={(open) => {
|
|
1446
1672
|
if (!open) {
|
|
1447
|
-
|
|
1673
|
+
setSelectedTask(null);
|
|
1448
1674
|
}
|
|
1449
1675
|
}}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
</SheetDescription>
|
|
1459
|
-
</SheetHeader>
|
|
1460
|
-
|
|
1461
|
-
<div className="mt-4 space-y-4">
|
|
1462
|
-
{selectedTask.description ? (
|
|
1463
|
-
<div className="rounded-lg border bg-muted/10 p-3 text-sm text-muted-foreground">
|
|
1464
|
-
{selectedTask.description}
|
|
1465
|
-
</div>
|
|
1466
|
-
) : null}
|
|
1467
|
-
|
|
1468
|
-
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
1469
|
-
<div className="rounded-lg border p-3">
|
|
1470
|
-
<div className="mb-1 text-xs text-muted-foreground">
|
|
1471
|
-
Prioridade
|
|
1472
|
-
</div>
|
|
1473
|
-
<div className="font-medium">
|
|
1474
|
-
{getTaskPriorityLabel(selectedTask.priority)}
|
|
1475
|
-
</div>
|
|
1476
|
-
</div>
|
|
1477
|
-
<div className="rounded-lg border p-3">
|
|
1478
|
-
<div className="mb-1 text-xs text-muted-foreground">
|
|
1479
|
-
Estimativa
|
|
1480
|
-
</div>
|
|
1481
|
-
<div className="font-medium">
|
|
1482
|
-
{selectedTask.estimateHours != null
|
|
1483
|
-
? `${selectedTask.estimateHours} horas`
|
|
1484
|
-
: '—'}
|
|
1485
|
-
</div>
|
|
1486
|
-
</div>
|
|
1487
|
-
<div className="rounded-lg border p-3">
|
|
1488
|
-
<div className="mb-1 text-xs text-muted-foreground">
|
|
1489
|
-
Prazo
|
|
1490
|
-
</div>
|
|
1491
|
-
<div className="font-medium">
|
|
1492
|
-
{formatDate(
|
|
1493
|
-
selectedTask.dueDate,
|
|
1494
|
-
getSettingValue,
|
|
1495
|
-
currentLocaleCode
|
|
1496
|
-
)}
|
|
1497
|
-
</div>
|
|
1498
|
-
</div>
|
|
1499
|
-
<div className="rounded-lg border p-3">
|
|
1500
|
-
<div className="mb-1 text-xs text-muted-foreground">
|
|
1501
|
-
Status
|
|
1502
|
-
</div>
|
|
1503
|
-
<div className="font-medium">
|
|
1504
|
-
{
|
|
1505
|
-
KANBAN_COLUMNS.find(
|
|
1506
|
-
(column) => column.id === selectedTask.status
|
|
1507
|
-
)?.label
|
|
1508
|
-
}
|
|
1509
|
-
</div>
|
|
1510
|
-
</div>
|
|
1511
|
-
</div>
|
|
1512
|
-
|
|
1513
|
-
<div className="rounded-lg border p-3">
|
|
1514
|
-
<div className="mb-2 text-xs text-muted-foreground">
|
|
1515
|
-
Responsavel
|
|
1516
|
-
</div>
|
|
1517
|
-
<div className="flex items-center gap-2 text-sm">
|
|
1518
|
-
<Avatar className="h-8 w-8 border border-border/60 bg-muted">
|
|
1519
|
-
<AvatarImage
|
|
1520
|
-
src={
|
|
1521
|
-
getUserPhotoUrl(selectedTask.assigneeUserPhotoId) ||
|
|
1522
|
-
getPersonAvatarUrl(
|
|
1523
|
-
selectedTask.assigneePersonAvatarId
|
|
1524
|
-
)
|
|
1525
|
-
}
|
|
1526
|
-
alt={selectedTask.assigneeName || 'Responsavel'}
|
|
1527
|
-
/>
|
|
1528
|
-
<AvatarFallback className="bg-muted text-xs font-semibold text-foreground">
|
|
1529
|
-
{getInitials(selectedTask.assigneeName || 'N/A')}
|
|
1530
|
-
</AvatarFallback>
|
|
1531
|
-
</Avatar>
|
|
1532
|
-
<span>
|
|
1533
|
-
{selectedTask.assigneeName ||
|
|
1534
|
-
commonT('labels.notAssigned')}
|
|
1535
|
-
</span>
|
|
1536
|
-
</div>
|
|
1537
|
-
</div>
|
|
1538
|
-
|
|
1539
|
-
{selectedTask.tags ? (
|
|
1540
|
-
<div className="rounded-lg border p-3">
|
|
1541
|
-
<div className="mb-2 text-xs text-muted-foreground">
|
|
1542
|
-
Etiquetas
|
|
1543
|
-
</div>
|
|
1544
|
-
<div className="flex flex-wrap gap-1.5">
|
|
1545
|
-
{selectedTask.tags.split(',').map((tag) => (
|
|
1546
|
-
<span
|
|
1547
|
-
key={`${selectedTask.id}-sheet-${tag.trim()}`}
|
|
1548
|
-
className="rounded-md bg-muted px-2 py-1 text-xs"
|
|
1549
|
-
>
|
|
1550
|
-
{tag.trim()}
|
|
1551
|
-
</span>
|
|
1552
|
-
))}
|
|
1553
|
-
</div>
|
|
1554
|
-
</div>
|
|
1555
|
-
) : null}
|
|
1556
|
-
|
|
1557
|
-
<div className="grid grid-cols-2 gap-3 border-t pt-5">
|
|
1676
|
+
statusLabel={(status) =>
|
|
1677
|
+
KANBAN_COLUMNS.find((column) => column.id === status)?.label ?? status
|
|
1678
|
+
}
|
|
1679
|
+
footer={
|
|
1680
|
+
selectedTask && !isLimitedView ? (
|
|
1681
|
+
<div className="grid grid-cols-2 gap-3">
|
|
1682
|
+
{archivedTasks.some((task) => task.id === selectedTask.id) ? (
|
|
1683
|
+
<>
|
|
1558
1684
|
<Button
|
|
1559
1685
|
variant="outline"
|
|
1560
1686
|
size="sm"
|
|
1561
1687
|
className="h-10 gap-2"
|
|
1562
|
-
onClick={() =>
|
|
1688
|
+
onClick={() => void handleRestoreTask(selectedTask.id)}
|
|
1689
|
+
>
|
|
1690
|
+
<ArchiveRestore className="size-3.5" />
|
|
1691
|
+
{commonT('actions.unarchive')}
|
|
1692
|
+
</Button>
|
|
1693
|
+
<Button
|
|
1694
|
+
variant="destructive"
|
|
1695
|
+
size="sm"
|
|
1696
|
+
className="h-10 gap-2"
|
|
1697
|
+
onClick={() => setDeletePromptTask(selectedTask)}
|
|
1698
|
+
>
|
|
1699
|
+
<Trash2 className="size-3.5" />
|
|
1700
|
+
{commonT('actions.delete')}
|
|
1701
|
+
</Button>
|
|
1702
|
+
</>
|
|
1703
|
+
) : (
|
|
1704
|
+
<>
|
|
1705
|
+
<Button
|
|
1706
|
+
variant="outline"
|
|
1707
|
+
size="sm"
|
|
1708
|
+
className="h-10 gap-2"
|
|
1709
|
+
onClick={() => openEditTaskForm(selectedTask as BoardTask)}
|
|
1563
1710
|
>
|
|
1564
1711
|
<Pencil className="size-3.5" />
|
|
1565
1712
|
{commonT('actions.edit')}
|
|
@@ -1567,284 +1714,298 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1567
1714
|
<Button
|
|
1568
1715
|
variant="outline"
|
|
1569
1716
|
size="sm"
|
|
1570
|
-
className="h-10 gap-2
|
|
1571
|
-
onClick={() =>
|
|
1717
|
+
className="h-10 gap-2"
|
|
1718
|
+
onClick={() => void handleArchiveTask(selectedTask.id)}
|
|
1572
1719
|
>
|
|
1573
|
-
<
|
|
1574
|
-
{commonT('actions.
|
|
1720
|
+
<Archive className="size-3.5" />
|
|
1721
|
+
{commonT('actions.archive')}
|
|
1575
1722
|
</Button>
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
</>
|
|
1579
|
-
) : null}
|
|
1580
|
-
</SheetContent>
|
|
1581
|
-
</Sheet>
|
|
1582
|
-
|
|
1583
|
-
<Sheet
|
|
1584
|
-
open={isEditSheetOpen}
|
|
1585
|
-
onOpenChange={(open) => {
|
|
1586
|
-
if (!open) {
|
|
1587
|
-
closeEditSheet();
|
|
1588
|
-
}
|
|
1589
|
-
}}
|
|
1590
|
-
>
|
|
1591
|
-
<SheetContent className="w-full overflow-x-hidden overflow-y-auto sm:max-w-[min(92vw,64rem)]">
|
|
1592
|
-
<SheetHeader>
|
|
1593
|
-
<SheetTitle>{formT('editTitle')}</SheetTitle>
|
|
1594
|
-
<SheetDescription>{formT('description')}</SheetDescription>
|
|
1595
|
-
</SheetHeader>
|
|
1596
|
-
|
|
1597
|
-
<ProjectFormScreen
|
|
1598
|
-
projectId={projectId}
|
|
1599
|
-
onCancel={closeEditSheet}
|
|
1600
|
-
onSaved={async () => {
|
|
1601
|
-
closeEditSheet();
|
|
1602
|
-
await refetch();
|
|
1603
|
-
}}
|
|
1604
|
-
/>
|
|
1605
|
-
</SheetContent>
|
|
1606
|
-
</Sheet>
|
|
1607
|
-
|
|
1608
|
-
{/* Task form dialog */}
|
|
1609
|
-
<Dialog
|
|
1610
|
-
open={taskFormOpen}
|
|
1611
|
-
onOpenChange={(open) => {
|
|
1612
|
-
if (!open) {
|
|
1613
|
-
setTaskFormOpen(false);
|
|
1614
|
-
setEditingTaskId(null);
|
|
1615
|
-
setTaskFormData(EMPTY_TASK_FORM);
|
|
1616
|
-
}
|
|
1617
|
-
}}
|
|
1618
|
-
>
|
|
1619
|
-
<DialogContent className="sm:max-w-lg">
|
|
1620
|
-
<DialogHeader>
|
|
1621
|
-
<DialogTitle>
|
|
1622
|
-
{editingTaskId ? t('taskForm.titleEdit') : t('taskForm.titleNew')}
|
|
1623
|
-
</DialogTitle>
|
|
1624
|
-
</DialogHeader>
|
|
1625
|
-
|
|
1626
|
-
<div className="space-y-4">
|
|
1627
|
-
<div className="space-y-1.5">
|
|
1628
|
-
<Label htmlFor="task-name">{t('taskForm.nameLabel')} *</Label>
|
|
1629
|
-
<Input
|
|
1630
|
-
id="task-name"
|
|
1631
|
-
placeholder={t('taskForm.namePlaceholder')}
|
|
1632
|
-
value={taskFormData.name}
|
|
1633
|
-
onChange={(e) =>
|
|
1634
|
-
setTaskFormData((prev) => ({ ...prev, name: e.target.value }))
|
|
1635
|
-
}
|
|
1636
|
-
/>
|
|
1723
|
+
</>
|
|
1724
|
+
)}
|
|
1637
1725
|
</div>
|
|
1726
|
+
) : null
|
|
1727
|
+
}
|
|
1728
|
+
/>
|
|
1638
1729
|
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1730
|
+
{!isLimitedView ? (
|
|
1731
|
+
<Sheet
|
|
1732
|
+
open={isEditSheetOpen}
|
|
1733
|
+
onOpenChange={(open) => {
|
|
1734
|
+
if (!open) {
|
|
1735
|
+
closeEditSheet();
|
|
1736
|
+
}
|
|
1737
|
+
}}
|
|
1738
|
+
>
|
|
1739
|
+
<SheetContent className="w-full overflow-x-hidden overflow-y-auto sm:max-w-[min(92vw,64rem)]">
|
|
1740
|
+
<SheetHeader>
|
|
1741
|
+
<SheetTitle>{formT('editTitle')}</SheetTitle>
|
|
1742
|
+
<SheetDescription>{formT('description')}</SheetDescription>
|
|
1743
|
+
</SheetHeader>
|
|
1744
|
+
|
|
1745
|
+
<ProjectFormScreen
|
|
1746
|
+
projectId={projectId}
|
|
1747
|
+
onCancel={closeEditSheet}
|
|
1748
|
+
onSaved={async () => {
|
|
1749
|
+
closeEditSheet();
|
|
1750
|
+
await refetch();
|
|
1751
|
+
}}
|
|
1752
|
+
/>
|
|
1753
|
+
</SheetContent>
|
|
1754
|
+
</Sheet>
|
|
1755
|
+
) : null}
|
|
1756
|
+
|
|
1757
|
+
{!isLimitedView ? (
|
|
1758
|
+
<Dialog
|
|
1759
|
+
open={taskFormOpen}
|
|
1760
|
+
onOpenChange={(open) => {
|
|
1761
|
+
if (!open) {
|
|
1762
|
+
setTaskFormOpen(false);
|
|
1763
|
+
setEditingTaskId(null);
|
|
1764
|
+
setTaskFormData(EMPTY_TASK_FORM);
|
|
1765
|
+
}
|
|
1766
|
+
}}
|
|
1767
|
+
>
|
|
1768
|
+
<DialogContent className="sm:max-w-lg">
|
|
1769
|
+
<DialogHeader>
|
|
1770
|
+
<DialogTitle>
|
|
1771
|
+
{editingTaskId
|
|
1772
|
+
? t('taskForm.titleEdit')
|
|
1773
|
+
: t('taskForm.titleNew')}
|
|
1774
|
+
</DialogTitle>
|
|
1775
|
+
</DialogHeader>
|
|
1776
|
+
|
|
1777
|
+
<div className="space-y-4">
|
|
1778
|
+
<div className="space-y-1.5">
|
|
1779
|
+
<Label htmlFor="task-name">{t('taskForm.nameLabel')} *</Label>
|
|
1780
|
+
<Input
|
|
1781
|
+
id="task-name"
|
|
1782
|
+
placeholder={t('taskForm.namePlaceholder')}
|
|
1783
|
+
value={taskFormData.name}
|
|
1784
|
+
onChange={(e) =>
|
|
1785
|
+
setTaskFormData((prev) => ({
|
|
1786
|
+
...prev,
|
|
1787
|
+
name: e.target.value,
|
|
1788
|
+
}))
|
|
1789
|
+
}
|
|
1790
|
+
/>
|
|
1791
|
+
</div>
|
|
1656
1792
|
|
|
1657
|
-
<div className="grid grid-cols-2 gap-3">
|
|
1658
1793
|
<div className="space-y-1.5">
|
|
1659
|
-
<Label>
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1794
|
+
<Label htmlFor="task-description">
|
|
1795
|
+
{t('taskForm.descriptionLabel')}
|
|
1796
|
+
</Label>
|
|
1797
|
+
<Textarea
|
|
1798
|
+
id="task-description"
|
|
1799
|
+
placeholder={t('taskForm.descriptionPlaceholder')}
|
|
1800
|
+
rows={3}
|
|
1801
|
+
value={taskFormData.description}
|
|
1802
|
+
onChange={(e) =>
|
|
1663
1803
|
setTaskFormData((prev) => ({
|
|
1664
1804
|
...prev,
|
|
1665
|
-
|
|
1805
|
+
description: e.target.value,
|
|
1666
1806
|
}))
|
|
1667
1807
|
}
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1808
|
+
/>
|
|
1809
|
+
</div>
|
|
1810
|
+
|
|
1811
|
+
<div className="grid grid-cols-2 gap-3">
|
|
1812
|
+
<div className="space-y-1.5">
|
|
1813
|
+
<Label>{t('taskForm.priorityLabel')}</Label>
|
|
1814
|
+
<Select
|
|
1815
|
+
value={taskFormData.priority}
|
|
1816
|
+
onValueChange={(v) =>
|
|
1817
|
+
setTaskFormData((prev) => ({
|
|
1818
|
+
...prev,
|
|
1819
|
+
priority: v as TaskFormState['priority'],
|
|
1820
|
+
}))
|
|
1821
|
+
}
|
|
1822
|
+
>
|
|
1823
|
+
<SelectTrigger className="w-full">
|
|
1824
|
+
<SelectValue />
|
|
1825
|
+
</SelectTrigger>
|
|
1826
|
+
<SelectContent>
|
|
1827
|
+
<SelectItem value="low">
|
|
1828
|
+
{getTaskPriorityLabel('low')}
|
|
1829
|
+
</SelectItem>
|
|
1830
|
+
<SelectItem value="medium">
|
|
1831
|
+
{getTaskPriorityLabel('medium')}
|
|
1832
|
+
</SelectItem>
|
|
1833
|
+
<SelectItem value="high">
|
|
1834
|
+
{getTaskPriorityLabel('high')}
|
|
1835
|
+
</SelectItem>
|
|
1836
|
+
</SelectContent>
|
|
1837
|
+
</Select>
|
|
1838
|
+
</div>
|
|
1839
|
+
|
|
1840
|
+
<div className="space-y-1.5">
|
|
1841
|
+
<Label>{t('taskForm.columnLabel')}</Label>
|
|
1842
|
+
<Select
|
|
1843
|
+
value={taskFormData.status}
|
|
1844
|
+
onValueChange={(v) =>
|
|
1845
|
+
setTaskFormData((prev) => ({
|
|
1846
|
+
...prev,
|
|
1847
|
+
status: v as BoardColumnId,
|
|
1848
|
+
}))
|
|
1849
|
+
}
|
|
1850
|
+
>
|
|
1851
|
+
<SelectTrigger className="w-full">
|
|
1852
|
+
<SelectValue />
|
|
1853
|
+
</SelectTrigger>
|
|
1854
|
+
<SelectContent>
|
|
1855
|
+
{KANBAN_COLUMNS.map((col) => (
|
|
1856
|
+
<SelectItem key={col.id} value={col.id}>
|
|
1857
|
+
{col.label}
|
|
1858
|
+
</SelectItem>
|
|
1859
|
+
))}
|
|
1860
|
+
</SelectContent>
|
|
1861
|
+
</Select>
|
|
1862
|
+
</div>
|
|
1684
1863
|
</div>
|
|
1685
1864
|
|
|
1686
1865
|
<div className="space-y-1.5">
|
|
1687
|
-
<Label>
|
|
1866
|
+
<Label>Responsável</Label>
|
|
1688
1867
|
<Select
|
|
1689
|
-
value={taskFormData.
|
|
1690
|
-
onValueChange={(
|
|
1868
|
+
value={taskFormData.assigneeCollaboratorId}
|
|
1869
|
+
onValueChange={(value) =>
|
|
1691
1870
|
setTaskFormData((prev) => ({
|
|
1692
1871
|
...prev,
|
|
1693
|
-
|
|
1872
|
+
assigneeCollaboratorId: value,
|
|
1694
1873
|
}))
|
|
1695
1874
|
}
|
|
1696
1875
|
>
|
|
1697
1876
|
<SelectTrigger className="w-full">
|
|
1698
|
-
<SelectValue />
|
|
1877
|
+
<SelectValue placeholder={commonT('labels.notAssigned')} />
|
|
1699
1878
|
</SelectTrigger>
|
|
1700
1879
|
<SelectContent>
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1880
|
+
<SelectItem value="none">
|
|
1881
|
+
{commonT('labels.notAssigned')}
|
|
1882
|
+
</SelectItem>
|
|
1883
|
+
{taskAssigneeOptions.map((option) => (
|
|
1884
|
+
<SelectItem key={option.id} value={option.id}>
|
|
1885
|
+
{option.label}
|
|
1704
1886
|
</SelectItem>
|
|
1705
1887
|
))}
|
|
1706
1888
|
</SelectContent>
|
|
1707
1889
|
</Select>
|
|
1708
1890
|
</div>
|
|
1709
|
-
</div>
|
|
1710
1891
|
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
</SelectItem>
|
|
1729
|
-
{taskAssigneeOptions.map((option) => (
|
|
1730
|
-
<SelectItem key={option.id} value={option.id}>
|
|
1731
|
-
{option.label}
|
|
1732
|
-
</SelectItem>
|
|
1733
|
-
))}
|
|
1734
|
-
</SelectContent>
|
|
1735
|
-
</Select>
|
|
1736
|
-
</div>
|
|
1892
|
+
<div className="grid grid-cols-2 gap-3">
|
|
1893
|
+
<div className="space-y-1.5">
|
|
1894
|
+
<Label htmlFor="task-due-date">
|
|
1895
|
+
{t('taskForm.deadlineLabel')}
|
|
1896
|
+
</Label>
|
|
1897
|
+
<Input
|
|
1898
|
+
id="task-due-date"
|
|
1899
|
+
type="date"
|
|
1900
|
+
value={taskFormData.dueDate}
|
|
1901
|
+
onChange={(e) =>
|
|
1902
|
+
setTaskFormData((prev) => ({
|
|
1903
|
+
...prev,
|
|
1904
|
+
dueDate: e.target.value,
|
|
1905
|
+
}))
|
|
1906
|
+
}
|
|
1907
|
+
/>
|
|
1908
|
+
</div>
|
|
1737
1909
|
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1910
|
+
<div className="space-y-1.5">
|
|
1911
|
+
<Label htmlFor="task-estimate">
|
|
1912
|
+
{t('taskForm.estimateLabel')}
|
|
1913
|
+
</Label>
|
|
1914
|
+
<Input
|
|
1915
|
+
id="task-estimate"
|
|
1916
|
+
type="number"
|
|
1917
|
+
min="0"
|
|
1918
|
+
step="0.5"
|
|
1919
|
+
placeholder="0"
|
|
1920
|
+
value={taskFormData.estimateHours}
|
|
1921
|
+
onChange={(e) =>
|
|
1922
|
+
setTaskFormData((prev) => ({
|
|
1923
|
+
...prev,
|
|
1924
|
+
estimateHours: e.target.value,
|
|
1925
|
+
}))
|
|
1926
|
+
}
|
|
1927
|
+
/>
|
|
1928
|
+
</div>
|
|
1754
1929
|
</div>
|
|
1755
1930
|
|
|
1756
1931
|
<div className="space-y-1.5">
|
|
1757
|
-
<Label htmlFor="task-
|
|
1758
|
-
{t('taskForm.estimateLabel')}
|
|
1759
|
-
</Label>
|
|
1932
|
+
<Label htmlFor="task-tags">{t('taskForm.tagsLabel')}</Label>
|
|
1760
1933
|
<Input
|
|
1761
|
-
id="task-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
step="0.5"
|
|
1765
|
-
placeholder="0"
|
|
1766
|
-
value={taskFormData.estimateHours}
|
|
1934
|
+
id="task-tags"
|
|
1935
|
+
placeholder={t('taskForm.tagsPlaceholder')}
|
|
1936
|
+
value={taskFormData.tags}
|
|
1767
1937
|
onChange={(e) =>
|
|
1768
1938
|
setTaskFormData((prev) => ({
|
|
1769
1939
|
...prev,
|
|
1770
|
-
|
|
1940
|
+
tags: e.target.value,
|
|
1771
1941
|
}))
|
|
1772
1942
|
}
|
|
1773
1943
|
/>
|
|
1774
1944
|
</div>
|
|
1775
1945
|
</div>
|
|
1776
1946
|
|
|
1777
|
-
<
|
|
1778
|
-
<
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
>
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
void handleDeleteTask(deleteConfirmId);
|
|
1840
|
-
}
|
|
1841
|
-
}}
|
|
1842
|
-
>
|
|
1843
|
-
Excluir
|
|
1844
|
-
</Button>
|
|
1845
|
-
</DialogFooter>
|
|
1846
|
-
</DialogContent>
|
|
1847
|
-
</Dialog>
|
|
1947
|
+
<DialogFooter className="mt-4">
|
|
1948
|
+
<Button
|
|
1949
|
+
variant="outline"
|
|
1950
|
+
onClick={() => {
|
|
1951
|
+
setTaskFormOpen(false);
|
|
1952
|
+
setEditingTaskId(null);
|
|
1953
|
+
setTaskFormData(EMPTY_TASK_FORM);
|
|
1954
|
+
}}
|
|
1955
|
+
disabled={taskFormLoading}
|
|
1956
|
+
>
|
|
1957
|
+
{commonT('actions.cancel')}
|
|
1958
|
+
</Button>
|
|
1959
|
+
<Button
|
|
1960
|
+
onClick={() => void handleTaskFormSubmit()}
|
|
1961
|
+
disabled={taskFormLoading || !taskFormData.name.trim()}
|
|
1962
|
+
>
|
|
1963
|
+
{taskFormLoading
|
|
1964
|
+
? t('taskForm.saving')
|
|
1965
|
+
: editingTaskId
|
|
1966
|
+
? commonT('actions.save')
|
|
1967
|
+
: commonT('actions.create')}
|
|
1968
|
+
</Button>
|
|
1969
|
+
</DialogFooter>
|
|
1970
|
+
</DialogContent>
|
|
1971
|
+
</Dialog>
|
|
1972
|
+
) : null}
|
|
1973
|
+
|
|
1974
|
+
{!isLimitedView ? (
|
|
1975
|
+
<>
|
|
1976
|
+
<Dialog
|
|
1977
|
+
open={deletePromptTask !== null}
|
|
1978
|
+
onOpenChange={(open) => {
|
|
1979
|
+
if (!open) setDeletePromptTask(null);
|
|
1980
|
+
}}
|
|
1981
|
+
>
|
|
1982
|
+
<DialogContent className="sm:max-w-sm">
|
|
1983
|
+
<DialogHeader>
|
|
1984
|
+
<DialogTitle>{t('dialogs.deleteTitle')}</DialogTitle>
|
|
1985
|
+
</DialogHeader>
|
|
1986
|
+
<p className="text-sm text-muted-foreground">
|
|
1987
|
+
{t('dialogs.deleteDescription')}
|
|
1988
|
+
</p>
|
|
1989
|
+
<DialogFooter className="mt-4">
|
|
1990
|
+
<Button
|
|
1991
|
+
variant="outline"
|
|
1992
|
+
onClick={() => setDeletePromptTask(null)}
|
|
1993
|
+
>
|
|
1994
|
+
{commonT('actions.cancel')}
|
|
1995
|
+
</Button>
|
|
1996
|
+
{deletePromptTask ? (
|
|
1997
|
+
<Button
|
|
1998
|
+
variant="destructive"
|
|
1999
|
+
onClick={() => void handleDeleteTask(deletePromptTask.id)}
|
|
2000
|
+
>
|
|
2001
|
+
{commonT('actions.delete')}
|
|
2002
|
+
</Button>
|
|
2003
|
+
) : null}
|
|
2004
|
+
</DialogFooter>
|
|
2005
|
+
</DialogContent>
|
|
2006
|
+
</Dialog>
|
|
2007
|
+
</>
|
|
2008
|
+
) : null}
|
|
1848
2009
|
</Page>
|
|
1849
2010
|
);
|
|
1850
2011
|
}
|