@hed-hog/operations 0.0.306 → 0.0.310
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-approvals.controller.d.ts +114 -1
- package/dist/controllers/operations-approvals.controller.d.ts.map +1 -1
- package/dist/controllers/operations-approvals.controller.js +16 -3
- package/dist/controllers/operations-approvals.controller.js.map +1 -1
- package/dist/controllers/operations-collaborators.controller.d.ts +16 -1
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
- package/dist/controllers/operations-collaborators.controller.js +16 -3
- package/dist/controllers/operations-collaborators.controller.js.map +1 -1
- package/dist/controllers/operations-contracts.controller.d.ts +14 -453
- package/dist/controllers/operations-contracts.controller.d.ts.map +1 -1
- package/dist/controllers/operations-contracts.controller.js +11 -112
- package/dist/controllers/operations-contracts.controller.js.map +1 -1
- package/dist/controllers/operations-org-structure.controller.d.ts +65 -2
- package/dist/controllers/operations-org-structure.controller.d.ts.map +1 -1
- package/dist/controllers/operations-org-structure.controller.js +18 -5
- package/dist/controllers/operations-org-structure.controller.js.map +1 -1
- package/dist/controllers/operations-projects.controller.d.ts +28 -4
- package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
- package/dist/controllers/operations-projects.controller.js +17 -5
- package/dist/controllers/operations-projects.controller.js.map +1 -1
- package/dist/controllers/operations-timesheets.controller.d.ts +31 -4
- package/dist/controllers/operations-timesheets.controller.d.ts.map +1 -1
- package/dist/controllers/operations-timesheets.controller.js +16 -11
- package/dist/controllers/operations-timesheets.controller.js.map +1 -1
- package/dist/dto/list-approvals.dto.d.ts +6 -0
- package/dist/dto/list-approvals.dto.d.ts.map +1 -0
- package/dist/dto/list-approvals.dto.js +28 -0
- package/dist/dto/list-approvals.dto.js.map +1 -0
- package/dist/dto/list-collaborator-types.dto.d.ts +3 -1
- package/dist/dto/list-collaborator-types.dto.d.ts.map +1 -1
- package/dist/dto/list-collaborator-types.dto.js +7 -1
- package/dist/dto/list-collaborator-types.dto.js.map +1 -1
- package/dist/dto/list-collaborators.dto.d.ts +1 -0
- package/dist/dto/list-collaborators.dto.d.ts.map +1 -1
- package/dist/dto/list-collaborators.dto.js +5 -0
- package/dist/dto/list-collaborators.dto.js.map +1 -1
- package/dist/dto/list-contracts.dto.d.ts +8 -0
- package/dist/dto/list-contracts.dto.d.ts.map +1 -0
- package/dist/dto/list-contracts.dto.js +38 -0
- package/dist/dto/list-contracts.dto.js.map +1 -0
- package/dist/dto/list-departments.dto.d.ts +5 -0
- package/dist/dto/list-departments.dto.d.ts.map +1 -0
- package/dist/dto/list-departments.dto.js +23 -0
- package/dist/dto/list-departments.dto.js.map +1 -0
- package/dist/dto/list-projects.dto.d.ts +5 -0
- package/dist/dto/list-projects.dto.d.ts.map +1 -0
- package/dist/dto/list-projects.dto.js +23 -0
- package/dist/dto/list-projects.dto.js.map +1 -0
- package/dist/dto/list-schedule-adjustments.dto.d.ts +5 -0
- package/dist/dto/list-schedule-adjustments.dto.d.ts.map +1 -0
- package/dist/dto/list-schedule-adjustments.dto.js +23 -0
- package/dist/dto/list-schedule-adjustments.dto.js.map +1 -0
- package/dist/dto/list-time-off-requests.dto.d.ts +5 -0
- package/dist/dto/list-time-off-requests.dto.d.ts.map +1 -0
- package/dist/dto/list-time-off-requests.dto.js +23 -0
- package/dist/dto/list-time-off-requests.dto.js.map +1 -0
- package/dist/dto/list-timesheets.dto.d.ts +5 -0
- package/dist/dto/list-timesheets.dto.d.ts.map +1 -0
- package/dist/dto/list-timesheets.dto.js +23 -0
- package/dist/dto/list-timesheets.dto.js.map +1 -0
- package/dist/dto/reorder-collaborator-types.dto.d.ts +4 -0
- package/dist/dto/reorder-collaborator-types.dto.d.ts.map +1 -0
- package/dist/dto/reorder-collaborator-types.dto.js +25 -0
- package/dist/dto/reorder-collaborator-types.dto.js.map +1 -0
- package/dist/operations.service.d.ts +340 -271
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +1007 -1043
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +0 -22
- package/dist/operations.service.spec.js.map +1 -1
- package/hedhog/data/menu.yaml +0 -36
- package/hedhog/data/route.yaml +42 -73
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +8 -1
- package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +15 -10
- package/hedhog/frontend/app/_components/contract-details-screen.tsx.ejs +108 -213
- package/hedhog/frontend/app/_components/contract-form-screen.tsx.ejs +251 -2039
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +167 -60
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +70 -301
- package/hedhog/frontend/app/_components/system-user-select-with-create.tsx.ejs +102 -51
- package/hedhog/frontend/app/_lib/types.ts.ejs +19 -24
- package/hedhog/frontend/app/_lib/utils/format.ts.ejs +14 -9
- package/hedhog/frontend/app/approvals/page.tsx.ejs +842 -150
- package/hedhog/frontend/app/collaborator-types/page.tsx.ejs +445 -153
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +118 -49
- package/hedhog/frontend/app/contracts/[id]/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/contracts/page.tsx.ejs +215 -617
- package/hedhog/frontend/app/departments/page.tsx.ejs +257 -113
- package/hedhog/frontend/app/projects/page.tsx.ejs +90 -51
- package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +412 -147
- package/hedhog/frontend/app/time-off/page.tsx.ejs +400 -123
- package/hedhog/frontend/app/timesheets/page.tsx.ejs +460 -365
- package/hedhog/frontend/messages/en.json +143 -14
- package/hedhog/frontend/messages/pt.json +192 -54
- package/hedhog/table/operations_contract.yaml +0 -9
- package/package.json +4 -4
- package/src/controllers/operations-approvals.controller.ts +9 -3
- package/src/controllers/operations-collaborators.controller.ts +15 -2
- package/src/controllers/operations-contracts.controller.ts +8 -92
- package/src/controllers/operations-org-structure.controller.ts +17 -4
- package/src/controllers/operations-projects.controller.ts +10 -4
- package/src/controllers/operations-timesheets.controller.ts +17 -8
- package/src/dto/list-approvals.dto.ts +12 -0
- package/src/dto/list-collaborator-types.dto.ts +7 -2
- package/src/dto/list-collaborators.dto.ts +4 -0
- package/src/dto/list-contracts.dto.ts +20 -0
- package/src/dto/list-departments.dto.ts +8 -0
- package/src/dto/list-projects.dto.ts +8 -0
- package/src/dto/list-schedule-adjustments.dto.ts +8 -0
- package/src/dto/list-time-off-requests.dto.ts +8 -0
- package/src/dto/list-timesheets.dto.ts +8 -0
- package/src/dto/reorder-collaborator-types.dto.ts +10 -0
- package/src/operations.service.spec.ts +0 -30
- package/src/operations.service.ts +1557 -1806
- package/hedhog/frontend/app/_components/contract-creation-wizard.tsx.ejs +0 -631
- package/hedhog/frontend/app/_components/contract-template-form-screen.tsx.ejs +0 -526
- package/hedhog/frontend/app/_components/contract-template-select-with-create.tsx.ejs +0 -247
- package/hedhog/frontend/app/_components/contract-wizard-sheet.tsx.ejs +0 -3520
- package/hedhog/frontend/app/contracts/templates/page.tsx.ejs +0 -380
- package/hedhog/frontend/app/team/page.tsx.ejs +0 -352
- package/hedhog/table/operations_contract_financial_term.yaml +0 -40
- package/hedhog/table/operations_contract_revision.yaml +0 -38
- package/hedhog/table/operations_contract_signature.yaml +0 -38
- package/hedhog/table/operations_contract_template.yaml +0 -58
|
@@ -316,12 +316,54 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
316
316
|
const t = useTranslations('operations.ProjectDetailsPage');
|
|
317
317
|
const commonT = useTranslations('operations.Common');
|
|
318
318
|
const formT = useTranslations('operations.ProjectFormPage');
|
|
319
|
+
const contractT = useTranslations('operations.ContractFormPage');
|
|
319
320
|
const { request, currentLocaleCode, getSettingValue } = useApp();
|
|
320
321
|
const access = useOperationsAccess();
|
|
321
322
|
const router = useRouter();
|
|
322
323
|
const pathname = usePathname();
|
|
323
324
|
const searchParams = useSearchParams();
|
|
324
325
|
|
|
326
|
+
const getProjectStatusLabel = (value?: string | null) => {
|
|
327
|
+
if (!value) return commonT('labels.notAvailable');
|
|
328
|
+
try {
|
|
329
|
+
return formT(`options.statuses.${value}`);
|
|
330
|
+
} catch {
|
|
331
|
+
return formatEnumLabel(value);
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
const getContractStatusLabel = (value?: string | null) => {
|
|
335
|
+
if (!value) return commonT('labels.notAvailable');
|
|
336
|
+
try {
|
|
337
|
+
return contractT(`options.statuses.${value}`);
|
|
338
|
+
} catch {
|
|
339
|
+
return formatEnumLabel(value);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const getContractCategoryLabel = (value?: string | null) => {
|
|
343
|
+
if (!value) return commonT('labels.notAvailable');
|
|
344
|
+
try {
|
|
345
|
+
return contractT(`options.contractCategories.${value}`);
|
|
346
|
+
} catch {
|
|
347
|
+
return formatEnumLabel(value);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
const getContractTypeLabel = (value?: string | null) => {
|
|
351
|
+
if (!value) return commonT('labels.notAvailable');
|
|
352
|
+
try {
|
|
353
|
+
return contractT(`options.contractTypes.${value}`);
|
|
354
|
+
} catch {
|
|
355
|
+
return formatEnumLabel(value);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
const getSignatureStatusLabel = (value?: string | null) => {
|
|
359
|
+
if (!value) return commonT('labels.notAvailable');
|
|
360
|
+
try {
|
|
361
|
+
return contractT(`options.signatureStatuses.${value}`);
|
|
362
|
+
} catch {
|
|
363
|
+
return formatEnumLabel(value);
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
325
367
|
const isEditSheetOpen = useMemo(
|
|
326
368
|
() => shouldOpenEditSheet(searchParams.get('edit'), projectId),
|
|
327
369
|
[projectId, searchParams]
|
|
@@ -401,6 +443,21 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
401
443
|
enabled: Boolean(project),
|
|
402
444
|
});
|
|
403
445
|
|
|
446
|
+
const { data: projectStats } = useQuery<{
|
|
447
|
+
weeklyVelocity: Array<{ weekLabel: string; loggedHours: number }>;
|
|
448
|
+
allocationByCollaborator: Array<{ name: string; allocation: number }>;
|
|
449
|
+
quickRadar: {
|
|
450
|
+
activeAssignments: number;
|
|
451
|
+
pendingTimesheets: number;
|
|
452
|
+
totalWeeklyHours: number;
|
|
453
|
+
};
|
|
454
|
+
}>({
|
|
455
|
+
queryKey: ['operations-project-stats', projectId],
|
|
456
|
+
queryFn: () =>
|
|
457
|
+
fetchOperations(request, `/operations/projects/${projectId}/stats`),
|
|
458
|
+
enabled: Boolean(project),
|
|
459
|
+
});
|
|
460
|
+
|
|
404
461
|
const [boardState, setBoardState] = useState<BoardState | null>(null);
|
|
405
462
|
const [selectedTaskId, setSelectedTaskId] = useState<number | null>(null);
|
|
406
463
|
const [taskFormOpen, setTaskFormOpen] = useState(false);
|
|
@@ -543,18 +600,20 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
543
600
|
);
|
|
544
601
|
|
|
545
602
|
const allocationChartData = useMemo(() => {
|
|
603
|
+
if (projectStats?.allocationByCollaborator?.length) {
|
|
604
|
+
return projectStats.allocationByCollaborator;
|
|
605
|
+
}
|
|
546
606
|
if (!project) {
|
|
547
607
|
return [];
|
|
548
608
|
}
|
|
549
|
-
|
|
550
609
|
return project.assignments.slice(0, 6).map((assignment) => ({
|
|
551
610
|
name: getInitials(assignment.collaboratorName),
|
|
552
611
|
allocation:
|
|
553
612
|
typeof assignment.allocationPercent === 'number'
|
|
554
|
-
? Math.round(assignment.allocationPercent
|
|
613
|
+
? Math.round(assignment.allocationPercent)
|
|
555
614
|
: 0,
|
|
556
615
|
}));
|
|
557
|
-
}, [project]);
|
|
616
|
+
}, [project, projectStats]);
|
|
558
617
|
|
|
559
618
|
const sensors = useSensors(
|
|
560
619
|
useSensor(PointerSensor, {
|
|
@@ -563,22 +622,15 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
563
622
|
);
|
|
564
623
|
|
|
565
624
|
const velocityChartData = useMemo(() => {
|
|
566
|
-
if (
|
|
567
|
-
return
|
|
625
|
+
if (projectStats?.weeklyVelocity?.length) {
|
|
626
|
+
return projectStats.weeklyVelocity.map((row) => ({
|
|
627
|
+
week: row.weekLabel,
|
|
628
|
+
loggedHours: row.loggedHours,
|
|
629
|
+
completedTasks: 0,
|
|
630
|
+
}));
|
|
568
631
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
const closedBase = Math.max(
|
|
572
|
-
project.operationalIndicators.billableAssignments,
|
|
573
|
-
1
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
return [0, 1, 2, 3, 4, 5].map((index) => ({
|
|
577
|
-
week: `S${index + 1}`,
|
|
578
|
-
loggedHours: Math.round((totalHours / 6) * (0.8 + index * 0.08)),
|
|
579
|
-
completedTasks: Math.round((closedBase / 6) * (0.6 + index * 0.1)),
|
|
580
|
-
}));
|
|
581
|
-
}, [project]);
|
|
632
|
+
return [];
|
|
633
|
+
}, [projectStats]);
|
|
582
634
|
|
|
583
635
|
const findColumnByTask = (taskId: number) => {
|
|
584
636
|
const match = KANBAN_COLUMNS.find((column) =>
|
|
@@ -781,7 +833,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
781
833
|
</dt>
|
|
782
834
|
<dd className="font-medium">
|
|
783
835
|
<StatusBadge
|
|
784
|
-
label={
|
|
836
|
+
label={getProjectStatusLabel(project.status)}
|
|
785
837
|
className={getStatusBadgeClass(project.status)}
|
|
786
838
|
/>
|
|
787
839
|
</dd>
|
|
@@ -826,7 +878,11 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
826
878
|
</dt>
|
|
827
879
|
<dd className="font-medium">
|
|
828
880
|
{project.budgetAmount
|
|
829
|
-
? formatCurrency(
|
|
881
|
+
? formatCurrency(
|
|
882
|
+
project.budgetAmount,
|
|
883
|
+
getSettingValue,
|
|
884
|
+
currentLocaleCode
|
|
885
|
+
)
|
|
830
886
|
: commonT('labels.notAvailable')}
|
|
831
887
|
</dd>
|
|
832
888
|
</div>
|
|
@@ -858,7 +914,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
858
914
|
<dd className="font-medium">
|
|
859
915
|
{project.contractStatus ? (
|
|
860
916
|
<StatusBadge
|
|
861
|
-
label={
|
|
917
|
+
label={getContractStatusLabel(project.contractStatus)}
|
|
862
918
|
className={getStatusBadgeClass(project.contractStatus)}
|
|
863
919
|
/>
|
|
864
920
|
) : (
|
|
@@ -895,7 +951,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
895
951
|
</div>
|
|
896
952
|
</div>
|
|
897
953
|
<StatusBadge
|
|
898
|
-
label={
|
|
954
|
+
label={getContractStatusLabel(project.relatedContract.status)}
|
|
899
955
|
className={getStatusBadgeClass(
|
|
900
956
|
project.relatedContract.status
|
|
901
957
|
)}
|
|
@@ -908,7 +964,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
908
964
|
</dt>
|
|
909
965
|
<dd className="font-medium">
|
|
910
966
|
{project.relatedContract.contractCategory
|
|
911
|
-
?
|
|
967
|
+
? getContractCategoryLabel(
|
|
912
968
|
project.relatedContract.contractCategory
|
|
913
969
|
)
|
|
914
970
|
: commonT('labels.notAvailable')}
|
|
@@ -920,7 +976,9 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
920
976
|
</dt>
|
|
921
977
|
<dd className="font-medium">
|
|
922
978
|
{project.relatedContract.contractType
|
|
923
|
-
?
|
|
979
|
+
? getContractTypeLabel(
|
|
980
|
+
project.relatedContract.contractType
|
|
981
|
+
)
|
|
924
982
|
: commonT('labels.notAvailable')}
|
|
925
983
|
</dd>
|
|
926
984
|
</div>
|
|
@@ -951,7 +1009,9 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
951
1009
|
</dt>
|
|
952
1010
|
<dd className="font-medium">
|
|
953
1011
|
{project.relatedContract.signatureStatus
|
|
954
|
-
?
|
|
1012
|
+
? getSignatureStatusLabel(
|
|
1013
|
+
project.relatedContract.signatureStatus
|
|
1014
|
+
)
|
|
955
1015
|
: commonT('labels.notAvailable')}
|
|
956
1016
|
</dd>
|
|
957
1017
|
</div>
|
|
@@ -961,7 +1021,11 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
961
1021
|
</dt>
|
|
962
1022
|
<dd className="font-medium">
|
|
963
1023
|
{project.relatedContract.budgetAmount
|
|
964
|
-
? formatCurrency(
|
|
1024
|
+
? formatCurrency(
|
|
1025
|
+
project.relatedContract.budgetAmount,
|
|
1026
|
+
getSettingValue,
|
|
1027
|
+
currentLocaleCode
|
|
1028
|
+
)
|
|
965
1029
|
: commonT('labels.notAvailable')}
|
|
966
1030
|
</dd>
|
|
967
1031
|
</div>
|
|
@@ -984,15 +1048,15 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
984
1048
|
|
|
985
1049
|
<div className="grid gap-4 xl:grid-cols-12">
|
|
986
1050
|
<SectionCard
|
|
987
|
-
title=
|
|
988
|
-
description=
|
|
1051
|
+
title={t('sections.deliveryHealth')}
|
|
1052
|
+
description={t('sections.deliveryHealthDescription')}
|
|
989
1053
|
className="rounded-xl border bg-card p-4 shadow-sm xl:col-span-7"
|
|
990
1054
|
>
|
|
991
1055
|
<div className="grid gap-4 lg:grid-cols-2">
|
|
992
1056
|
<div className="rounded-lg border bg-muted/10 p-3">
|
|
993
1057
|
<div className="mb-2 flex items-center gap-2 text-sm font-medium">
|
|
994
1058
|
<BarChart3 className="size-4 text-sky-700" />
|
|
995
|
-
|
|
1059
|
+
{t('charts.allocationByCollaborator')}
|
|
996
1060
|
</div>
|
|
997
1061
|
<ChartContainer className="h-60 w-full" config={boardChartConfig}>
|
|
998
1062
|
<BarChart data={allocationChartData}>
|
|
@@ -1012,7 +1076,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1012
1076
|
<div className="rounded-lg border bg-muted/10 p-3">
|
|
1013
1077
|
<div className="mb-2 flex items-center gap-2 text-sm font-medium">
|
|
1014
1078
|
<Rocket className="size-4 text-emerald-700" />
|
|
1015
|
-
|
|
1079
|
+
{t('charts.weeklyVelocity')}
|
|
1016
1080
|
</div>
|
|
1017
1081
|
<ChartContainer className="h-60 w-full" config={boardChartConfig}>
|
|
1018
1082
|
<LineChart data={velocityChartData}>
|
|
@@ -1041,38 +1105,43 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1041
1105
|
</SectionCard>
|
|
1042
1106
|
|
|
1043
1107
|
<SectionCard
|
|
1044
|
-
title=
|
|
1045
|
-
description=
|
|
1108
|
+
title={t('sections.quickRadar')}
|
|
1109
|
+
description={t('sections.quickRadarDescription')}
|
|
1046
1110
|
className="rounded-xl border bg-card p-4 shadow-sm xl:col-span-5"
|
|
1047
1111
|
>
|
|
1048
1112
|
<div className="space-y-3">
|
|
1049
1113
|
<div className="rounded-lg border bg-emerald-50/50 p-3">
|
|
1050
1114
|
<div className="flex items-center justify-between text-sm">
|
|
1051
1115
|
<span className="text-muted-foreground">
|
|
1052
|
-
|
|
1116
|
+
{t('quickRadar.activeAssignments')}
|
|
1053
1117
|
</span>
|
|
1054
1118
|
<span className="font-semibold text-emerald-700">
|
|
1055
|
-
{
|
|
1119
|
+
{projectStats?.quickRadar?.activeAssignments ??
|
|
1120
|
+
project.operationalIndicators.activeAssignments}
|
|
1056
1121
|
</span>
|
|
1057
1122
|
</div>
|
|
1058
1123
|
</div>
|
|
1059
1124
|
<div className="rounded-lg border bg-amber-50/50 p-3">
|
|
1060
1125
|
<div className="flex items-center justify-between text-sm">
|
|
1061
1126
|
<span className="text-muted-foreground">
|
|
1062
|
-
|
|
1127
|
+
{t('quickRadar.timesheetPendencies')}
|
|
1063
1128
|
</span>
|
|
1064
1129
|
<span className="font-semibold text-amber-700">
|
|
1065
|
-
{
|
|
1130
|
+
{projectStats?.quickRadar?.pendingTimesheets ??
|
|
1131
|
+
project.timesheetSummary.pendingTimesheets}
|
|
1066
1132
|
</span>
|
|
1067
1133
|
</div>
|
|
1068
1134
|
</div>
|
|
1069
1135
|
<div className="rounded-lg border bg-sky-50/50 p-3">
|
|
1070
1136
|
<div className="flex items-center justify-between text-sm">
|
|
1071
1137
|
<span className="text-muted-foreground">
|
|
1072
|
-
|
|
1138
|
+
{t('quickRadar.plannedWeeklyHours')}
|
|
1073
1139
|
</span>
|
|
1074
1140
|
<span className="font-semibold text-sky-700">
|
|
1075
|
-
{formatHours(
|
|
1141
|
+
{formatHours(
|
|
1142
|
+
projectStats?.quickRadar?.totalWeeklyHours ??
|
|
1143
|
+
project.operationalIndicators.totalWeeklyHours
|
|
1144
|
+
)}
|
|
1076
1145
|
</span>
|
|
1077
1146
|
</div>
|
|
1078
1147
|
</div>
|
|
@@ -1081,8 +1150,8 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1081
1150
|
</div>
|
|
1082
1151
|
|
|
1083
1152
|
<SectionCard
|
|
1084
|
-
title=
|
|
1085
|
-
description=
|
|
1153
|
+
title={t('sections.taskBoard')}
|
|
1154
|
+
description={t('sections.taskBoardDescription')}
|
|
1086
1155
|
className="rounded-xl border bg-card p-4 shadow-sm"
|
|
1087
1156
|
actions={
|
|
1088
1157
|
<Button
|
|
@@ -1091,7 +1160,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1091
1160
|
onClick={() => openCreateTaskForm()}
|
|
1092
1161
|
>
|
|
1093
1162
|
<Plus className="size-4" />
|
|
1094
|
-
|
|
1163
|
+
{t('taskForm.titleNew')}
|
|
1095
1164
|
</Button>
|
|
1096
1165
|
}
|
|
1097
1166
|
>
|
|
@@ -1182,6 +1251,38 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1182
1251
|
: ''}
|
|
1183
1252
|
</span>
|
|
1184
1253
|
</div>
|
|
1254
|
+
|
|
1255
|
+
{task.assigneeName ? (
|
|
1256
|
+
<div className="mt-2 flex items-center gap-1.5">
|
|
1257
|
+
<div className="flex size-5 shrink-0 items-center justify-center overflow-hidden rounded-full bg-muted text-[9px] font-semibold uppercase text-muted-foreground ring-1 ring-border">
|
|
1258
|
+
{(() => {
|
|
1259
|
+
const photoUrl = getUserPhotoUrl(
|
|
1260
|
+
task.assigneeUserPhotoId
|
|
1261
|
+
);
|
|
1262
|
+
const avatarUrl =
|
|
1263
|
+
task.assigneePersonAvatarId
|
|
1264
|
+
? getPersonAvatarUrl(
|
|
1265
|
+
task.assigneePersonAvatarId
|
|
1266
|
+
)
|
|
1267
|
+
: null;
|
|
1268
|
+
const imgSrc = photoUrl ?? avatarUrl;
|
|
1269
|
+
return imgSrc ? (
|
|
1270
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
1271
|
+
<img
|
|
1272
|
+
src={imgSrc}
|
|
1273
|
+
alt={task.assigneeName}
|
|
1274
|
+
className="size-full object-cover"
|
|
1275
|
+
/>
|
|
1276
|
+
) : (
|
|
1277
|
+
getInitials(task.assigneeName)
|
|
1278
|
+
);
|
|
1279
|
+
})()}
|
|
1280
|
+
</div>
|
|
1281
|
+
<span className="truncate text-[11px] text-muted-foreground">
|
|
1282
|
+
{task.assigneeName}
|
|
1283
|
+
</span>
|
|
1284
|
+
</div>
|
|
1285
|
+
) : null}
|
|
1185
1286
|
</button>
|
|
1186
1287
|
)}
|
|
1187
1288
|
</DraggableTaskCard>
|
|
@@ -1291,10 +1392,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1291
1392
|
</div>
|
|
1292
1393
|
<div>
|
|
1293
1394
|
<dt className="text-muted-foreground">
|
|
1294
|
-
{t('indicators.
|
|
1395
|
+
{t('indicators.completedAssignments')}
|
|
1295
1396
|
</dt>
|
|
1296
1397
|
<dd className="font-medium">
|
|
1297
|
-
{project.operationalIndicators.
|
|
1398
|
+
{project.operationalIndicators.completedAssignments}
|
|
1298
1399
|
</dd>
|
|
1299
1400
|
</div>
|
|
1300
1401
|
<div>
|
|
@@ -1518,16 +1619,16 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1518
1619
|
<DialogContent className="sm:max-w-lg">
|
|
1519
1620
|
<DialogHeader>
|
|
1520
1621
|
<DialogTitle>
|
|
1521
|
-
{editingTaskId ? '
|
|
1622
|
+
{editingTaskId ? t('taskForm.titleEdit') : t('taskForm.titleNew')}
|
|
1522
1623
|
</DialogTitle>
|
|
1523
1624
|
</DialogHeader>
|
|
1524
1625
|
|
|
1525
1626
|
<div className="space-y-4">
|
|
1526
1627
|
<div className="space-y-1.5">
|
|
1527
|
-
<Label htmlFor="task-name">
|
|
1628
|
+
<Label htmlFor="task-name">{t('taskForm.nameLabel')} *</Label>
|
|
1528
1629
|
<Input
|
|
1529
1630
|
id="task-name"
|
|
1530
|
-
placeholder=
|
|
1631
|
+
placeholder={t('taskForm.namePlaceholder')}
|
|
1531
1632
|
value={taskFormData.name}
|
|
1532
1633
|
onChange={(e) =>
|
|
1533
1634
|
setTaskFormData((prev) => ({ ...prev, name: e.target.value }))
|
|
@@ -1536,10 +1637,12 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1536
1637
|
</div>
|
|
1537
1638
|
|
|
1538
1639
|
<div className="space-y-1.5">
|
|
1539
|
-
<Label htmlFor="task-description">
|
|
1640
|
+
<Label htmlFor="task-description">
|
|
1641
|
+
{t('taskForm.descriptionLabel')}
|
|
1642
|
+
</Label>
|
|
1540
1643
|
<Textarea
|
|
1541
1644
|
id="task-description"
|
|
1542
|
-
placeholder=
|
|
1645
|
+
placeholder={t('taskForm.descriptionPlaceholder')}
|
|
1543
1646
|
rows={3}
|
|
1544
1647
|
value={taskFormData.description}
|
|
1545
1648
|
onChange={(e) =>
|
|
@@ -1553,7 +1656,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1553
1656
|
|
|
1554
1657
|
<div className="grid grid-cols-2 gap-3">
|
|
1555
1658
|
<div className="space-y-1.5">
|
|
1556
|
-
<Label>
|
|
1659
|
+
<Label>{t('taskForm.priorityLabel')}</Label>
|
|
1557
1660
|
<Select
|
|
1558
1661
|
value={taskFormData.priority}
|
|
1559
1662
|
onValueChange={(v) =>
|
|
@@ -1563,7 +1666,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1563
1666
|
}))
|
|
1564
1667
|
}
|
|
1565
1668
|
>
|
|
1566
|
-
<SelectTrigger>
|
|
1669
|
+
<SelectTrigger className="w-full">
|
|
1567
1670
|
<SelectValue />
|
|
1568
1671
|
</SelectTrigger>
|
|
1569
1672
|
<SelectContent>
|
|
@@ -1581,7 +1684,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1581
1684
|
</div>
|
|
1582
1685
|
|
|
1583
1686
|
<div className="space-y-1.5">
|
|
1584
|
-
<Label>
|
|
1687
|
+
<Label>{t('taskForm.columnLabel')}</Label>
|
|
1585
1688
|
<Select
|
|
1586
1689
|
value={taskFormData.status}
|
|
1587
1690
|
onValueChange={(v) =>
|
|
@@ -1591,7 +1694,7 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1591
1694
|
}))
|
|
1592
1695
|
}
|
|
1593
1696
|
>
|
|
1594
|
-
<SelectTrigger>
|
|
1697
|
+
<SelectTrigger className="w-full">
|
|
1595
1698
|
<SelectValue />
|
|
1596
1699
|
</SelectTrigger>
|
|
1597
1700
|
<SelectContent>
|
|
@@ -1634,7 +1737,9 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1634
1737
|
|
|
1635
1738
|
<div className="grid grid-cols-2 gap-3">
|
|
1636
1739
|
<div className="space-y-1.5">
|
|
1637
|
-
<Label htmlFor="task-due-date">
|
|
1740
|
+
<Label htmlFor="task-due-date">
|
|
1741
|
+
{t('taskForm.deadlineLabel')}
|
|
1742
|
+
</Label>
|
|
1638
1743
|
<Input
|
|
1639
1744
|
id="task-due-date"
|
|
1640
1745
|
type="date"
|
|
@@ -1649,7 +1754,9 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1649
1754
|
</div>
|
|
1650
1755
|
|
|
1651
1756
|
<div className="space-y-1.5">
|
|
1652
|
-
<Label htmlFor="task-estimate">
|
|
1757
|
+
<Label htmlFor="task-estimate">
|
|
1758
|
+
{t('taskForm.estimateLabel')}
|
|
1759
|
+
</Label>
|
|
1653
1760
|
<Input
|
|
1654
1761
|
id="task-estimate"
|
|
1655
1762
|
type="number"
|
|
@@ -1668,10 +1775,10 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1668
1775
|
</div>
|
|
1669
1776
|
|
|
1670
1777
|
<div className="space-y-1.5">
|
|
1671
|
-
<Label htmlFor="task-tags">
|
|
1778
|
+
<Label htmlFor="task-tags">{t('taskForm.tagsLabel')}</Label>
|
|
1672
1779
|
<Input
|
|
1673
1780
|
id="task-tags"
|
|
1674
|
-
placeholder=
|
|
1781
|
+
placeholder={t('taskForm.tagsPlaceholder')}
|
|
1675
1782
|
value={taskFormData.tags}
|
|
1676
1783
|
onChange={(e) =>
|
|
1677
1784
|
setTaskFormData((prev) => ({ ...prev, tags: e.target.value }))
|
|
@@ -1690,17 +1797,17 @@ export function ProjectDetailsScreen({ projectId }: { projectId: number }) {
|
|
|
1690
1797
|
}}
|
|
1691
1798
|
disabled={taskFormLoading}
|
|
1692
1799
|
>
|
|
1693
|
-
|
|
1800
|
+
{commonT('actions.cancel')}
|
|
1694
1801
|
</Button>
|
|
1695
1802
|
<Button
|
|
1696
1803
|
onClick={() => void handleTaskFormSubmit()}
|
|
1697
1804
|
disabled={taskFormLoading || !taskFormData.name.trim()}
|
|
1698
1805
|
>
|
|
1699
1806
|
{taskFormLoading
|
|
1700
|
-
? '
|
|
1807
|
+
? t('taskForm.saving')
|
|
1701
1808
|
: editingTaskId
|
|
1702
|
-
? '
|
|
1703
|
-
: '
|
|
1809
|
+
? commonT('actions.save')
|
|
1810
|
+
: commonT('actions.create')}
|
|
1704
1811
|
</Button>
|
|
1705
1812
|
</DialogFooter>
|
|
1706
1813
|
</DialogContent>
|