@hed-hog/operations 0.0.332 → 0.0.347
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/operations-collaborators.controller.d.ts +55 -36
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
- package/dist/controllers/operations-projects.controller.d.ts +3 -0
- package/dist/controllers/operations-projects.controller.d.ts.map +1 -1
- package/dist/operations.service.d.ts +58 -36
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +34 -34
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +6 -0
- package/dist/operations.service.spec.js.map +1 -1
- package/hedhog/data/menu.yaml +5 -3
- package/hedhog/data/route.yaml +7 -7
- package/hedhog/frontend/app/_components/collaborator-details-screen.tsx.ejs +476 -0
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +3 -1
- package/hedhog/frontend/app/_components/collaborator-select-with-create.tsx.ejs +261 -0
- package/hedhog/frontend/app/_components/collaborator-tasks-tab.tsx.ejs +358 -358
- package/hedhog/frontend/app/_components/collaborator-timesheets-tab.tsx.ejs +6 -6
- package/hedhog/frontend/app/_components/contract-content-editor.tsx.ejs +258 -0
- package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +5 -4
- package/hedhog/frontend/app/_components/person-select-with-create.tsx.ejs +1 -0
- package/hedhog/frontend/app/_components/project-assignments-tab.tsx.ejs +0 -6
- package/hedhog/frontend/app/_components/project-cost-report-screen.tsx.ejs +23 -23
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +23 -50
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +62 -28
- package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +23 -6
- package/hedhog/frontend/app/_components/task-form-sheet.tsx.ejs +629 -629
- package/hedhog/frontend/app/_lib/api.ts.ejs +2 -2
- package/hedhog/frontend/app/_lib/utils/task-ui.ts.ejs +1 -1
- package/hedhog/frontend/app/my-projects/page.tsx.ejs +2 -16
- package/hedhog/frontend/app/my-tasks/page.tsx.ejs +86 -24
- package/hedhog/frontend/app/projects/page.tsx.ejs +6 -42
- package/hedhog/frontend/messages/operations/operations/en.json +2100 -0
- package/hedhog/frontend/messages/operations/operations/pt.json +2111 -0
- package/hedhog/frontend/widgets/capacity-distribution.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/effort-by-project.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/headcount-by-area.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/index.ts.ejs +25 -25
- package/hedhog/frontend/widgets/managed-projects-status.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-hours-period-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-open-requests-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-pending-requests-list.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-project-allocations-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-quick-actions.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-relevant-deadlines.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-timesheet-status-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/my-weekly-journey.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/portfolio-costs-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/portfolio-effort-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/portfolio-projects-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/portfolio-risk-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/project-status-overview.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/shared-operations-widget.tsx.ejs +169 -169
- package/hedhog/frontend/widgets/strategic-deadlines.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/team-approval-queue.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/team-capacity-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/team-headcount-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/team-hours-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/team-pending-approvals-kpi.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/team-utilization-overview.tsx.ejs +16 -16
- package/hedhog/frontend/widgets/team-workload-alerts.tsx.ejs +16 -16
- package/hedhog/table/operations_collaborator.yaml +8 -8
- package/hedhog/table/operations_task.yaml +76 -76
- package/hedhog/table/operations_task_activity.yaml +51 -51
- package/package.json +6 -6
- package/src/controllers/operations-collaborators.controller.ts +9 -9
- package/src/controllers/operations-tasks.controller.ts +156 -156
- package/src/dashboard/widgets/MyQuickActions.tsx +22 -22
- package/src/dto/create-collaborator.dto.ts +4 -4
- package/src/operations.service.spec.ts +1006 -988
- package/src/operations.service.ts +40 -42
|
@@ -97,6 +97,7 @@ import {
|
|
|
97
97
|
} from '../_lib/utils/forms';
|
|
98
98
|
import { ContractFormScreen } from './contract-form-screen';
|
|
99
99
|
import { OperationsHeader } from './operations-header';
|
|
100
|
+
import { ProjectFileAttachments } from './project-file-attachments';
|
|
100
101
|
|
|
101
102
|
const OPTION_PAGE_SIZE = 12;
|
|
102
103
|
|
|
@@ -158,12 +159,33 @@ type ProjectFormState = {
|
|
|
158
159
|
teamAssignments: TeamAssignmentState[];
|
|
159
160
|
};
|
|
160
161
|
|
|
161
|
-
type ProjectFormValues = ProjectFormState;
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
type ProjectFormValues = ProjectFormState;
|
|
163
|
+
|
|
164
|
+
type ManagerOption = {
|
|
165
|
+
id: number;
|
|
166
|
+
title: string;
|
|
167
|
+
description: string;
|
|
168
|
+
avatarUrl: string | null;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
function toManagerOption(collaborator: OperationsCollaborator): ManagerOption {
|
|
172
|
+
return {
|
|
173
|
+
id: collaborator.id,
|
|
174
|
+
title: collaborator.displayName,
|
|
175
|
+
description: [collaborator.department, collaborator.title]
|
|
176
|
+
.filter(Boolean)
|
|
177
|
+
.join(' • '),
|
|
178
|
+
avatarUrl:
|
|
179
|
+
getUserPhotoUrl(collaborator.userPhotoId) ??
|
|
180
|
+
getPersonAvatarUrl(collaborator.personAvatarId) ??
|
|
181
|
+
null,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function generateProjectCode(name: string): string {
|
|
186
|
+
const words = name.trim().split(/\s+/).filter(Boolean);
|
|
187
|
+
if (words.length === 0) return '';
|
|
188
|
+
if (words.length === 1) return words[0]!.slice(0, 5).toUpperCase();
|
|
167
189
|
return words
|
|
168
190
|
.map((w) => w[0]!.toUpperCase())
|
|
169
191
|
.join('')
|
|
@@ -673,11 +695,11 @@ export function ProjectFormScreen({
|
|
|
673
695
|
const personSheetModeRef = useRef<'create' | 'edit'>('edit');
|
|
674
696
|
const [personSheetOpen, setPersonSheetOpen] = useState(false);
|
|
675
697
|
const [personToEdit, setPersonToEdit] = useState<Person | null>(null);
|
|
676
|
-
const [createdManagerCollaborators, setCreatedManagerCollaborators] =
|
|
677
|
-
useState<OperationsCollaborator[]>([]);
|
|
678
698
|
const isSheetMode = Boolean(onCancel);
|
|
679
699
|
const isCreateMode = !projectId;
|
|
680
700
|
const [codeAutoMode, setCodeAutoMode] = useState(isCreateMode);
|
|
701
|
+
const [createdManagerCollaborators, setCreatedManagerCollaborators] =
|
|
702
|
+
useState<OperationsCollaborator[]>([]);
|
|
681
703
|
|
|
682
704
|
const projectFormSchema = useMemo(
|
|
683
705
|
() =>
|
|
@@ -769,7 +791,9 @@ export function ProjectFormScreen({
|
|
|
769
791
|
[rawCollaborators]
|
|
770
792
|
);
|
|
771
793
|
|
|
772
|
-
const createManagerCollaborator = async (
|
|
794
|
+
const createManagerCollaborator = async (
|
|
795
|
+
values: Record<string, string>
|
|
796
|
+
): Promise<ManagerOption | null> => {
|
|
773
797
|
const displayName = values.displayName?.trim() ?? '';
|
|
774
798
|
const weeklyCapacityHours = parseNumberInput(
|
|
775
799
|
values.weeklyCapacityHours ?? ''
|
|
@@ -797,8 +821,8 @@ export function ProjectFormScreen({
|
|
|
797
821
|
return next;
|
|
798
822
|
});
|
|
799
823
|
|
|
800
|
-
return created;
|
|
801
|
-
};
|
|
824
|
+
return toManagerOption(created);
|
|
825
|
+
};
|
|
802
826
|
|
|
803
827
|
const { data: contracts = [], refetch: refetchContracts } = useQuery<
|
|
804
828
|
OperationsContract[]
|
|
@@ -929,21 +953,13 @@ export function ProjectFormScreen({
|
|
|
929
953
|
[availableContracts, form.contractId]
|
|
930
954
|
);
|
|
931
955
|
|
|
932
|
-
const managerOptions = useMemo(
|
|
933
|
-
() =>
|
|
934
|
-
availableCollaborators.map((collaborator) =>
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
.join(' • '),
|
|
940
|
-
avatarUrl:
|
|
941
|
-
getUserPhotoUrl(collaborator.userPhotoId) ??
|
|
942
|
-
getPersonAvatarUrl(collaborator.personAvatarId) ??
|
|
943
|
-
null,
|
|
944
|
-
})),
|
|
945
|
-
[availableCollaborators]
|
|
946
|
-
);
|
|
956
|
+
const managerOptions = useMemo(
|
|
957
|
+
() =>
|
|
958
|
+
availableCollaborators.map((collaborator) =>
|
|
959
|
+
toManagerOption(collaborator)
|
|
960
|
+
),
|
|
961
|
+
[availableCollaborators]
|
|
962
|
+
);
|
|
947
963
|
|
|
948
964
|
const selectedAssignmentsCount = useMemo(
|
|
949
965
|
() =>
|
|
@@ -1233,6 +1249,9 @@ export function ProjectFormScreen({
|
|
|
1233
1249
|
: null
|
|
1234
1250
|
}
|
|
1235
1251
|
initialSelectedLabel={field.value}
|
|
1252
|
+
initialSelectedAvatarId={
|
|
1253
|
+
project?.clientAvatarId ?? null
|
|
1254
|
+
}
|
|
1236
1255
|
selectPlaceholder={t('placeholders.clientName')}
|
|
1237
1256
|
onChange={(personId, personName) => {
|
|
1238
1257
|
field.onChange(personName ?? '');
|
|
@@ -1321,7 +1340,7 @@ export function ProjectFormScreen({
|
|
|
1321
1340
|
)}
|
|
1322
1341
|
</p>
|
|
1323
1342
|
</div>
|
|
1324
|
-
<div className="grid min-w-0 gap-3 md:grid-cols-2 xl:grid-cols-
|
|
1343
|
+
<div className="grid min-w-0 gap-3 md:grid-cols-2 xl:grid-cols-5">
|
|
1325
1344
|
<div className="min-w-0 space-y-2">
|
|
1326
1345
|
<FieldLabel label={commonT('labels.manager')} />
|
|
1327
1346
|
<EntityPicker
|
|
@@ -1509,11 +1528,12 @@ export function ProjectFormScreen({
|
|
|
1509
1528
|
{!isCreateMode ? (
|
|
1510
1529
|
<>
|
|
1511
1530
|
<Tabs defaultValue="financials" className="space-y-3">
|
|
1512
|
-
<TabsList className="grid w-full grid-cols-
|
|
1531
|
+
<TabsList className="grid w-full grid-cols-3">
|
|
1513
1532
|
<TabsTrigger value="financials">
|
|
1514
1533
|
{t('sections.financials')}
|
|
1515
1534
|
</TabsTrigger>
|
|
1516
1535
|
<TabsTrigger value="team">{t('sections.team')}</TabsTrigger>
|
|
1536
|
+
<TabsTrigger value="files">{t('sections.files')}</TabsTrigger>
|
|
1517
1537
|
</TabsList>
|
|
1518
1538
|
|
|
1519
1539
|
<TabsContent value="financials" className="space-y-3">
|
|
@@ -1800,6 +1820,20 @@ export function ProjectFormScreen({
|
|
|
1800
1820
|
</div>
|
|
1801
1821
|
</div>
|
|
1802
1822
|
</TabsContent>
|
|
1823
|
+
|
|
1824
|
+
<TabsContent value="files" className="space-y-3">
|
|
1825
|
+
<div className="space-y-0.5">
|
|
1826
|
+
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
1827
|
+
{t('sections.files')}
|
|
1828
|
+
</h3>
|
|
1829
|
+
<p className="text-[11px] text-muted-foreground/80">
|
|
1830
|
+
{t('sections.filesDescription')}
|
|
1831
|
+
</p>
|
|
1832
|
+
</div>
|
|
1833
|
+
{projectId ? (
|
|
1834
|
+
<ProjectFileAttachments projectId={projectId} />
|
|
1835
|
+
) : null}
|
|
1836
|
+
</TabsContent>
|
|
1803
1837
|
</Tabs>
|
|
1804
1838
|
</>
|
|
1805
1839
|
) : (
|
|
@@ -41,7 +41,11 @@ import type {
|
|
|
41
41
|
OperationsTaskActivity,
|
|
42
42
|
OperationsTaskComment,
|
|
43
43
|
} from '../_lib/types';
|
|
44
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
formatDate,
|
|
46
|
+
formatDateTime,
|
|
47
|
+
getStatusBadgeClass,
|
|
48
|
+
} from '../_lib/utils/format';
|
|
45
49
|
import {
|
|
46
50
|
formatDurationMinutes,
|
|
47
51
|
getElapsedDoingMinutes,
|
|
@@ -138,7 +142,10 @@ export type TaskCommentsSectionProps = {
|
|
|
138
142
|
onChanged?: () => void;
|
|
139
143
|
};
|
|
140
144
|
|
|
141
|
-
export function TaskCommentsSection({
|
|
145
|
+
export function TaskCommentsSection({
|
|
146
|
+
taskId,
|
|
147
|
+
onChanged,
|
|
148
|
+
}: TaskCommentsSectionProps) {
|
|
142
149
|
const { request, showToastHandler, getSettingValue } = useApp();
|
|
143
150
|
const ct = useTranslations('operations.ProjectDetailsPage.commentsSection');
|
|
144
151
|
const editWindowMinutes = Number(
|
|
@@ -386,9 +393,13 @@ export function TaskCommentsSection({ taskId, onChanged }: TaskCommentsSectionPr
|
|
|
386
393
|
/>
|
|
387
394
|
<div className="flex items-center justify-end gap-2">
|
|
388
395
|
<span className="text-[10px] text-muted-foreground select-none">
|
|
389
|
-
<kbd className="rounded border bg-muted px-1 py-0.5 font-mono text-[10px]">
|
|
396
|
+
<kbd className="rounded border bg-muted px-1 py-0.5 font-mono text-[10px]">
|
|
397
|
+
Ctrl
|
|
398
|
+
</kbd>
|
|
390
399
|
{' + '}
|
|
391
|
-
<kbd className="rounded border bg-muted px-1 py-0.5 font-mono text-[10px]"
|
|
400
|
+
<kbd className="rounded border bg-muted px-1 py-0.5 font-mono text-[10px]">
|
|
401
|
+
↵
|
|
402
|
+
</kbd>
|
|
392
403
|
</span>
|
|
393
404
|
<Button
|
|
394
405
|
size="sm"
|
|
@@ -655,7 +666,9 @@ export function TaskDetailSheet({
|
|
|
655
666
|
|
|
656
667
|
<Tabs
|
|
657
668
|
value={tab}
|
|
658
|
-
onValueChange={(v) =>
|
|
669
|
+
onValueChange={(v) =>
|
|
670
|
+
setTab(v as 'comments' | 'activities')
|
|
671
|
+
}
|
|
659
672
|
className="flex flex-1 min-h-0 flex-col"
|
|
660
673
|
>
|
|
661
674
|
<div className="shrink-0 border-b px-5 py-2">
|
|
@@ -745,7 +758,11 @@ export function TaskDetailSheet({
|
|
|
745
758
|
<div className="flex items-center gap-1.5 text-sm">
|
|
746
759
|
<Calendar className="size-3.5 shrink-0 text-muted-foreground" />
|
|
747
760
|
<span>
|
|
748
|
-
{formatDate(
|
|
761
|
+
{formatDate(
|
|
762
|
+
task.dueDate,
|
|
763
|
+
getSettingValue,
|
|
764
|
+
currentLocaleCode
|
|
765
|
+
)}
|
|
749
766
|
</span>
|
|
750
767
|
</div>
|
|
751
768
|
</div>
|