@hed-hog/operations 0.0.331 → 0.0.332
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 +54 -0
- package/dist/controllers/operations-collaborators.controller.d.ts.map +1 -1
- package/dist/controllers/operations-collaborators.controller.js +100 -0
- package/dist/controllers/operations-collaborators.controller.js.map +1 -1
- package/dist/dto/create-collaborator-invoice.dto.d.ts +11 -0
- package/dist/dto/create-collaborator-invoice.dto.d.ts.map +1 -0
- package/dist/dto/create-collaborator-invoice.dto.js +55 -0
- package/dist/dto/create-collaborator-invoice.dto.js.map +1 -0
- package/dist/dto/create-collaborator-payment.dto.d.ts +10 -0
- package/dist/dto/create-collaborator-payment.dto.d.ts.map +1 -0
- package/dist/dto/create-collaborator-payment.dto.js +50 -0
- package/dist/dto/create-collaborator-payment.dto.js.map +1 -0
- package/dist/dto/list-collaborator-invoice.dto.d.ts +4 -0
- package/dist/dto/list-collaborator-invoice.dto.d.ts.map +1 -0
- package/dist/dto/list-collaborator-invoice.dto.js +8 -0
- package/dist/dto/list-collaborator-invoice.dto.js.map +1 -0
- package/dist/dto/list-collaborator-payment.dto.d.ts +4 -0
- package/dist/dto/list-collaborator-payment.dto.d.ts.map +1 -0
- package/dist/dto/list-collaborator-payment.dto.js +8 -0
- package/dist/dto/list-collaborator-payment.dto.js.map +1 -0
- package/dist/dto/update-collaborator-invoice.dto.d.ts +6 -0
- package/dist/dto/update-collaborator-invoice.dto.d.ts.map +1 -0
- package/dist/dto/update-collaborator-invoice.dto.js +9 -0
- package/dist/dto/update-collaborator-invoice.dto.js.map +1 -0
- package/dist/dto/update-collaborator-payment.dto.d.ts +6 -0
- package/dist/dto/update-collaborator-payment.dto.d.ts.map +1 -0
- package/dist/dto/update-collaborator-payment.dto.js +9 -0
- package/dist/dto/update-collaborator-payment.dto.js.map +1 -0
- package/dist/operations.service.d.ts +76 -0
- package/dist/operations.service.d.ts.map +1 -1
- package/dist/operations.service.js +235 -5
- package/dist/operations.service.js.map +1 -1
- package/hedhog/data/menu.yaml +27 -8
- package/hedhog/data/route.yaml +72 -0
- package/hedhog/frontend/app/_components/collaborator-form-screen.tsx.ejs +39 -3
- package/hedhog/frontend/app/_components/collaborator-invoices-tab.tsx.ejs +443 -0
- package/hedhog/frontend/app/_components/collaborator-payment-history-tab.tsx.ejs +429 -0
- package/hedhog/frontend/app/_components/my-project-summary-screen.tsx.ejs +86 -87
- package/hedhog/frontend/app/_components/project-assignments-tab.tsx.ejs +218 -10
- package/hedhog/frontend/app/_components/project-details-screen.tsx.ejs +710 -26
- package/hedhog/frontend/app/_components/project-form-screen.tsx.ejs +158 -38
- package/hedhog/frontend/app/_components/task-detail-sheet.tsx.ejs +807 -803
- package/hedhog/frontend/app/_lib/api.ts.ejs +631 -480
- package/hedhog/frontend/app/_lib/types.ts.ejs +6 -5
- package/hedhog/frontend/app/_lib/utils/task-ui.ts.ejs +18 -0
- package/hedhog/frontend/app/my-projects/page.tsx.ejs +16 -2
- package/hedhog/frontend/app/my-tasks/page.tsx.ejs +95 -157
- package/hedhog/frontend/app/projects/page.tsx.ejs +42 -6
- package/hedhog/frontend/app/tasks-gantt/page.tsx.ejs +953 -0
- package/hedhog/frontend/messages/en.json +96 -2
- package/hedhog/frontend/messages/pt.json +96 -2
- package/hedhog/table/operations_collaborator_invoice.yaml +35 -0
- package/hedhog/table/operations_collaborator_payment.yaml +32 -0
- package/package.json +5 -5
- package/src/controllers/operations-collaborators.controller.ts +117 -8
- package/src/dto/create-collaborator-invoice.dto.ts +39 -0
- package/src/dto/create-collaborator-payment.dto.ts +35 -0
- package/src/dto/list-collaborator-invoice.dto.ts +3 -0
- package/src/dto/list-collaborator-payment.dto.ts +3 -0
- package/src/dto/update-collaborator-invoice.dto.ts +6 -0
- package/src/dto/update-collaborator-payment.dto.ts +6 -0
- package/src/operations.service.ts +328 -5
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
CommandItem,
|
|
19
19
|
CommandList,
|
|
20
20
|
} from '@/components/ui/command';
|
|
21
|
+
import { EntityPicker } from '@/components/ui/entity-picker';
|
|
21
22
|
import {
|
|
22
23
|
Form,
|
|
23
24
|
FormControl,
|
|
@@ -49,12 +50,7 @@ import {
|
|
|
49
50
|
SheetHeader,
|
|
50
51
|
SheetTitle,
|
|
51
52
|
} from '@/components/ui/sheet';
|
|
52
|
-
import {
|
|
53
|
-
Tabs,
|
|
54
|
-
TabsContent,
|
|
55
|
-
TabsList,
|
|
56
|
-
TabsTrigger,
|
|
57
|
-
} from '@/components/ui/tabs';
|
|
53
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
58
54
|
import { Textarea } from '@/components/ui/textarea';
|
|
59
55
|
import {
|
|
60
56
|
Tooltip,
|
|
@@ -100,12 +96,36 @@ import {
|
|
|
100
96
|
trimToNull,
|
|
101
97
|
} from '../_lib/utils/forms';
|
|
102
98
|
import { ContractFormScreen } from './contract-form-screen';
|
|
103
|
-
import { DepartmentPicker } from './department-picker';
|
|
104
99
|
import { OperationsHeader } from './operations-header';
|
|
105
|
-
import { ProjectFileAttachments } from './project-file-attachments';
|
|
106
100
|
|
|
107
101
|
const OPTION_PAGE_SIZE = 12;
|
|
108
102
|
|
|
103
|
+
function getPersonAvatarUrl(avatarId?: number | null) {
|
|
104
|
+
return typeof avatarId === 'number' && avatarId > 0
|
|
105
|
+
? `${process.env.NEXT_PUBLIC_API_BASE_URL}/person/avatar/${avatarId}`
|
|
106
|
+
: undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getUserPhotoUrl(photoId?: number | null) {
|
|
110
|
+
return typeof photoId === 'number' && photoId > 0
|
|
111
|
+
? `${process.env.NEXT_PUBLIC_API_BASE_URL}/user/avatar/${photoId}`
|
|
112
|
+
: undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getInitials(value?: string | null) {
|
|
116
|
+
const parts = String(value ?? '')
|
|
117
|
+
.trim()
|
|
118
|
+
.split(/\s+/)
|
|
119
|
+
.filter(Boolean)
|
|
120
|
+
.slice(0, 2);
|
|
121
|
+
|
|
122
|
+
if (!parts.length) {
|
|
123
|
+
return '??';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return parts.map((part) => part[0]?.toUpperCase() ?? '').join('');
|
|
127
|
+
}
|
|
128
|
+
|
|
109
129
|
type TeamAssignmentState = {
|
|
110
130
|
collaboratorId: number;
|
|
111
131
|
selected: boolean;
|
|
@@ -645,6 +665,7 @@ export function ProjectFormScreen({
|
|
|
645
665
|
const t = useTranslations('operations.ProjectFormPage');
|
|
646
666
|
const commonT = useTranslations('operations.Common');
|
|
647
667
|
const contractT = useTranslations('operations.ContractFormPage');
|
|
668
|
+
const collaboratorFormT = useTranslations('operations.CollaboratorFormPage');
|
|
648
669
|
const { request, showToastHandler, currentLocaleCode } = useApp();
|
|
649
670
|
const access = useOperationsAccess();
|
|
650
671
|
const router = useRouter();
|
|
@@ -652,6 +673,8 @@ export function ProjectFormScreen({
|
|
|
652
673
|
const personSheetModeRef = useRef<'create' | 'edit'>('edit');
|
|
653
674
|
const [personSheetOpen, setPersonSheetOpen] = useState(false);
|
|
654
675
|
const [personToEdit, setPersonToEdit] = useState<Person | null>(null);
|
|
676
|
+
const [createdManagerCollaborators, setCreatedManagerCollaborators] =
|
|
677
|
+
useState<OperationsCollaborator[]>([]);
|
|
655
678
|
const isSheetMode = Boolean(onCancel);
|
|
656
679
|
const isCreateMode = !projectId;
|
|
657
680
|
const [codeAutoMode, setCodeAutoMode] = useState(isCreateMode);
|
|
@@ -746,6 +769,37 @@ export function ProjectFormScreen({
|
|
|
746
769
|
[rawCollaborators]
|
|
747
770
|
);
|
|
748
771
|
|
|
772
|
+
const createManagerCollaborator = async (values: Record<string, string>) => {
|
|
773
|
+
const displayName = values.displayName?.trim() ?? '';
|
|
774
|
+
const weeklyCapacityHours = parseNumberInput(
|
|
775
|
+
values.weeklyCapacityHours ?? ''
|
|
776
|
+
);
|
|
777
|
+
|
|
778
|
+
if (!displayName) {
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const created = await mutateOperations<OperationsCollaborator>(
|
|
783
|
+
request,
|
|
784
|
+
'/operations/collaborators',
|
|
785
|
+
'POST',
|
|
786
|
+
{
|
|
787
|
+
displayName,
|
|
788
|
+
weeklyCapacityHours,
|
|
789
|
+
status: 'active',
|
|
790
|
+
autoGenerateContractDraft: false,
|
|
791
|
+
}
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
setCreatedManagerCollaborators((current) => {
|
|
795
|
+
const next = current.filter((item) => item.id !== created.id);
|
|
796
|
+
next.unshift(created);
|
|
797
|
+
return next;
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
return created;
|
|
801
|
+
};
|
|
802
|
+
|
|
749
803
|
const { data: contracts = [], refetch: refetchContracts } = useQuery<
|
|
750
804
|
OperationsContract[]
|
|
751
805
|
>({
|
|
@@ -819,6 +873,10 @@ export function ProjectFormScreen({
|
|
|
819
873
|
byId.set(collaborator.id, collaborator);
|
|
820
874
|
}
|
|
821
875
|
|
|
876
|
+
for (const collaborator of createdManagerCollaborators) {
|
|
877
|
+
byId.set(collaborator.id, collaborator);
|
|
878
|
+
}
|
|
879
|
+
|
|
822
880
|
if (
|
|
823
881
|
project?.managerCollaboratorId &&
|
|
824
882
|
!byId.has(project.managerCollaboratorId)
|
|
@@ -847,7 +905,7 @@ export function ProjectFormScreen({
|
|
|
847
905
|
}
|
|
848
906
|
|
|
849
907
|
return Array.from(byId.values());
|
|
850
|
-
}, [collaborators, project]);
|
|
908
|
+
}, [collaborators, createdManagerCollaborators, project]);
|
|
851
909
|
|
|
852
910
|
const availableContracts = useMemo(() => {
|
|
853
911
|
if (!project?.relatedContract) {
|
|
@@ -880,10 +938,9 @@ export function ProjectFormScreen({
|
|
|
880
938
|
.filter(Boolean)
|
|
881
939
|
.join(' • '),
|
|
882
940
|
avatarUrl:
|
|
883
|
-
|
|
884
|
-
collaborator.personAvatarId
|
|
885
|
-
|
|
886
|
-
: null,
|
|
941
|
+
getUserPhotoUrl(collaborator.userPhotoId) ??
|
|
942
|
+
getPersonAvatarUrl(collaborator.personAvatarId) ??
|
|
943
|
+
null,
|
|
887
944
|
})),
|
|
888
945
|
[availableCollaborators]
|
|
889
946
|
);
|
|
@@ -1264,20 +1321,100 @@ export function ProjectFormScreen({
|
|
|
1264
1321
|
)}
|
|
1265
1322
|
</p>
|
|
1266
1323
|
</div>
|
|
1267
|
-
<div className="grid min-w-0 gap-3 md:grid-cols-2 xl:grid-cols-
|
|
1324
|
+
<div className="grid min-w-0 gap-3 md:grid-cols-2 xl:grid-cols-3">
|
|
1268
1325
|
<div className="min-w-0 space-y-2">
|
|
1269
1326
|
<FieldLabel label={commonT('labels.manager')} />
|
|
1270
|
-
<
|
|
1271
|
-
|
|
1272
|
-
|
|
1327
|
+
<EntityPicker
|
|
1328
|
+
value={
|
|
1329
|
+
form.managerCollaboratorId === 'none'
|
|
1330
|
+
? null
|
|
1331
|
+
: Number(form.managerCollaboratorId)
|
|
1332
|
+
}
|
|
1273
1333
|
options={managerOptions}
|
|
1334
|
+
getOptionValue={(option) => option.id}
|
|
1335
|
+
getOptionLabel={(option) => option.title}
|
|
1336
|
+
getOptionDescription={(option) =>
|
|
1337
|
+
option.description || undefined
|
|
1338
|
+
}
|
|
1339
|
+
renderOption={({ option }) => (
|
|
1340
|
+
<div className="flex min-w-0 items-center gap-2.5">
|
|
1341
|
+
<Avatar className="size-6 shrink-0">
|
|
1342
|
+
<AvatarImage
|
|
1343
|
+
src={option.avatarUrl ?? undefined}
|
|
1344
|
+
alt={option.title}
|
|
1345
|
+
/>
|
|
1346
|
+
<AvatarFallback className="text-[10px]">
|
|
1347
|
+
{getInitials(option.title)}
|
|
1348
|
+
</AvatarFallback>
|
|
1349
|
+
</Avatar>
|
|
1350
|
+
<div className="min-w-0">
|
|
1351
|
+
<div className="truncate text-sm">{option.title}</div>
|
|
1352
|
+
{option.description ? (
|
|
1353
|
+
<div className="truncate text-xs text-muted-foreground">
|
|
1354
|
+
{option.description}
|
|
1355
|
+
</div>
|
|
1356
|
+
) : null}
|
|
1357
|
+
</div>
|
|
1358
|
+
</div>
|
|
1359
|
+
)}
|
|
1360
|
+
renderSelectedValue={({ option, label }) =>
|
|
1361
|
+
option ? (
|
|
1362
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
1363
|
+
<Avatar className="size-5 shrink-0">
|
|
1364
|
+
<AvatarImage
|
|
1365
|
+
src={option.avatarUrl ?? undefined}
|
|
1366
|
+
alt={option.title}
|
|
1367
|
+
/>
|
|
1368
|
+
<AvatarFallback className="text-[10px]">
|
|
1369
|
+
{getInitials(option.title)}
|
|
1370
|
+
</AvatarFallback>
|
|
1371
|
+
</Avatar>
|
|
1372
|
+
<span className="truncate">{option.title}</span>
|
|
1373
|
+
</div>
|
|
1374
|
+
) : (
|
|
1375
|
+
<span className="text-muted-foreground">{label}</span>
|
|
1376
|
+
)
|
|
1377
|
+
}
|
|
1274
1378
|
placeholder={commonT('labels.notAssigned')}
|
|
1275
1379
|
searchPlaceholder={t('placeholders.managerSearch')}
|
|
1276
|
-
|
|
1380
|
+
emptySelectionLabel={commonT('labels.notAssigned')}
|
|
1381
|
+
valueType="number"
|
|
1382
|
+
clearable
|
|
1383
|
+
allowEmptySelection
|
|
1384
|
+
showCreateButton
|
|
1385
|
+
entityLabel={commonT('labels.manager').toLowerCase()}
|
|
1386
|
+
createActionLabel={`${commonT('actions.create')} ${commonT(
|
|
1387
|
+
'labels.manager'
|
|
1388
|
+
).toLowerCase()}`}
|
|
1389
|
+
createTitle={`${commonT('actions.create')} ${commonT(
|
|
1390
|
+
'labels.manager'
|
|
1391
|
+
).toLowerCase()}`}
|
|
1392
|
+
createDescription={collaboratorFormT(
|
|
1393
|
+
'sections.employmentInfoCreateDescription'
|
|
1394
|
+
)}
|
|
1395
|
+
createFields={[
|
|
1396
|
+
{
|
|
1397
|
+
name: 'displayName',
|
|
1398
|
+
label: collaboratorFormT('fields.displayName'),
|
|
1399
|
+
placeholder: collaboratorFormT('fields.displayName'),
|
|
1400
|
+
required: true,
|
|
1401
|
+
},
|
|
1402
|
+
{
|
|
1403
|
+
name: 'weeklyCapacityHours',
|
|
1404
|
+
label: collaboratorFormT('fields.weeklyCapacityHours'),
|
|
1405
|
+
placeholder: '40',
|
|
1406
|
+
type: 'number',
|
|
1407
|
+
},
|
|
1408
|
+
]}
|
|
1409
|
+
mapSearchToCreateValues={(search) => ({
|
|
1410
|
+
displayName: search,
|
|
1411
|
+
weeklyCapacityHours: '40',
|
|
1412
|
+
})}
|
|
1413
|
+
onCreate={createManagerCollaborator}
|
|
1277
1414
|
onChange={(value) =>
|
|
1278
1415
|
setForm((current) => ({
|
|
1279
1416
|
...current,
|
|
1280
|
-
managerCollaboratorId: value,
|
|
1417
|
+
managerCollaboratorId: value ? String(value) : 'none',
|
|
1281
1418
|
}))
|
|
1282
1419
|
}
|
|
1283
1420
|
/>
|
|
@@ -1372,12 +1509,11 @@ export function ProjectFormScreen({
|
|
|
1372
1509
|
{!isCreateMode ? (
|
|
1373
1510
|
<>
|
|
1374
1511
|
<Tabs defaultValue="financials" className="space-y-3">
|
|
1375
|
-
<TabsList className="grid w-full grid-cols-
|
|
1512
|
+
<TabsList className="grid w-full grid-cols-2">
|
|
1376
1513
|
<TabsTrigger value="financials">
|
|
1377
1514
|
{t('sections.financials')}
|
|
1378
1515
|
</TabsTrigger>
|
|
1379
1516
|
<TabsTrigger value="team">{t('sections.team')}</TabsTrigger>
|
|
1380
|
-
<TabsTrigger value="files">{t('sections.files')}</TabsTrigger>
|
|
1381
1517
|
</TabsList>
|
|
1382
1518
|
|
|
1383
1519
|
<TabsContent value="financials" className="space-y-3">
|
|
@@ -1487,9 +1623,7 @@ export function ProjectFormScreen({
|
|
|
1487
1623
|
}}
|
|
1488
1624
|
initialValues={{
|
|
1489
1625
|
code: form.code ? `PRJ-${form.code}` : '',
|
|
1490
|
-
name: form.name
|
|
1491
|
-
? `${form.name} Service Agreement`
|
|
1492
|
-
: '',
|
|
1626
|
+
name: form.name ? `${form.name} Service Agreement` : '',
|
|
1493
1627
|
clientName: form.clientName,
|
|
1494
1628
|
contractCategory: 'client',
|
|
1495
1629
|
contractType: 'service_agreement',
|
|
@@ -1666,20 +1800,6 @@ export function ProjectFormScreen({
|
|
|
1666
1800
|
</div>
|
|
1667
1801
|
</div>
|
|
1668
1802
|
</TabsContent>
|
|
1669
|
-
|
|
1670
|
-
<TabsContent value="files" className="space-y-3">
|
|
1671
|
-
<div className="space-y-0.5">
|
|
1672
|
-
<h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
1673
|
-
{t('sections.files')}
|
|
1674
|
-
</h3>
|
|
1675
|
-
<p className="text-[11px] text-muted-foreground/80">
|
|
1676
|
-
{t('sections.filesDescription')}
|
|
1677
|
-
</p>
|
|
1678
|
-
</div>
|
|
1679
|
-
{projectId ? (
|
|
1680
|
-
<ProjectFileAttachments projectId={projectId} />
|
|
1681
|
-
) : null}
|
|
1682
|
-
</TabsContent>
|
|
1683
1803
|
</Tabs>
|
|
1684
1804
|
</>
|
|
1685
1805
|
) : (
|