@hed-hog/operations 0.0.329 → 0.0.330
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-tasks.controller.d.ts +4 -4
- package/dist/operations.service.d.ts +4 -4
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +24 -4
- package/dist/operations.service.js.map +1 -1
- package/dist/operations.service.spec.js +1 -1
- package/dist/operations.service.spec.js.map +1 -1
- package/hedhog/data/dashboard_component_role.yaml +8 -8
- package/hedhog/data/dashboard_role.yaml +1 -1
- package/hedhog/data/menu.yaml +6 -16
- package/hedhog/data/role.yaml +1 -1
- package/hedhog/data/route.yaml +55 -55
- package/hedhog/frontend/app/_components/async-options-combobox.tsx.ejs +15 -9
- package/hedhog/frontend/app/_components/project-costs-section.tsx.ejs +51 -81
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +39 -11
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +21 -4
- package/hedhog/frontend/app/_components/task-file-attachments.tsx.ejs +10 -8
- package/hedhog/frontend/app/_lib/hooks/use-values-visibility.ts.ejs +61 -0
- package/hedhog/frontend/app/approvals/page.tsx.ejs +5 -1
- package/hedhog/frontend/app/collaborators/page.tsx.ejs +68 -34
- package/hedhog/frontend/app/my-projects/page.tsx.ejs +45 -6
- package/hedhog/frontend/app/my-tasks/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/projects/page.tsx.ejs +60 -5
- package/hedhog/frontend/app/reports/collaborators/page.tsx.ejs +65 -52
- package/hedhog/frontend/app/reports/projects/page.tsx.ejs +80 -82
- package/hedhog/frontend/app/schedule-adjustments/page.tsx.ejs +7 -1
- package/hedhog/frontend/messages/en.json +223 -16
- package/hedhog/frontend/messages/pt.json +223 -16
- package/package.json +4 -4
- package/src/operations.service.spec.ts +1 -1
- package/src/operations.service.ts +25 -5
|
@@ -35,6 +35,8 @@ import {
|
|
|
35
35
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
36
36
|
import {
|
|
37
37
|
CalendarDays,
|
|
38
|
+
Eye,
|
|
39
|
+
EyeOff,
|
|
38
40
|
FileText,
|
|
39
41
|
Info,
|
|
40
42
|
LayoutGrid,
|
|
@@ -56,6 +58,10 @@ import { OperationsHeader } from '../_components/operations-header';
|
|
|
56
58
|
import { StatusBadge } from '../_components/status-badge';
|
|
57
59
|
import { fetchOperations, mutateOperations } from '../_lib/api';
|
|
58
60
|
import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
|
|
61
|
+
import {
|
|
62
|
+
MASKED_VALUE,
|
|
63
|
+
useValuesVisibility,
|
|
64
|
+
} from '../_lib/hooks/use-values-visibility';
|
|
59
65
|
import type {
|
|
60
66
|
OperationsCollaborator,
|
|
61
67
|
OperationsCollaboratorStats,
|
|
@@ -128,6 +134,7 @@ export default function OperationsCollaboratorsPage() {
|
|
|
128
134
|
return savedViewMode === 'cards' ? 'cards' : 'table';
|
|
129
135
|
});
|
|
130
136
|
const [isCreateSheetOpen, setIsCreateSheetOpen] = useState(false);
|
|
137
|
+
const { valuesVisible, toggleValuesVisible } = useValuesVisibility();
|
|
131
138
|
|
|
132
139
|
const editParam = searchParams.get('edit');
|
|
133
140
|
const editingCollaboratorId =
|
|
@@ -345,11 +352,13 @@ export default function OperationsCollaboratorsPage() {
|
|
|
345
352
|
t('cards.totalSalaryDescription'),
|
|
346
353
|
t('cards.totalSalaryTooltip')
|
|
347
354
|
),
|
|
348
|
-
value:
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
355
|
+
value: valuesVisible
|
|
356
|
+
? formatCurrency(
|
|
357
|
+
collaboratorStats?.totalSalary ?? 0,
|
|
358
|
+
getSettingValue,
|
|
359
|
+
currentLocaleCode
|
|
360
|
+
)
|
|
361
|
+
: MASKED_VALUE,
|
|
353
362
|
icon: Wallet,
|
|
354
363
|
accentClassName: 'from-emerald-500/20 via-green-500/10 to-transparent',
|
|
355
364
|
iconContainerClassName:
|
|
@@ -362,11 +371,13 @@ export default function OperationsCollaboratorsPage() {
|
|
|
362
371
|
t('cards.totalCostsDescription'),
|
|
363
372
|
t('cards.totalCostsTooltip')
|
|
364
373
|
),
|
|
365
|
-
value:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
374
|
+
value: valuesVisible
|
|
375
|
+
? formatCurrency(
|
|
376
|
+
collaboratorStats?.totalCosts ?? 0,
|
|
377
|
+
getSettingValue,
|
|
378
|
+
currentLocaleCode
|
|
379
|
+
)
|
|
380
|
+
: MASKED_VALUE,
|
|
370
381
|
icon: TrendingUp,
|
|
371
382
|
accentClassName: 'from-amber-500/20 via-yellow-500/10 to-transparent',
|
|
372
383
|
iconContainerClassName:
|
|
@@ -379,11 +390,13 @@ export default function OperationsCollaboratorsPage() {
|
|
|
379
390
|
t('cards.avgSalaryPlusCostsDescription'),
|
|
380
391
|
t('cards.avgSalaryPlusCostsTooltip')
|
|
381
392
|
),
|
|
382
|
-
value:
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
393
|
+
value: valuesVisible
|
|
394
|
+
? formatCurrency(
|
|
395
|
+
collaboratorStats?.avgSalaryPlusCosts ?? 0,
|
|
396
|
+
getSettingValue,
|
|
397
|
+
currentLocaleCode
|
|
398
|
+
)
|
|
399
|
+
: MASKED_VALUE,
|
|
387
400
|
icon: Wallet,
|
|
388
401
|
accentClassName: 'from-violet-500/20 via-purple-500/10 to-transparent',
|
|
389
402
|
iconContainerClassName:
|
|
@@ -396,11 +409,13 @@ export default function OperationsCollaboratorsPage() {
|
|
|
396
409
|
t('cards.avgSalaryPlusCostsPerCollaboratorDescription'),
|
|
397
410
|
t('cards.avgSalaryPlusCostsPerCollaboratorTooltip')
|
|
398
411
|
),
|
|
399
|
-
value:
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
412
|
+
value: valuesVisible
|
|
413
|
+
? formatCurrency(
|
|
414
|
+
collaboratorStats?.avgSalaryPlusCostsPerCollaborator ?? 0,
|
|
415
|
+
getSettingValue,
|
|
416
|
+
currentLocaleCode
|
|
417
|
+
)
|
|
418
|
+
: MASKED_VALUE,
|
|
404
419
|
icon: TrendingUp,
|
|
405
420
|
accentClassName: 'from-rose-500/20 via-pink-500/10 to-transparent',
|
|
406
421
|
iconContainerClassName:
|
|
@@ -413,6 +428,7 @@ export default function OperationsCollaboratorsPage() {
|
|
|
413
428
|
formatCurrency,
|
|
414
429
|
getSettingValue,
|
|
415
430
|
currentLocaleCode,
|
|
431
|
+
valuesVisible,
|
|
416
432
|
]);
|
|
417
433
|
|
|
418
434
|
const handleViewModeChange = (value: string) => {
|
|
@@ -452,13 +468,27 @@ export default function OperationsCollaboratorsPage() {
|
|
|
452
468
|
description={t('description')}
|
|
453
469
|
current={t('breadcrumb')}
|
|
454
470
|
actions={
|
|
455
|
-
|
|
456
|
-
<
|
|
471
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
472
|
+
<Button
|
|
473
|
+
size="icon"
|
|
474
|
+
variant="ghost"
|
|
475
|
+
onClick={toggleValuesVisible}
|
|
476
|
+
title={commonT(
|
|
477
|
+
valuesVisible ? 'actions.hideValues' : 'actions.showValues'
|
|
478
|
+
)}
|
|
479
|
+
>
|
|
480
|
+
{valuesVisible ? (
|
|
481
|
+
<EyeOff className="h-4 w-4" />
|
|
482
|
+
) : (
|
|
483
|
+
<Eye className="h-4 w-4" />
|
|
484
|
+
)}
|
|
485
|
+
</Button>
|
|
486
|
+
{access.isDirector ? (
|
|
457
487
|
<Button size="sm" onClick={openCreateSheet}>
|
|
458
488
|
{commonT('actions.create')}
|
|
459
489
|
</Button>
|
|
460
|
-
|
|
461
|
-
|
|
490
|
+
) : null}
|
|
491
|
+
</div>
|
|
462
492
|
}
|
|
463
493
|
/>
|
|
464
494
|
|
|
@@ -620,11 +650,13 @@ export default function OperationsCollaboratorsPage() {
|
|
|
620
650
|
{formT('fields.compensationAmount')}:
|
|
621
651
|
</span>{' '}
|
|
622
652
|
{collaborator.compensationAmount != null
|
|
623
|
-
?
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
653
|
+
? valuesVisible
|
|
654
|
+
? formatCurrency(
|
|
655
|
+
Number(collaborator.compensationAmount),
|
|
656
|
+
getSettingValue,
|
|
657
|
+
currentLocaleCode
|
|
658
|
+
)
|
|
659
|
+
: MASKED_VALUE
|
|
628
660
|
: commonT('labels.notAvailable')}
|
|
629
661
|
</div>
|
|
630
662
|
<div>
|
|
@@ -823,11 +855,13 @@ export default function OperationsCollaboratorsPage() {
|
|
|
823
855
|
</TableCell>
|
|
824
856
|
<TableCell className="hidden 2xl:table-cell">
|
|
825
857
|
{collaborator.compensationAmount != null
|
|
826
|
-
?
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
858
|
+
? valuesVisible
|
|
859
|
+
? formatCurrency(
|
|
860
|
+
Number(collaborator.compensationAmount),
|
|
861
|
+
getSettingValue,
|
|
862
|
+
currentLocaleCode
|
|
863
|
+
)
|
|
864
|
+
: MASKED_VALUE
|
|
831
865
|
: commonT('labels.notAvailable')}
|
|
832
866
|
</TableCell>
|
|
833
867
|
<TableCell className="hidden xl:table-cell">
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
PaginationFooter,
|
|
7
7
|
SearchBar,
|
|
8
8
|
} from '@/components/entity-list';
|
|
9
|
+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
9
10
|
import { Button } from '@/components/ui/button';
|
|
10
11
|
import { Card, CardContent } from '@/components/ui/card';
|
|
11
12
|
import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
|
|
@@ -41,6 +42,22 @@ const MY_PROJECTS_VIEW_STORAGE_KEY = 'operations-my-projects-view-mode';
|
|
|
41
42
|
|
|
42
43
|
type ProjectViewMode = 'table' | 'cards';
|
|
43
44
|
|
|
45
|
+
function getPersonAvatarUrl(avatarId?: number | null): string {
|
|
46
|
+
return typeof avatarId === 'number' && avatarId > 0
|
|
47
|
+
? `${process.env.NEXT_PUBLIC_API_BASE_URL}/person/avatar/${avatarId}`
|
|
48
|
+
: '/placeholder.png';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getInitials(value?: string | null): string {
|
|
52
|
+
if (!value) return '?';
|
|
53
|
+
return value
|
|
54
|
+
.split(' ')
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
.slice(0, 2)
|
|
57
|
+
.map((w) => w[0]!.toUpperCase())
|
|
58
|
+
.join('');
|
|
59
|
+
}
|
|
60
|
+
|
|
44
61
|
export default function OperationsMyProjectsPage() {
|
|
45
62
|
const t = useTranslations('operations.MyProjectsPage');
|
|
46
63
|
const commonT = useTranslations('operations.Common');
|
|
@@ -266,11 +283,22 @@ export default function OperationsMyProjectsPage() {
|
|
|
266
283
|
</div>
|
|
267
284
|
|
|
268
285
|
<div className="grid gap-2 text-sm text-muted-foreground lg:grid-cols-2">
|
|
269
|
-
<div>
|
|
270
|
-
<span className="font-medium text-foreground">
|
|
286
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
287
|
+
<span className="shrink-0 font-medium text-foreground">
|
|
271
288
|
{commonT('labels.client')}:
|
|
272
|
-
</span>
|
|
273
|
-
|
|
289
|
+
</span>
|
|
290
|
+
<Avatar className="h-5 w-5 shrink-0">
|
|
291
|
+
<AvatarImage
|
|
292
|
+
src={getPersonAvatarUrl(project.clientAvatarId)}
|
|
293
|
+
alt={project.clientName ?? ''}
|
|
294
|
+
/>
|
|
295
|
+
<AvatarFallback className="text-[9px] font-medium">
|
|
296
|
+
{getInitials(project.clientName)}
|
|
297
|
+
</AvatarFallback>
|
|
298
|
+
</Avatar>
|
|
299
|
+
<span className="truncate">
|
|
300
|
+
{project.clientName || commonT('labels.notAvailable')}
|
|
301
|
+
</span>
|
|
274
302
|
</div>
|
|
275
303
|
<div>
|
|
276
304
|
<span className="font-medium text-foreground">
|
|
@@ -367,8 +395,19 @@ export default function OperationsMyProjectsPage() {
|
|
|
367
395
|
</div>
|
|
368
396
|
</TableCell>
|
|
369
397
|
<TableCell>
|
|
370
|
-
<div className="
|
|
371
|
-
|
|
398
|
+
<div className="flex min-w-0 items-center gap-1.5">
|
|
399
|
+
<Avatar className="h-6 w-6 shrink-0">
|
|
400
|
+
<AvatarImage
|
|
401
|
+
src={getPersonAvatarUrl(project.clientAvatarId)}
|
|
402
|
+
alt={project.clientName ?? ''}
|
|
403
|
+
/>
|
|
404
|
+
<AvatarFallback className="text-[9px] font-medium">
|
|
405
|
+
{getInitials(project.clientName)}
|
|
406
|
+
</AvatarFallback>
|
|
407
|
+
</Avatar>
|
|
408
|
+
<span className="truncate">
|
|
409
|
+
{project.clientName || commonT('labels.notAvailable')}
|
|
410
|
+
</span>
|
|
372
411
|
</div>
|
|
373
412
|
</TableCell>
|
|
374
413
|
<TableCell>
|
|
@@ -945,7 +945,7 @@ export default function OperationsMyTasksPage() {
|
|
|
945
945
|
|
|
946
946
|
<div className="mt-3 space-y-1.5">
|
|
947
947
|
<div className="flex items-center justify-between text-[11px] text-muted-foreground">
|
|
948
|
-
<span>
|
|
948
|
+
<span>{t('labels.progress')}</span>
|
|
949
949
|
<span>
|
|
950
950
|
{getTaskProgress(task.status)}%
|
|
951
951
|
</span>
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
ArchiveRestore,
|
|
33
33
|
CalendarDays,
|
|
34
34
|
Eye,
|
|
35
|
+
EyeOff,
|
|
35
36
|
FileText,
|
|
36
37
|
FolderKanban,
|
|
37
38
|
LayoutGrid,
|
|
@@ -49,8 +50,16 @@ import { ProjectFormScreen } from '../_components/project-form-screen';
|
|
|
49
50
|
import { StatusBadge } from '../_components/status-badge';
|
|
50
51
|
import { fetchOperations, mutateOperations } from '../_lib/api';
|
|
51
52
|
import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
|
|
53
|
+
import {
|
|
54
|
+
MASKED_VALUE,
|
|
55
|
+
useValuesVisibility,
|
|
56
|
+
} from '../_lib/hooks/use-values-visibility';
|
|
52
57
|
import type { OperationsProject, PaginatedResponse } from '../_lib/types';
|
|
53
|
-
import {
|
|
58
|
+
import {
|
|
59
|
+
formatCurrency,
|
|
60
|
+
formatDate,
|
|
61
|
+
getStatusBadgeClass,
|
|
62
|
+
} from '../_lib/utils/format';
|
|
54
63
|
|
|
55
64
|
const PROJECT_VIEW_STORAGE_KEY = 'operations-projects-view-mode';
|
|
56
65
|
|
|
@@ -154,6 +163,8 @@ export default function OperationsProjectsPage() {
|
|
|
154
163
|
updateSheetQuery({ editId: projectId });
|
|
155
164
|
};
|
|
156
165
|
|
|
166
|
+
const { valuesVisible, toggleValuesVisible } = useValuesVisibility();
|
|
167
|
+
|
|
157
168
|
const { data: projectsResponse, refetch } = useQuery<
|
|
158
169
|
PaginatedResponse<OperationsProject>
|
|
159
170
|
>({
|
|
@@ -269,11 +280,27 @@ export default function OperationsProjectsPage() {
|
|
|
269
280
|
description={t('description')}
|
|
270
281
|
current={t('breadcrumb')}
|
|
271
282
|
actions={
|
|
272
|
-
|
|
273
|
-
<Button
|
|
274
|
-
|
|
283
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
284
|
+
<Button
|
|
285
|
+
size="icon"
|
|
286
|
+
variant="ghost"
|
|
287
|
+
onClick={toggleValuesVisible}
|
|
288
|
+
title={commonT(
|
|
289
|
+
valuesVisible ? 'actions.hideValues' : 'actions.showValues'
|
|
290
|
+
)}
|
|
291
|
+
>
|
|
292
|
+
{valuesVisible ? (
|
|
293
|
+
<EyeOff className="h-4 w-4" />
|
|
294
|
+
) : (
|
|
295
|
+
<Eye className="h-4 w-4" />
|
|
296
|
+
)}
|
|
275
297
|
</Button>
|
|
276
|
-
|
|
298
|
+
{access.isDirector ? (
|
|
299
|
+
<Button size="sm" onClick={openCreateSheet}>
|
|
300
|
+
{commonT('actions.create')}
|
|
301
|
+
</Button>
|
|
302
|
+
) : null}
|
|
303
|
+
</div>
|
|
277
304
|
}
|
|
278
305
|
/>
|
|
279
306
|
|
|
@@ -441,6 +468,20 @@ export default function OperationsProjectsPage() {
|
|
|
441
468
|
currentLocaleCode
|
|
442
469
|
)}
|
|
443
470
|
</div>
|
|
471
|
+
<div>
|
|
472
|
+
<span className="font-medium text-foreground">
|
|
473
|
+
{commonT('labels.budget')}:
|
|
474
|
+
</span>{' '}
|
|
475
|
+
{project.budgetAmount != null
|
|
476
|
+
? valuesVisible
|
|
477
|
+
? formatCurrency(
|
|
478
|
+
project.budgetAmount,
|
|
479
|
+
getSettingValue,
|
|
480
|
+
currentLocaleCode
|
|
481
|
+
)
|
|
482
|
+
: MASKED_VALUE
|
|
483
|
+
: commonT('labels.notAvailable')}
|
|
484
|
+
</div>
|
|
444
485
|
<div className="flex items-center gap-2">
|
|
445
486
|
<span className="font-medium text-foreground">
|
|
446
487
|
{commonT('labels.contractStatus')}:
|
|
@@ -538,6 +579,9 @@ export default function OperationsProjectsPage() {
|
|
|
538
579
|
<TableHead className="hidden xl:table-cell">
|
|
539
580
|
{commonT('labels.endDate')}
|
|
540
581
|
</TableHead>
|
|
582
|
+
<TableHead className="hidden xl:table-cell">
|
|
583
|
+
{commonT('labels.budget')}
|
|
584
|
+
</TableHead>
|
|
541
585
|
<TableHead className="hidden 2xl:table-cell">
|
|
542
586
|
{commonT('labels.contractStatus')}
|
|
543
587
|
</TableHead>
|
|
@@ -626,6 +670,17 @@ export default function OperationsProjectsPage() {
|
|
|
626
670
|
currentLocaleCode
|
|
627
671
|
)}
|
|
628
672
|
</TableCell>
|
|
673
|
+
<TableCell className="hidden xl:table-cell">
|
|
674
|
+
{project.budgetAmount != null
|
|
675
|
+
? valuesVisible
|
|
676
|
+
? formatCurrency(
|
|
677
|
+
project.budgetAmount,
|
|
678
|
+
getSettingValue,
|
|
679
|
+
currentLocaleCode
|
|
680
|
+
)
|
|
681
|
+
: MASKED_VALUE
|
|
682
|
+
: commonT('labels.notAvailable')}
|
|
683
|
+
</TableCell>
|
|
629
684
|
<TableCell className="hidden 2xl:table-cell">
|
|
630
685
|
{project.contractStatus ? (
|
|
631
686
|
<StatusBadge
|