@hed-hog/operations 0.0.325 → 0.0.326
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 +5 -0
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
- package/dist/operations.service.d.ts +9 -1
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +140 -26
- package/dist/operations.service.js.map +1 -1
- package/hedhog/data/integration_event_catalog.yaml +313 -0
- package/hedhog/data/setting_group.yaml +21 -0
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +410 -23
- package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +504 -375
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +258 -230
- package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +225 -162
- package/hedhog/frontend/app/_components/task-form-sheet.tsx.ejs +484 -230
- package/hedhog/frontend/app/_lib/api.ts.ejs +13 -4
- package/hedhog/frontend/app/_lib/hooks/use-mention-items.ts.ejs +28 -0
- package/hedhog/frontend/app/_lib/types.ts.ejs +30 -29
- package/hedhog/frontend/app/my-tasks/page.tsx.ejs +347 -236
- package/hedhog/frontend/app/reports/projects/page.tsx.ejs +31 -7
- package/hedhog/frontend/messages/en.json +38 -55
- package/hedhog/frontend/messages/en.json.ejs +21 -4
- package/hedhog/frontend/messages/pt.json +36 -55
- package/hedhog/frontend/messages/pt.json.ejs +14 -3
- package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/types.d.ts +1 -0
- package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/types.d.ts.map +1 -1
- package/hedhog/frontend/src/app/(app)/(libraries)/operations/_lib/types.ts +1 -0
- package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/types.d.ts +1 -0
- package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/types.d.ts.map +1 -1
- package/hedhog/frontend/src/app/(app)/(libraries)/operations/operations/_lib/types.ts +1 -0
- package/hedhog/table/operations_collaborator.yaml +5 -0
- package/hedhog/table/operations_collaborator_compensation_history.yaml +4 -0
- package/package.json +5 -5
- package/src/operations.service.ts +202 -26
|
@@ -41,6 +41,14 @@ import {
|
|
|
41
41
|
SelectValue,
|
|
42
42
|
} from '@/components/ui/select';
|
|
43
43
|
import { Switch } from '@/components/ui/switch';
|
|
44
|
+
import {
|
|
45
|
+
Table,
|
|
46
|
+
TableBody,
|
|
47
|
+
TableCell,
|
|
48
|
+
TableHead,
|
|
49
|
+
TableHeader,
|
|
50
|
+
TableRow,
|
|
51
|
+
} from '@/components/ui/table';
|
|
44
52
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
45
53
|
import { Textarea } from '@/components/ui/textarea';
|
|
46
54
|
import {
|
|
@@ -62,6 +70,16 @@ import { useTranslations } from 'next-intl';
|
|
|
62
70
|
import Link from 'next/link';
|
|
63
71
|
import { useRouter } from 'next/navigation';
|
|
64
72
|
import { useEffect, useMemo, useState } from 'react';
|
|
73
|
+
import {
|
|
74
|
+
CartesianGrid,
|
|
75
|
+
Legend,
|
|
76
|
+
Line,
|
|
77
|
+
LineChart,
|
|
78
|
+
ResponsiveContainer,
|
|
79
|
+
Tooltip as RechartsTooltip,
|
|
80
|
+
XAxis,
|
|
81
|
+
YAxis,
|
|
82
|
+
} from 'recharts';
|
|
65
83
|
import { PersonPicker } from '../../contact/_components/person-picker';
|
|
66
84
|
import {
|
|
67
85
|
fetchOperations,
|
|
@@ -80,6 +98,7 @@ import type {
|
|
|
80
98
|
PaginatedResponse,
|
|
81
99
|
} from '../_lib/types';
|
|
82
100
|
import {
|
|
101
|
+
formatCurrency,
|
|
83
102
|
formatDate,
|
|
84
103
|
formatEnumLabel,
|
|
85
104
|
formatHours,
|
|
@@ -229,6 +248,8 @@ const EQUITY_ENABLED_TYPE_SLUGS = new Set([
|
|
|
229
248
|
'parceiro',
|
|
230
249
|
]);
|
|
231
250
|
|
|
251
|
+
const HOURLY_RATE_SLUGS = new Set(['pj', 'freelancer']);
|
|
252
|
+
|
|
232
253
|
type CollaboratorFormState = {
|
|
233
254
|
userId: string;
|
|
234
255
|
personId: string;
|
|
@@ -244,6 +265,9 @@ type CollaboratorFormState = {
|
|
|
244
265
|
leftAt: string;
|
|
245
266
|
supervisorCollaboratorId: string;
|
|
246
267
|
compensationAmount: string;
|
|
268
|
+
hourlyRate: string;
|
|
269
|
+
compensationEffectiveDate: string;
|
|
270
|
+
compensationNotes: string;
|
|
247
271
|
contractId: string;
|
|
248
272
|
notes: string;
|
|
249
273
|
equityParticipation: {
|
|
@@ -289,6 +313,9 @@ function buildEmptyForm(): CollaboratorFormState {
|
|
|
289
313
|
leftAt: '',
|
|
290
314
|
supervisorCollaboratorId: 'none',
|
|
291
315
|
compensationAmount: '',
|
|
316
|
+
hourlyRate: '',
|
|
317
|
+
compensationEffectiveDate: '',
|
|
318
|
+
compensationNotes: '',
|
|
292
319
|
contractId: '',
|
|
293
320
|
notes: '',
|
|
294
321
|
equityParticipation: {
|
|
@@ -362,6 +389,12 @@ function toFormState(
|
|
|
362
389
|
collaborator.relatedContracts?.[0]?.budgetAmount !== undefined
|
|
363
390
|
? String(collaborator.relatedContracts[0].budgetAmount)
|
|
364
391
|
: '',
|
|
392
|
+
hourlyRate:
|
|
393
|
+
collaborator.hourlyRate !== null && collaborator.hourlyRate !== undefined
|
|
394
|
+
? String(collaborator.hourlyRate)
|
|
395
|
+
: '',
|
|
396
|
+
compensationEffectiveDate: '',
|
|
397
|
+
compensationNotes: '',
|
|
365
398
|
contractId: collaborator.relatedContracts?.[0]?.id
|
|
366
399
|
? String(collaborator.relatedContracts[0].id)
|
|
367
400
|
: '',
|
|
@@ -561,7 +594,8 @@ export function CollaboratorFormScreen({
|
|
|
561
594
|
const t = useTranslations('operations.CollaboratorFormPage');
|
|
562
595
|
const commonT = useTranslations('operations.Common');
|
|
563
596
|
const detailsT = useTranslations('operations.CollaboratorDetailsPage');
|
|
564
|
-
const { request, showToastHandler, currentLocaleCode } =
|
|
597
|
+
const { request, showToastHandler, currentLocaleCode, getSettingValue } =
|
|
598
|
+
useApp();
|
|
565
599
|
const access = useOperationsAccess();
|
|
566
600
|
const router = useRouter();
|
|
567
601
|
const [form, setForm] = useState<CollaboratorFormState>(buildEmptyForm());
|
|
@@ -706,6 +740,34 @@ export function CollaboratorFormScreen({
|
|
|
706
740
|
placeholderData: (previous) => previous ?? [],
|
|
707
741
|
});
|
|
708
742
|
|
|
743
|
+
type CompensationHistoryEntry = {
|
|
744
|
+
id: number;
|
|
745
|
+
collaboratorId: number;
|
|
746
|
+
amount: string;
|
|
747
|
+
effectiveDate: string | null;
|
|
748
|
+
actorUserId: number | null;
|
|
749
|
+
actorName: string | null;
|
|
750
|
+
notes: string | null;
|
|
751
|
+
createdAt: string;
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
const {
|
|
755
|
+
data: compensationHistory = [],
|
|
756
|
+
refetch: refetchCompensationHistory,
|
|
757
|
+
} = useQuery<CompensationHistoryEntry[]>({
|
|
758
|
+
queryKey: [
|
|
759
|
+
'operations-collaborator-compensation-history',
|
|
760
|
+
currentLocaleCode,
|
|
761
|
+
collaboratorId,
|
|
762
|
+
],
|
|
763
|
+
enabled: !isCreateMode && access.isDirector,
|
|
764
|
+
queryFn: () =>
|
|
765
|
+
fetchOperations<CompensationHistoryEntry[]>(
|
|
766
|
+
request,
|
|
767
|
+
`/operations/collaborators/${collaboratorId}/compensation-history`
|
|
768
|
+
),
|
|
769
|
+
});
|
|
770
|
+
|
|
709
771
|
const { data: documentTypes = [] } = useQuery<DocumentTypeOption[]>({
|
|
710
772
|
queryKey: ['contact-person-document-types', currentLocaleCode],
|
|
711
773
|
enabled: personSheetOpen,
|
|
@@ -826,6 +888,10 @@ export function CollaboratorFormScreen({
|
|
|
826
888
|
EQUITY_ENABLED_TYPE_SLUGS.has(selectedCollaboratorType.slug)
|
|
827
889
|
);
|
|
828
890
|
|
|
891
|
+
const isHourlyType = HOURLY_RATE_SLUGS.has(
|
|
892
|
+
selectedCollaboratorType?.slug ?? ''
|
|
893
|
+
);
|
|
894
|
+
|
|
829
895
|
const getCollaboratorTypeLabel = (
|
|
830
896
|
value?: string | null,
|
|
831
897
|
fallbackName?: string | null
|
|
@@ -1017,7 +1083,10 @@ export function CollaboratorFormScreen({
|
|
|
1017
1083
|
const onSubmit = async () => {
|
|
1018
1084
|
const userId = parseNumberInput(form.userId);
|
|
1019
1085
|
const personId = parseNumberInput(form.personId);
|
|
1020
|
-
const compensationAmount =
|
|
1086
|
+
const compensationAmount = isHourlyType
|
|
1087
|
+
? null
|
|
1088
|
+
: parseNumberInput(form.compensationAmount);
|
|
1089
|
+
const hourlyRate = isHourlyType ? parseNumberInput(form.hourlyRate) : null;
|
|
1021
1090
|
|
|
1022
1091
|
if (!personId) {
|
|
1023
1092
|
showToastHandler?.('error', t('messages.personRequired'));
|
|
@@ -1052,6 +1121,9 @@ export function CollaboratorFormScreen({
|
|
|
1052
1121
|
: parseNumberInput(form.supervisorCollaboratorId),
|
|
1053
1122
|
compensationAmount:
|
|
1054
1123
|
compensationAmount !== null ? compensationAmount : null,
|
|
1124
|
+
hourlyRate: hourlyRate !== null ? hourlyRate : null,
|
|
1125
|
+
compensationEffectiveDate: trimToNull(form.compensationEffectiveDate),
|
|
1126
|
+
compensationNotes: trimToNull(form.compensationNotes),
|
|
1055
1127
|
contractId: parseNumberInput(form.contractId),
|
|
1056
1128
|
notes: trimToNull(form.notes),
|
|
1057
1129
|
equityParticipation: shouldShowEquitySection
|
|
@@ -1092,10 +1164,16 @@ export function CollaboratorFormScreen({
|
|
|
1092
1164
|
payload
|
|
1093
1165
|
);
|
|
1094
1166
|
|
|
1095
|
-
setForm(
|
|
1167
|
+
setForm((prev) => ({
|
|
1168
|
+
...toFormState(response),
|
|
1169
|
+
compensationEffectiveDate: '',
|
|
1170
|
+
compensationNotes: '',
|
|
1171
|
+
weeklySchedule: prev.weeklySchedule,
|
|
1172
|
+
}));
|
|
1096
1173
|
|
|
1097
1174
|
await Promise.all([
|
|
1098
1175
|
collaboratorId ? refetchCollaborator() : Promise.resolve(),
|
|
1176
|
+
collaboratorId ? refetchCompensationHistory() : Promise.resolve(),
|
|
1099
1177
|
refetchCollaboratorTypes(),
|
|
1100
1178
|
refetchDepartments(),
|
|
1101
1179
|
refetchCollaborators(),
|
|
@@ -1590,21 +1668,70 @@ export function CollaboratorFormScreen({
|
|
|
1590
1668
|
}
|
|
1591
1669
|
/>
|
|
1592
1670
|
</div>
|
|
1671
|
+
{isHourlyType ? (
|
|
1672
|
+
<div className="space-y-2">
|
|
1673
|
+
<label className="text-sm font-medium">
|
|
1674
|
+
{t('fields.hourlyRate')}
|
|
1675
|
+
</label>
|
|
1676
|
+
<InputMoney
|
|
1677
|
+
step="0.01"
|
|
1678
|
+
value={form.hourlyRate === '' ? '' : Number(form.hourlyRate)}
|
|
1679
|
+
onValueChange={(value) =>
|
|
1680
|
+
setForm((current) => ({
|
|
1681
|
+
...current,
|
|
1682
|
+
hourlyRate: value !== null ? String(value) : '',
|
|
1683
|
+
}))
|
|
1684
|
+
}
|
|
1685
|
+
/>
|
|
1686
|
+
</div>
|
|
1687
|
+
) : (
|
|
1688
|
+
<div className="space-y-2">
|
|
1689
|
+
<label className="text-sm font-medium">
|
|
1690
|
+
{t('fields.compensationAmount')}
|
|
1691
|
+
</label>
|
|
1692
|
+
<InputMoney
|
|
1693
|
+
step="0.01"
|
|
1694
|
+
value={
|
|
1695
|
+
form.compensationAmount === ''
|
|
1696
|
+
? ''
|
|
1697
|
+
: Number(form.compensationAmount)
|
|
1698
|
+
}
|
|
1699
|
+
onValueChange={(value) =>
|
|
1700
|
+
setForm((current) => ({
|
|
1701
|
+
...current,
|
|
1702
|
+
compensationAmount: value !== null ? String(value) : '',
|
|
1703
|
+
}))
|
|
1704
|
+
}
|
|
1705
|
+
/>
|
|
1706
|
+
</div>
|
|
1707
|
+
)}
|
|
1593
1708
|
<div className="space-y-2">
|
|
1594
1709
|
<label className="text-sm font-medium">
|
|
1595
|
-
{t('fields.
|
|
1710
|
+
{t('fields.compensationEffectiveDate')}
|
|
1596
1711
|
</label>
|
|
1597
|
-
<
|
|
1598
|
-
|
|
1599
|
-
value={
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1712
|
+
<Input
|
|
1713
|
+
type="date"
|
|
1714
|
+
value={form.compensationEffectiveDate}
|
|
1715
|
+
onChange={(event) =>
|
|
1716
|
+
setForm((current) => ({
|
|
1717
|
+
...current,
|
|
1718
|
+
compensationEffectiveDate: event.target.value,
|
|
1719
|
+
}))
|
|
1603
1720
|
}
|
|
1604
|
-
|
|
1721
|
+
/>
|
|
1722
|
+
</div>
|
|
1723
|
+
<div className="space-y-2">
|
|
1724
|
+
<label className="text-sm font-medium">
|
|
1725
|
+
{t('fields.compensationNotes')}
|
|
1726
|
+
</label>
|
|
1727
|
+
<Textarea
|
|
1728
|
+
rows={2}
|
|
1729
|
+
value={form.compensationNotes}
|
|
1730
|
+
placeholder={t('placeholders.compensationNotes')}
|
|
1731
|
+
onChange={(event) =>
|
|
1605
1732
|
setForm((current) => ({
|
|
1606
1733
|
...current,
|
|
1607
|
-
|
|
1734
|
+
compensationNotes: event.target.value,
|
|
1608
1735
|
}))
|
|
1609
1736
|
}
|
|
1610
1737
|
/>
|
|
@@ -1780,21 +1907,70 @@ export function CollaboratorFormScreen({
|
|
|
1780
1907
|
}
|
|
1781
1908
|
/>
|
|
1782
1909
|
</div>
|
|
1910
|
+
{isHourlyType ? (
|
|
1911
|
+
<div className="space-y-2">
|
|
1912
|
+
<label className="text-sm font-medium">
|
|
1913
|
+
{t('fields.hourlyRate')}
|
|
1914
|
+
</label>
|
|
1915
|
+
<InputMoney
|
|
1916
|
+
step="0.01"
|
|
1917
|
+
value={form.hourlyRate === '' ? '' : Number(form.hourlyRate)}
|
|
1918
|
+
onValueChange={(value) =>
|
|
1919
|
+
setForm((current) => ({
|
|
1920
|
+
...current,
|
|
1921
|
+
hourlyRate: value !== null ? String(value) : '',
|
|
1922
|
+
}))
|
|
1923
|
+
}
|
|
1924
|
+
/>
|
|
1925
|
+
</div>
|
|
1926
|
+
) : (
|
|
1927
|
+
<div className="space-y-2">
|
|
1928
|
+
<label className="text-sm font-medium">
|
|
1929
|
+
{t('fields.compensationAmount')}
|
|
1930
|
+
</label>
|
|
1931
|
+
<InputMoney
|
|
1932
|
+
step="0.01"
|
|
1933
|
+
value={
|
|
1934
|
+
form.compensationAmount === ''
|
|
1935
|
+
? ''
|
|
1936
|
+
: Number(form.compensationAmount)
|
|
1937
|
+
}
|
|
1938
|
+
onValueChange={(value) =>
|
|
1939
|
+
setForm((current) => ({
|
|
1940
|
+
...current,
|
|
1941
|
+
compensationAmount: value !== null ? String(value) : '',
|
|
1942
|
+
}))
|
|
1943
|
+
}
|
|
1944
|
+
/>
|
|
1945
|
+
</div>
|
|
1946
|
+
)}
|
|
1783
1947
|
<div className="space-y-2">
|
|
1784
1948
|
<label className="text-sm font-medium">
|
|
1785
|
-
{t('fields.
|
|
1949
|
+
{t('fields.compensationEffectiveDate')}
|
|
1786
1950
|
</label>
|
|
1787
|
-
<
|
|
1788
|
-
|
|
1789
|
-
value={
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1951
|
+
<Input
|
|
1952
|
+
type="date"
|
|
1953
|
+
value={form.compensationEffectiveDate}
|
|
1954
|
+
onChange={(event) =>
|
|
1955
|
+
setForm((current) => ({
|
|
1956
|
+
...current,
|
|
1957
|
+
compensationEffectiveDate: event.target.value,
|
|
1958
|
+
}))
|
|
1793
1959
|
}
|
|
1794
|
-
|
|
1960
|
+
/>
|
|
1961
|
+
</div>
|
|
1962
|
+
<div className="space-y-2">
|
|
1963
|
+
<label className="text-sm font-medium">
|
|
1964
|
+
{t('fields.compensationNotes')}
|
|
1965
|
+
</label>
|
|
1966
|
+
<Textarea
|
|
1967
|
+
rows={2}
|
|
1968
|
+
value={form.compensationNotes}
|
|
1969
|
+
placeholder={t('placeholders.compensationNotes')}
|
|
1970
|
+
onChange={(event) =>
|
|
1795
1971
|
setForm((current) => ({
|
|
1796
1972
|
...current,
|
|
1797
|
-
|
|
1973
|
+
compensationNotes: event.target.value,
|
|
1798
1974
|
}))
|
|
1799
1975
|
}
|
|
1800
1976
|
/>
|
|
@@ -1952,6 +2128,58 @@ export function CollaboratorFormScreen({
|
|
|
1952
2128
|
</div>
|
|
1953
2129
|
);
|
|
1954
2130
|
|
|
2131
|
+
const salaryChartData = useMemo(() => {
|
|
2132
|
+
if (!compensationHistory.length) return [];
|
|
2133
|
+
|
|
2134
|
+
const sorted = [...compensationHistory].sort(
|
|
2135
|
+
(a, b) =>
|
|
2136
|
+
new Date(a.effectiveDate ?? a.createdAt).getTime() -
|
|
2137
|
+
new Date(b.effectiveDate ?? b.createdAt).getTime()
|
|
2138
|
+
);
|
|
2139
|
+
|
|
2140
|
+
const historicalPoints = sorted.map((entry) => ({
|
|
2141
|
+
date: (entry.effectiveDate ?? entry.createdAt).slice(0, 10),
|
|
2142
|
+
valor: Number(entry.amount),
|
|
2143
|
+
projecao: null as number | null,
|
|
2144
|
+
}));
|
|
2145
|
+
|
|
2146
|
+
const lastEntry = sorted[sorted.length - 1];
|
|
2147
|
+
if (!lastEntry) return [];
|
|
2148
|
+
const lastAmount = Number(lastEntry.amount);
|
|
2149
|
+
const prevEntry = sorted.length > 1 ? sorted[sorted.length - 2] : null;
|
|
2150
|
+
const growthRate =
|
|
2151
|
+
prevEntry && Number(prevEntry.amount) > 0
|
|
2152
|
+
? (lastAmount - Number(prevEntry.amount)) / Number(prevEntry.amount)
|
|
2153
|
+
: 0;
|
|
2154
|
+
|
|
2155
|
+
const projectionPoints: { date: string; valor: null; projecao: number }[] =
|
|
2156
|
+
[];
|
|
2157
|
+
const baseDate = new Date(
|
|
2158
|
+
(lastEntry.effectiveDate ?? lastEntry.createdAt).slice(0, 10) +
|
|
2159
|
+
'T00:00:00'
|
|
2160
|
+
);
|
|
2161
|
+
|
|
2162
|
+
for (let i = 1; i <= 12; i++) {
|
|
2163
|
+
const d = new Date(baseDate);
|
|
2164
|
+
d.setMonth(d.getMonth() + i);
|
|
2165
|
+
projectionPoints.push({
|
|
2166
|
+
date: d.toISOString().slice(0, 10),
|
|
2167
|
+
valor: null,
|
|
2168
|
+
projecao:
|
|
2169
|
+
Math.round(lastAmount * Math.pow(1 + growthRate, i / 12) * 100) / 100,
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
return [
|
|
2174
|
+
...historicalPoints,
|
|
2175
|
+
{
|
|
2176
|
+
...historicalPoints[historicalPoints.length - 1],
|
|
2177
|
+
projecao: lastAmount,
|
|
2178
|
+
},
|
|
2179
|
+
...projectionPoints,
|
|
2180
|
+
];
|
|
2181
|
+
}, [compensationHistory]);
|
|
2182
|
+
|
|
1955
2183
|
const costsSection = (
|
|
1956
2184
|
<Accordion
|
|
1957
2185
|
type="single"
|
|
@@ -1986,13 +2214,22 @@ export function CollaboratorFormScreen({
|
|
|
1986
2214
|
|
|
1987
2215
|
const formContent = isSheetMode ? (
|
|
1988
2216
|
<Tabs defaultValue="details" className="w-full">
|
|
1989
|
-
<TabsList
|
|
2217
|
+
<TabsList
|
|
2218
|
+
className={`mx-4 mb-2 grid w-[calc(100%-2rem)] ${!isCreateMode && !isHourlyType ? 'grid-cols-7' : isCreateMode && isHourlyType ? 'grid-cols-5' : 'grid-cols-6'}`}
|
|
2219
|
+
>
|
|
1990
2220
|
<TabsTrigger value="details">{t('tabs.details')}</TabsTrigger>
|
|
1991
2221
|
<TabsTrigger value="costs">{t('tabs.costs')}</TabsTrigger>
|
|
1992
|
-
|
|
2222
|
+
{!isHourlyType ? (
|
|
2223
|
+
<TabsTrigger value="schedule">
|
|
2224
|
+
{t('tabs.defaultSchedule')}
|
|
2225
|
+
</TabsTrigger>
|
|
2226
|
+
) : null}
|
|
1993
2227
|
<TabsTrigger value="projects">{t('tabs.projects')}</TabsTrigger>
|
|
1994
2228
|
<TabsTrigger value="tasks">{t('tabs.tasks')}</TabsTrigger>
|
|
1995
2229
|
<TabsTrigger value="timesheets">{t('tabs.timesheets')}</TabsTrigger>
|
|
2230
|
+
{!isCreateMode ? (
|
|
2231
|
+
<TabsTrigger value="salary">{t('tabs.salary')}</TabsTrigger>
|
|
2232
|
+
) : null}
|
|
1996
2233
|
</TabsList>
|
|
1997
2234
|
<TabsContent value="details" className="mt-0 space-y-4 px-4 pt-2">
|
|
1998
2235
|
{profileContent}
|
|
@@ -2060,6 +2297,156 @@ export function CollaboratorFormScreen({
|
|
|
2060
2297
|
/>
|
|
2061
2298
|
)}
|
|
2062
2299
|
</TabsContent>
|
|
2300
|
+
{!isCreateMode ? (
|
|
2301
|
+
<TabsContent value="salary" className="mt-0 px-4 pt-2 space-y-6">
|
|
2302
|
+
{access.isDirector ? (
|
|
2303
|
+
<>
|
|
2304
|
+
<div>
|
|
2305
|
+
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground mb-3">
|
|
2306
|
+
{isHourlyType
|
|
2307
|
+
? t('chart.hourlyHistory')
|
|
2308
|
+
: t('chart.salaryHistory')}
|
|
2309
|
+
</h3>
|
|
2310
|
+
{salaryChartData.length > 0 ? (
|
|
2311
|
+
<ResponsiveContainer width="100%" height={260}>
|
|
2312
|
+
<LineChart
|
|
2313
|
+
data={salaryChartData}
|
|
2314
|
+
margin={{ top: 8, right: 16, left: 8, bottom: 8 }}
|
|
2315
|
+
>
|
|
2316
|
+
<CartesianGrid
|
|
2317
|
+
strokeDasharray="3 3"
|
|
2318
|
+
className="stroke-muted"
|
|
2319
|
+
/>
|
|
2320
|
+
<XAxis
|
|
2321
|
+
dataKey="date"
|
|
2322
|
+
tick={{ fontSize: 11 }}
|
|
2323
|
+
tickFormatter={(v: string) => {
|
|
2324
|
+
const [year, month] = v.split('-');
|
|
2325
|
+
return `${month}/${year?.slice(2)}`;
|
|
2326
|
+
}}
|
|
2327
|
+
/>
|
|
2328
|
+
<YAxis
|
|
2329
|
+
tick={{ fontSize: 11 }}
|
|
2330
|
+
tickFormatter={(v: number) =>
|
|
2331
|
+
formatCurrency(v, getSettingValue, currentLocaleCode)
|
|
2332
|
+
}
|
|
2333
|
+
width={90}
|
|
2334
|
+
/>
|
|
2335
|
+
<RechartsTooltip
|
|
2336
|
+
formatter={(value: number) => [
|
|
2337
|
+
formatCurrency(
|
|
2338
|
+
value,
|
|
2339
|
+
getSettingValue,
|
|
2340
|
+
currentLocaleCode
|
|
2341
|
+
),
|
|
2342
|
+
]}
|
|
2343
|
+
labelFormatter={(label: string) => label}
|
|
2344
|
+
/>
|
|
2345
|
+
<Legend />
|
|
2346
|
+
<Line
|
|
2347
|
+
type="monotone"
|
|
2348
|
+
dataKey="valor"
|
|
2349
|
+
name={t('chart.actual')}
|
|
2350
|
+
stroke="hsl(var(--primary))"
|
|
2351
|
+
strokeWidth={2}
|
|
2352
|
+
dot={{ r: 4 }}
|
|
2353
|
+
activeDot={{ r: 6 }}
|
|
2354
|
+
isAnimationActive
|
|
2355
|
+
animationDuration={800}
|
|
2356
|
+
connectNulls={false}
|
|
2357
|
+
/>
|
|
2358
|
+
<Line
|
|
2359
|
+
type="monotone"
|
|
2360
|
+
dataKey="projecao"
|
|
2361
|
+
name={t('chart.projection')}
|
|
2362
|
+
stroke="hsl(var(--muted-foreground))"
|
|
2363
|
+
strokeWidth={2}
|
|
2364
|
+
strokeDasharray="5 5"
|
|
2365
|
+
dot={false}
|
|
2366
|
+
isAnimationActive
|
|
2367
|
+
animationDuration={800}
|
|
2368
|
+
connectNulls={false}
|
|
2369
|
+
/>
|
|
2370
|
+
</LineChart>
|
|
2371
|
+
</ResponsiveContainer>
|
|
2372
|
+
) : (
|
|
2373
|
+
<p className="text-sm text-muted-foreground">
|
|
2374
|
+
{detailsT('noCompensationHistory')}
|
|
2375
|
+
</p>
|
|
2376
|
+
)}
|
|
2377
|
+
</div>
|
|
2378
|
+
<div>
|
|
2379
|
+
{compensationHistory.length > 0 ? (
|
|
2380
|
+
<div className="overflow-x-auto rounded-md border">
|
|
2381
|
+
<Table>
|
|
2382
|
+
<TableHeader>
|
|
2383
|
+
<TableRow>
|
|
2384
|
+
<TableHead>
|
|
2385
|
+
{detailsT('compensationHistory.amount')}
|
|
2386
|
+
</TableHead>
|
|
2387
|
+
<TableHead>
|
|
2388
|
+
{detailsT('compensationHistory.effectiveDate')}
|
|
2389
|
+
</TableHead>
|
|
2390
|
+
<TableHead>
|
|
2391
|
+
{detailsT('compensationHistory.recordedAt')}
|
|
2392
|
+
</TableHead>
|
|
2393
|
+
<TableHead>
|
|
2394
|
+
{detailsT('compensationHistory.changedBy')}
|
|
2395
|
+
</TableHead>
|
|
2396
|
+
<TableHead>
|
|
2397
|
+
{detailsT('compensationHistory.notes')}
|
|
2398
|
+
</TableHead>
|
|
2399
|
+
</TableRow>
|
|
2400
|
+
</TableHeader>
|
|
2401
|
+
<TableBody>
|
|
2402
|
+
{compensationHistory.map((entry) => (
|
|
2403
|
+
<TableRow key={entry.id}>
|
|
2404
|
+
<TableCell className="font-medium">
|
|
2405
|
+
{formatCurrency(
|
|
2406
|
+
Number(entry.amount),
|
|
2407
|
+
getSettingValue,
|
|
2408
|
+
currentLocaleCode
|
|
2409
|
+
)}
|
|
2410
|
+
</TableCell>
|
|
2411
|
+
<TableCell>
|
|
2412
|
+
{entry.effectiveDate
|
|
2413
|
+
? formatDate(
|
|
2414
|
+
entry.effectiveDate,
|
|
2415
|
+
getSettingValue,
|
|
2416
|
+
currentLocaleCode
|
|
2417
|
+
)
|
|
2418
|
+
: '—'}
|
|
2419
|
+
</TableCell>
|
|
2420
|
+
<TableCell>
|
|
2421
|
+
{formatDate(
|
|
2422
|
+
entry.createdAt,
|
|
2423
|
+
getSettingValue,
|
|
2424
|
+
currentLocaleCode
|
|
2425
|
+
)}
|
|
2426
|
+
</TableCell>
|
|
2427
|
+
<TableCell>{entry.actorName ?? '—'}</TableCell>
|
|
2428
|
+
<TableCell className="text-muted-foreground">
|
|
2429
|
+
{entry.notes ?? '—'}
|
|
2430
|
+
</TableCell>
|
|
2431
|
+
</TableRow>
|
|
2432
|
+
))}
|
|
2433
|
+
</TableBody>
|
|
2434
|
+
</Table>
|
|
2435
|
+
</div>
|
|
2436
|
+
) : (
|
|
2437
|
+
<p className="text-sm text-muted-foreground">
|
|
2438
|
+
{detailsT('noCompensationHistory')}
|
|
2439
|
+
</p>
|
|
2440
|
+
)}
|
|
2441
|
+
</div>
|
|
2442
|
+
</>
|
|
2443
|
+
) : (
|
|
2444
|
+
<p className="text-sm text-muted-foreground">
|
|
2445
|
+
{t('noDirectorAccess')}
|
|
2446
|
+
</p>
|
|
2447
|
+
)}
|
|
2448
|
+
</TabsContent>
|
|
2449
|
+
) : null}
|
|
2063
2450
|
</Tabs>
|
|
2064
2451
|
) : (
|
|
2065
2452
|
<div className="space-y-4 px-4">
|