@hed-hog/operations 0.0.318 → 0.0.321
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-contracts.controller.d.ts +9 -9
- 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 +80 -1
- 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 +227 -1
- 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 +7 -7
- 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 +4 -0
- package/src/operations.service.spec.ts +45 -7
- package/src/operations.service.ts +1988 -221
|
@@ -36,6 +36,7 @@ import { Textarea } from '@/components/ui/textarea';
|
|
|
36
36
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
37
37
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
38
38
|
import {
|
|
39
|
+
CalendarDays,
|
|
39
40
|
CalendarOff,
|
|
40
41
|
Check,
|
|
41
42
|
ClipboardCheck,
|
|
@@ -48,7 +49,11 @@ import {
|
|
|
48
49
|
X,
|
|
49
50
|
} from 'lucide-react';
|
|
50
51
|
import { useTranslations } from 'next-intl';
|
|
51
|
-
import { useState } from 'react';
|
|
52
|
+
import { useMemo, useState } from 'react';
|
|
53
|
+
import {
|
|
54
|
+
OperationsCalendarView,
|
|
55
|
+
type OperationsCalendarItem,
|
|
56
|
+
} from '../_components/operations-calendar-view';
|
|
52
57
|
import { OperationsHeader } from '../_components/operations-header';
|
|
53
58
|
import { StatusBadge } from '../_components/status-badge';
|
|
54
59
|
import { fetchOperations } from '../_lib/api';
|
|
@@ -56,7 +61,6 @@ import { useOperationsAccess } from '../_lib/hooks/use-operations-access';
|
|
|
56
61
|
import type { OperationsApproval, PaginatedResponse } from '../_lib/types';
|
|
57
62
|
import {
|
|
58
63
|
formatDate,
|
|
59
|
-
formatDateRange,
|
|
60
64
|
formatDateTime,
|
|
61
65
|
formatEnumLabel,
|
|
62
66
|
formatHours,
|
|
@@ -217,13 +221,25 @@ export default function OperationsApprovalsPage() {
|
|
|
217
221
|
const [search, setSearch] = useState('');
|
|
218
222
|
const [statusFilter, setStatusFilter] = useState('all');
|
|
219
223
|
const [targetFilter, setTargetFilter] = useState('all');
|
|
224
|
+
const [dateFrom, setDateFrom] = useState('');
|
|
225
|
+
const [dateTo, setDateTo] = useState('');
|
|
226
|
+
const [calendarYear, setCalendarYear] = useState(() =>
|
|
227
|
+
new Date().getFullYear()
|
|
228
|
+
);
|
|
229
|
+
const [calendarMonth, setCalendarMonth] = useState(
|
|
230
|
+
() => new Date().getMonth() + 1
|
|
231
|
+
);
|
|
220
232
|
const [page, setPage] = useState(1);
|
|
221
233
|
const [pageSize, setPageSize] = useState(12);
|
|
222
|
-
const [viewMode, setViewMode] = useState<'table' | 'cards'>(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
234
|
+
const [viewMode, setViewMode] = useState<'table' | 'cards' | 'calendar'>(
|
|
235
|
+
() => {
|
|
236
|
+
if (typeof window === 'undefined') return 'table';
|
|
237
|
+
const saved = window.localStorage.getItem(
|
|
238
|
+
'operations-approvals-view-mode'
|
|
239
|
+
);
|
|
240
|
+
return saved === 'cards' || saved === 'calendar' ? saved : 'table';
|
|
241
|
+
}
|
|
242
|
+
);
|
|
227
243
|
const [decisionNote, setDecisionNote] = useState('');
|
|
228
244
|
const [pendingDecision, setPendingDecision] =
|
|
229
245
|
useState<PendingDecision | null>(null);
|
|
@@ -297,19 +313,16 @@ export default function OperationsApprovalsPage() {
|
|
|
297
313
|
const getTargetLabel = (approval: OperationsApproval) => {
|
|
298
314
|
switch (approval.targetType) {
|
|
299
315
|
case 'timesheet':
|
|
300
|
-
return `${getTargetTypeLabel('timesheet')} ${
|
|
301
|
-
approval.timesheetWeekStartDate
|
|
302
|
-
approval.timesheetWeekEndDate
|
|
316
|
+
return `${getTargetTypeLabel('timesheet')} ${formatDate(
|
|
317
|
+
approval.timesheetWeekStartDate
|
|
303
318
|
)}`;
|
|
304
319
|
case 'time_off_request':
|
|
305
|
-
return `${getTimeOffTypeLabel(approval.timeOffType)} ${
|
|
306
|
-
approval.timeOffStartDate
|
|
307
|
-
approval.timeOffEndDate
|
|
320
|
+
return `${getTimeOffTypeLabel(approval.timeOffType)} ${formatDate(
|
|
321
|
+
approval.timeOffStartDate
|
|
308
322
|
)}`;
|
|
309
323
|
case 'schedule_adjustment_request':
|
|
310
|
-
return `${getScheduleScopeLabel(approval.scheduleRequestScope)} ${
|
|
311
|
-
approval.scheduleStartDate
|
|
312
|
-
approval.scheduleEndDate
|
|
324
|
+
return `${getScheduleScopeLabel(approval.scheduleRequestScope)} ${formatDate(
|
|
325
|
+
approval.scheduleStartDate
|
|
313
326
|
)}`;
|
|
314
327
|
default:
|
|
315
328
|
return getTargetTypeLabel(approval.targetType);
|
|
@@ -325,6 +338,8 @@ export default function OperationsApprovalsPage() {
|
|
|
325
338
|
search,
|
|
326
339
|
statusFilter,
|
|
327
340
|
targetFilter,
|
|
341
|
+
dateFrom,
|
|
342
|
+
dateTo,
|
|
328
343
|
page,
|
|
329
344
|
pageSize,
|
|
330
345
|
],
|
|
@@ -337,6 +352,43 @@ export default function OperationsApprovalsPage() {
|
|
|
337
352
|
if (search.trim()) params.set('search', search.trim());
|
|
338
353
|
if (statusFilter !== 'all') params.set('status', statusFilter);
|
|
339
354
|
if (targetFilter !== 'all') params.set('targetType', targetFilter);
|
|
355
|
+
if (dateFrom) params.set('dateFrom', dateFrom);
|
|
356
|
+
if (dateTo) params.set('dateTo', dateTo);
|
|
357
|
+
return fetchOperations<PaginatedResponse<OperationsApproval>>(
|
|
358
|
+
request,
|
|
359
|
+
`/operations/approvals?${params.toString()}`
|
|
360
|
+
);
|
|
361
|
+
},
|
|
362
|
+
placeholderData: (previous) => previous,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const calendarDateFrom = `${String(calendarYear)}-${String(calendarMonth).padStart(2, '0')}-01`;
|
|
366
|
+
const calendarDateTo = (() => {
|
|
367
|
+
const lastDay = new Date(calendarYear, calendarMonth, 0).getDate();
|
|
368
|
+
return `${String(calendarYear)}-${String(calendarMonth).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;
|
|
369
|
+
})();
|
|
370
|
+
|
|
371
|
+
const { data: calendarApprovalsResponse } = useQuery<
|
|
372
|
+
PaginatedResponse<OperationsApproval>
|
|
373
|
+
>({
|
|
374
|
+
queryKey: [
|
|
375
|
+
'operations-approvals-calendar',
|
|
376
|
+
currentLocaleCode,
|
|
377
|
+
search,
|
|
378
|
+
statusFilter,
|
|
379
|
+
calendarYear,
|
|
380
|
+
calendarMonth,
|
|
381
|
+
],
|
|
382
|
+
enabled: access.isSupervisor && viewMode === 'calendar',
|
|
383
|
+
queryFn: () => {
|
|
384
|
+
const params = new URLSearchParams({
|
|
385
|
+
sortField: 'submittedAt',
|
|
386
|
+
sortOrder: 'desc',
|
|
387
|
+
dateFrom: calendarDateFrom,
|
|
388
|
+
dateTo: calendarDateTo,
|
|
389
|
+
});
|
|
390
|
+
if (search.trim()) params.set('search', search.trim());
|
|
391
|
+
if (statusFilter !== 'all') params.set('status', statusFilter);
|
|
340
392
|
return fetchOperations<PaginatedResponse<OperationsApproval>>(
|
|
341
393
|
request,
|
|
342
394
|
`/operations/approvals?${params.toString()}`
|
|
@@ -346,10 +398,56 @@ export default function OperationsApprovalsPage() {
|
|
|
346
398
|
});
|
|
347
399
|
|
|
348
400
|
const approvals = approvalsResponse?.data ?? [];
|
|
401
|
+
const calendarApprovals = calendarApprovalsResponse?.data ?? [];
|
|
402
|
+
|
|
403
|
+
const buildCalendarItem = (approval: OperationsApproval) => ({
|
|
404
|
+
id: `approval-${approval.id}`,
|
|
405
|
+
date:
|
|
406
|
+
approval.targetType === 'timesheet'
|
|
407
|
+
? String(approval.timesheetWeekStartDate ?? '').match(
|
|
408
|
+
/(\d{4}-\d{2}-\d{2})/
|
|
409
|
+
)?.[1] ||
|
|
410
|
+
approval.submittedAt?.slice(0, 10) ||
|
|
411
|
+
''
|
|
412
|
+
: approval.targetType === 'time_off_request'
|
|
413
|
+
? String(approval.timeOffStartDate ?? '').match(
|
|
414
|
+
/(\d{4}-\d{2}-\d{2})/
|
|
415
|
+
)?.[1] ||
|
|
416
|
+
approval.submittedAt?.slice(0, 10) ||
|
|
417
|
+
''
|
|
418
|
+
: String(approval.scheduleStartDate ?? '').match(
|
|
419
|
+
/(\d{4}-\d{2}-\d{2})/
|
|
420
|
+
)?.[1] ||
|
|
421
|
+
approval.submittedAt?.slice(0, 10) ||
|
|
422
|
+
'',
|
|
423
|
+
title: approval.requesterName,
|
|
424
|
+
subtitle: getTargetLabel(approval),
|
|
425
|
+
kind: approval.targetType,
|
|
426
|
+
meta:
|
|
427
|
+
approval.status === 'pending'
|
|
428
|
+
? formatDateTime(approval.submittedAt)
|
|
429
|
+
: formatDateTime(approval.decidedAt),
|
|
430
|
+
statusLabel: getStatusLabel(approval.status),
|
|
431
|
+
badgeClassName: getStatusBadgeClass(approval.status),
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const calendarItems = useMemo<OperationsCalendarItem[]>(
|
|
435
|
+
() =>
|
|
436
|
+
(viewMode === 'calendar' ? calendarApprovals : approvals).map(
|
|
437
|
+
buildCalendarItem
|
|
438
|
+
),
|
|
439
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
440
|
+
[approvals, calendarApprovals, viewMode]
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
const calendarApprovalById = useMemo(
|
|
444
|
+
() => new Map(calendarApprovals.map((a) => [`approval-${a.id}`, a])),
|
|
445
|
+
[calendarApprovals]
|
|
446
|
+
);
|
|
349
447
|
|
|
350
448
|
const handleViewModeChange = (value: string) => {
|
|
351
|
-
if (value !== 'table' && value !== 'cards') return;
|
|
352
|
-
setViewMode(value as 'table' | 'cards');
|
|
449
|
+
if (value !== 'table' && value !== 'cards' && value !== 'calendar') return;
|
|
450
|
+
setViewMode(value as 'table' | 'cards' | 'calendar');
|
|
353
451
|
if (typeof window !== 'undefined') {
|
|
354
452
|
window.localStorage.setItem('operations-approvals-view-mode', value);
|
|
355
453
|
}
|
|
@@ -513,6 +611,26 @@ export default function OperationsApprovalsPage() {
|
|
|
513
611
|
},
|
|
514
612
|
],
|
|
515
613
|
},
|
|
614
|
+
{
|
|
615
|
+
id: 'dateFrom',
|
|
616
|
+
type: 'date',
|
|
617
|
+
value: dateFrom,
|
|
618
|
+
onChange: (value) => {
|
|
619
|
+
setDateFrom(value);
|
|
620
|
+
setPage(1);
|
|
621
|
+
},
|
|
622
|
+
label: commonT('filters.dateFrom'),
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
id: 'dateTo',
|
|
626
|
+
type: 'date',
|
|
627
|
+
value: dateTo,
|
|
628
|
+
onChange: (value) => {
|
|
629
|
+
setDateTo(value);
|
|
630
|
+
setPage(1);
|
|
631
|
+
},
|
|
632
|
+
label: commonT('filters.dateTo'),
|
|
633
|
+
},
|
|
516
634
|
]}
|
|
517
635
|
/>
|
|
518
636
|
</div>
|
|
@@ -536,12 +654,37 @@ export default function OperationsApprovalsPage() {
|
|
|
536
654
|
<LayoutGrid className="h-4 w-4" />
|
|
537
655
|
<span className="hidden sm:inline">{t('viewModeCards')}</span>
|
|
538
656
|
</ToggleGroupItem>
|
|
657
|
+
<ToggleGroupItem value="calendar" className="gap-1.5 px-2.5">
|
|
658
|
+
<CalendarDays className="h-4 w-4" />
|
|
659
|
+
<span className="hidden sm:inline">{t('viewModeCalendar')}</span>
|
|
660
|
+
</ToggleGroupItem>
|
|
539
661
|
</ToggleGroup>
|
|
540
662
|
</div>
|
|
541
663
|
</div>
|
|
542
664
|
|
|
543
|
-
{approvals.length > 0 ? (
|
|
544
|
-
viewMode === '
|
|
665
|
+
{(viewMode === 'calendar' ? true : approvals.length > 0) ? (
|
|
666
|
+
viewMode === 'calendar' ? (
|
|
667
|
+
<OperationsCalendarView
|
|
668
|
+
locale={currentLocaleCode}
|
|
669
|
+
items={calendarItems.filter((item) => item.date)}
|
|
670
|
+
emptyLabel={t('emptyDescription')}
|
|
671
|
+
actionLabel={t('actions.viewDetails')}
|
|
672
|
+
onMonthChange={(year, month) => {
|
|
673
|
+
setCalendarYear(year);
|
|
674
|
+
setCalendarMonth(month);
|
|
675
|
+
}}
|
|
676
|
+
onDateSelect={(_date, items) => {
|
|
677
|
+
const approval = calendarApprovalById.get(String(items[0]?.id));
|
|
678
|
+
if (approval) void openDetails(approval);
|
|
679
|
+
}}
|
|
680
|
+
onOpenItem={(item) => {
|
|
681
|
+
const approval = calendarApprovalById.get(String(item.id));
|
|
682
|
+
if (approval) {
|
|
683
|
+
void openDetails(approval);
|
|
684
|
+
}
|
|
685
|
+
}}
|
|
686
|
+
/>
|
|
687
|
+
) : viewMode === 'cards' ? (
|
|
545
688
|
<div className="grid gap-4 md:grid-cols-2 2xl:grid-cols-3">
|
|
546
689
|
{approvals.map((approval) => (
|
|
547
690
|
<Card
|
|
@@ -792,19 +935,20 @@ export default function OperationsApprovalsPage() {
|
|
|
792
935
|
</span>
|
|
793
936
|
<span className="text-sm text-muted-foreground">
|
|
794
937
|
{selectedApproval.targetType === 'timesheet'
|
|
795
|
-
?
|
|
796
|
-
selectedApproval.timesheetWeekStartDate,
|
|
797
|
-
selectedApproval.timesheetWeekEndDate
|
|
798
|
-
)
|
|
938
|
+
? formatDate(selectedApproval.timesheetWeekStartDate)
|
|
799
939
|
: selectedApproval.targetType === 'time_off_request'
|
|
800
|
-
?
|
|
801
|
-
selectedApproval.timeOffStartDate,
|
|
802
|
-
selectedApproval.timeOffEndDate
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
940
|
+
? [
|
|
941
|
+
formatDate(selectedApproval.timeOffStartDate),
|
|
942
|
+
formatDate(selectedApproval.timeOffEndDate),
|
|
943
|
+
]
|
|
944
|
+
.filter(Boolean)
|
|
945
|
+
.join(' – ')
|
|
946
|
+
: [
|
|
947
|
+
formatDate(selectedApproval.scheduleStartDate),
|
|
948
|
+
formatDate(selectedApproval.scheduleEndDate),
|
|
949
|
+
]
|
|
950
|
+
.filter(Boolean)
|
|
951
|
+
.join(' – ')}
|
|
808
952
|
</span>
|
|
809
953
|
</div>
|
|
810
954
|
</div>
|
|
@@ -863,10 +1007,7 @@ export default function OperationsApprovalsPage() {
|
|
|
863
1007
|
{t('details.period')}
|
|
864
1008
|
</div>
|
|
865
1009
|
<div className="mt-1 text-sm">
|
|
866
|
-
{
|
|
867
|
-
selectedApproval.timesheetWeekStartDate,
|
|
868
|
-
selectedApproval.timesheetWeekEndDate
|
|
869
|
-
)}
|
|
1010
|
+
{formatDate(selectedApproval.timesheetWeekStartDate)}
|
|
870
1011
|
</div>
|
|
871
1012
|
</div>
|
|
872
1013
|
<div>
|
|
@@ -949,15 +1090,17 @@ export default function OperationsApprovalsPage() {
|
|
|
949
1090
|
|
|
950
1091
|
{selectedApproval.targetType === 'time_off_request' ? (
|
|
951
1092
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
952
|
-
<div>
|
|
1093
|
+
<div className="sm:col-span-2">
|
|
953
1094
|
<div className="text-xs font-medium uppercase text-muted-foreground">
|
|
954
1095
|
{t('details.period')}
|
|
955
1096
|
</div>
|
|
956
1097
|
<div className="mt-1 text-sm">
|
|
957
|
-
{
|
|
958
|
-
selectedApproval.timeOffStartDate,
|
|
959
|
-
selectedApproval.timeOffEndDate
|
|
960
|
-
|
|
1098
|
+
{[
|
|
1099
|
+
formatDate(selectedApproval.timeOffStartDate),
|
|
1100
|
+
formatDate(selectedApproval.timeOffEndDate),
|
|
1101
|
+
]
|
|
1102
|
+
.filter(Boolean)
|
|
1103
|
+
.join(' – ') || '—'}
|
|
961
1104
|
</div>
|
|
962
1105
|
</div>
|
|
963
1106
|
<div>
|
|
@@ -983,15 +1126,17 @@ export default function OperationsApprovalsPage() {
|
|
|
983
1126
|
{selectedApproval.targetType === 'schedule_adjustment_request' ? (
|
|
984
1127
|
<>
|
|
985
1128
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
986
|
-
<div>
|
|
1129
|
+
<div className="sm:col-span-2">
|
|
987
1130
|
<div className="text-xs font-medium uppercase text-muted-foreground">
|
|
988
1131
|
{t('details.period')}
|
|
989
1132
|
</div>
|
|
990
1133
|
<div className="mt-1 text-sm">
|
|
991
|
-
{
|
|
992
|
-
selectedApproval.scheduleStartDate,
|
|
993
|
-
selectedApproval.scheduleEndDate
|
|
994
|
-
|
|
1134
|
+
{[
|
|
1135
|
+
formatDate(selectedApproval.scheduleStartDate),
|
|
1136
|
+
formatDate(selectedApproval.scheduleEndDate),
|
|
1137
|
+
]
|
|
1138
|
+
.filter(Boolean)
|
|
1139
|
+
.join(' – ') || '—'}
|
|
995
1140
|
</div>
|
|
996
1141
|
</div>
|
|
997
1142
|
<div>
|
|
@@ -26,17 +26,26 @@ import {
|
|
|
26
26
|
TableRow,
|
|
27
27
|
} from '@/components/ui/table';
|
|
28
28
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
29
|
+
import {
|
|
30
|
+
Tooltip,
|
|
31
|
+
TooltipContent,
|
|
32
|
+
TooltipProvider,
|
|
33
|
+
TooltipTrigger,
|
|
34
|
+
} from '@/components/ui/tooltip';
|
|
29
35
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
30
36
|
import {
|
|
31
37
|
CalendarDays,
|
|
32
38
|
FileText,
|
|
39
|
+
Info,
|
|
33
40
|
LayoutGrid,
|
|
34
41
|
List,
|
|
35
42
|
Pencil,
|
|
36
43
|
Power,
|
|
44
|
+
TrendingUp,
|
|
37
45
|
UserCheck,
|
|
38
46
|
UserRound,
|
|
39
47
|
Users,
|
|
48
|
+
Wallet,
|
|
40
49
|
} from 'lucide-react';
|
|
41
50
|
import { useTranslations } from 'next-intl';
|
|
42
51
|
import Link from 'next/link';
|
|
@@ -264,39 +273,140 @@ export default function OperationsCollaboratorsPage() {
|
|
|
264
273
|
const collaborators = collaboratorsResponse?.data ?? [];
|
|
265
274
|
const filteredRows = collaborators;
|
|
266
275
|
|
|
267
|
-
const statsCards = useMemo(
|
|
268
|
-
() =>
|
|
276
|
+
const statsCards = useMemo(() => {
|
|
277
|
+
const withTooltip = (description: string, tooltip: string) => (
|
|
278
|
+
<TooltipProvider>
|
|
279
|
+
<Tooltip>
|
|
280
|
+
<TooltipTrigger asChild>
|
|
281
|
+
<span className="inline-flex cursor-default items-center gap-1">
|
|
282
|
+
{description}
|
|
283
|
+
<Info className="h-3 w-3 shrink-0 text-muted-foreground/60" />
|
|
284
|
+
</span>
|
|
285
|
+
</TooltipTrigger>
|
|
286
|
+
<TooltipContent side="bottom" className="max-w-64 text-xs">
|
|
287
|
+
{tooltip}
|
|
288
|
+
</TooltipContent>
|
|
289
|
+
</Tooltip>
|
|
290
|
+
</TooltipProvider>
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
return [
|
|
269
294
|
{
|
|
270
295
|
key: 'total',
|
|
271
296
|
title: t('cards.total'),
|
|
272
|
-
description:
|
|
297
|
+
description: withTooltip(
|
|
298
|
+
t('cards.totalDescription'),
|
|
299
|
+
t('cards.totalTooltip')
|
|
300
|
+
),
|
|
273
301
|
value: collaboratorStats?.total ?? 0,
|
|
274
302
|
icon: Users,
|
|
275
303
|
},
|
|
276
304
|
{
|
|
277
305
|
key: 'active',
|
|
278
306
|
title: t('cards.active'),
|
|
279
|
-
description:
|
|
307
|
+
description: withTooltip(
|
|
308
|
+
t('cards.activeDescription'),
|
|
309
|
+
t('cards.activeTooltip')
|
|
310
|
+
),
|
|
280
311
|
value: collaboratorStats?.active ?? 0,
|
|
281
312
|
icon: UserCheck,
|
|
282
313
|
},
|
|
283
314
|
{
|
|
284
315
|
key: 'onLeave',
|
|
285
316
|
title: t('cards.onLeave'),
|
|
286
|
-
description:
|
|
317
|
+
description: withTooltip(
|
|
318
|
+
t('cards.onLeaveDescription'),
|
|
319
|
+
t('cards.onLeaveTooltip')
|
|
320
|
+
),
|
|
287
321
|
value: collaboratorStats?.onLeave ?? 0,
|
|
288
322
|
icon: CalendarDays,
|
|
289
323
|
},
|
|
290
324
|
{
|
|
291
325
|
key: 'withContracts',
|
|
292
326
|
title: t('cards.withContracts'),
|
|
293
|
-
description:
|
|
327
|
+
description: withTooltip(
|
|
328
|
+
t('cards.withContractsDescription'),
|
|
329
|
+
t('cards.withContractsTooltip')
|
|
330
|
+
),
|
|
294
331
|
value: collaboratorStats?.withContracts ?? 0,
|
|
295
332
|
icon: FileText,
|
|
296
333
|
},
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
334
|
+
{
|
|
335
|
+
key: 'totalSalary',
|
|
336
|
+
title: t('cards.totalSalary'),
|
|
337
|
+
description: withTooltip(
|
|
338
|
+
t('cards.totalSalaryDescription'),
|
|
339
|
+
t('cards.totalSalaryTooltip')
|
|
340
|
+
),
|
|
341
|
+
value: formatCurrency(
|
|
342
|
+
collaboratorStats?.totalSalary ?? 0,
|
|
343
|
+
getSettingValue,
|
|
344
|
+
currentLocaleCode
|
|
345
|
+
),
|
|
346
|
+
icon: Wallet,
|
|
347
|
+
accentClassName: 'from-emerald-500/20 via-green-500/10 to-transparent',
|
|
348
|
+
iconContainerClassName:
|
|
349
|
+
'bg-emerald-500/10 text-emerald-700 dark:text-emerald-400',
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
key: 'totalCosts',
|
|
353
|
+
title: t('cards.totalCosts'),
|
|
354
|
+
description: withTooltip(
|
|
355
|
+
t('cards.totalCostsDescription'),
|
|
356
|
+
t('cards.totalCostsTooltip')
|
|
357
|
+
),
|
|
358
|
+
value: formatCurrency(
|
|
359
|
+
collaboratorStats?.totalCosts ?? 0,
|
|
360
|
+
getSettingValue,
|
|
361
|
+
currentLocaleCode
|
|
362
|
+
),
|
|
363
|
+
icon: TrendingUp,
|
|
364
|
+
accentClassName: 'from-amber-500/20 via-yellow-500/10 to-transparent',
|
|
365
|
+
iconContainerClassName:
|
|
366
|
+
'bg-amber-500/10 text-amber-700 dark:text-amber-400',
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
key: 'avgSalaryPlusCosts',
|
|
370
|
+
title: t('cards.avgSalaryPlusCosts'),
|
|
371
|
+
description: withTooltip(
|
|
372
|
+
t('cards.avgSalaryPlusCostsDescription'),
|
|
373
|
+
t('cards.avgSalaryPlusCostsTooltip')
|
|
374
|
+
),
|
|
375
|
+
value: formatCurrency(
|
|
376
|
+
collaboratorStats?.avgSalaryPlusCosts ?? 0,
|
|
377
|
+
getSettingValue,
|
|
378
|
+
currentLocaleCode
|
|
379
|
+
),
|
|
380
|
+
icon: Wallet,
|
|
381
|
+
accentClassName: 'from-violet-500/20 via-purple-500/10 to-transparent',
|
|
382
|
+
iconContainerClassName:
|
|
383
|
+
'bg-violet-500/10 text-violet-700 dark:text-violet-400',
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
key: 'avgSalaryPlusCostsPerCollaborator',
|
|
387
|
+
title: t('cards.avgSalaryPlusCostsPerCollaborator'),
|
|
388
|
+
description: withTooltip(
|
|
389
|
+
t('cards.avgSalaryPlusCostsPerCollaboratorDescription'),
|
|
390
|
+
t('cards.avgSalaryPlusCostsPerCollaboratorTooltip')
|
|
391
|
+
),
|
|
392
|
+
value: formatCurrency(
|
|
393
|
+
collaboratorStats?.avgSalaryPlusCostsPerCollaborator ?? 0,
|
|
394
|
+
getSettingValue,
|
|
395
|
+
currentLocaleCode
|
|
396
|
+
),
|
|
397
|
+
icon: TrendingUp,
|
|
398
|
+
accentClassName: 'from-rose-500/20 via-pink-500/10 to-transparent',
|
|
399
|
+
iconContainerClassName:
|
|
400
|
+
'bg-rose-500/10 text-rose-700 dark:text-rose-400',
|
|
401
|
+
},
|
|
402
|
+
];
|
|
403
|
+
}, [
|
|
404
|
+
collaboratorStats,
|
|
405
|
+
t,
|
|
406
|
+
formatCurrency,
|
|
407
|
+
getSettingValue,
|
|
408
|
+
currentLocaleCode,
|
|
409
|
+
]);
|
|
300
410
|
|
|
301
411
|
const handleViewModeChange = (value: string) => {
|
|
302
412
|
if (value !== 'table' && value !== 'cards') {
|
|
@@ -533,7 +643,7 @@ export default function OperationsCollaboratorsPage() {
|
|
|
533
643
|
</div>
|
|
534
644
|
) : null}
|
|
535
645
|
|
|
536
|
-
<div className="flex
|
|
646
|
+
<div className="flex items-center justify-end gap-1.5 border-t border-border/60 pt-3">
|
|
537
647
|
{access.isDirector ? (
|
|
538
648
|
<Button
|
|
539
649
|
variant="outline"
|
|
@@ -564,16 +674,15 @@ export default function OperationsCollaboratorsPage() {
|
|
|
564
674
|
{access.isDirector ? (
|
|
565
675
|
<Button
|
|
566
676
|
variant="outline"
|
|
567
|
-
size="
|
|
568
|
-
className="gap-1.5 px-2 sm:px-3"
|
|
677
|
+
size="icon"
|
|
569
678
|
onClick={() => void toggleStatus(collaborator)}
|
|
679
|
+
title={
|
|
680
|
+
collaborator.status === 'inactive'
|
|
681
|
+
? commonT('actions.activate')
|
|
682
|
+
: commonT('actions.deactivate')
|
|
683
|
+
}
|
|
570
684
|
>
|
|
571
685
|
<Power className="size-4" />
|
|
572
|
-
<span className="hidden sm:inline">
|
|
573
|
-
{collaborator.status === 'inactive'
|
|
574
|
-
? commonT('actions.activate')
|
|
575
|
-
: commonT('actions.deactivate')}
|
|
576
|
-
</span>
|
|
577
686
|
</Button>
|
|
578
687
|
) : null}
|
|
579
688
|
</div>
|
|
@@ -718,7 +827,7 @@ export default function OperationsCollaboratorsPage() {
|
|
|
718
827
|
)}
|
|
719
828
|
</TableCell>
|
|
720
829
|
<TableCell>
|
|
721
|
-
<div className="flex
|
|
830
|
+
<div className="flex items-center justify-end gap-1.5">
|
|
722
831
|
{access.isDirector ? (
|
|
723
832
|
<Button
|
|
724
833
|
variant="outline"
|
|
@@ -749,16 +858,15 @@ export default function OperationsCollaboratorsPage() {
|
|
|
749
858
|
{access.isDirector ? (
|
|
750
859
|
<Button
|
|
751
860
|
variant="outline"
|
|
752
|
-
size="
|
|
753
|
-
className="gap-1.5 px-2 sm:px-3"
|
|
861
|
+
size="icon"
|
|
754
862
|
onClick={() => void toggleStatus(collaborator)}
|
|
863
|
+
title={
|
|
864
|
+
collaborator.status === 'inactive'
|
|
865
|
+
? commonT('actions.activate')
|
|
866
|
+
: commonT('actions.deactivate')
|
|
867
|
+
}
|
|
755
868
|
>
|
|
756
869
|
<Power className="size-4" />
|
|
757
|
-
<span className="hidden sm:inline">
|
|
758
|
-
{collaborator.status === 'inactive'
|
|
759
|
-
? commonT('actions.activate')
|
|
760
|
-
: commonT('actions.deactivate')}
|
|
761
|
-
</span>
|
|
762
870
|
</Button>
|
|
763
871
|
) : null}
|
|
764
872
|
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MyProjectSummaryScreen } from '../../_components/my-project-summary-screen';
|
|
2
|
+
|
|
3
|
+
export default async function MyProjectSummaryPage({
|
|
4
|
+
params,
|
|
5
|
+
}: {
|
|
6
|
+
params: Promise<{ id: string }>;
|
|
7
|
+
}) {
|
|
8
|
+
const { id } = await params;
|
|
9
|
+
|
|
10
|
+
return <MyProjectSummaryScreen projectId={Number(id)} />;
|
|
11
|
+
}
|