@hed-hog/contact 0.0.303 → 0.0.305
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/README.md +225 -17
- package/dist/person/dto/account.dto.d.ts +5 -0
- package/dist/person/dto/account.dto.d.ts.map +1 -1
- package/dist/person/dto/account.dto.js +29 -0
- package/dist/person/dto/account.dto.js.map +1 -1
- package/dist/person/dto/import-preview.dto.d.ts +7 -0
- package/dist/person/dto/import-preview.dto.d.ts.map +1 -0
- package/dist/person/dto/import-preview.dto.js +7 -0
- package/dist/person/dto/import-preview.dto.js.map +1 -0
- package/dist/person/dto/import.dto.d.ts +15 -0
- package/dist/person/dto/import.dto.d.ts.map +1 -0
- package/dist/person/dto/import.dto.js +51 -0
- package/dist/person/dto/import.dto.js.map +1 -0
- package/dist/person/person.controller.d.ts +14 -0
- package/dist/person/person.controller.d.ts.map +1 -1
- package/dist/person/person.controller.js +53 -0
- package/dist/person/person.controller.js.map +1 -1
- package/dist/person/person.service.d.ts +19 -0
- package/dist/person/person.service.d.ts.map +1 -1
- package/dist/person/person.service.js +481 -67
- package/dist/person/person.service.js.map +1 -1
- package/dist/person-relation-type/person-relation-type.controller.d.ts +2 -2
- package/dist/person-relation-type/person-relation-type.service.d.ts +2 -2
- package/hedhog/data/route.yaml +6 -0
- package/hedhog/frontend/app/accounts/_components/account-form-sheet.tsx.ejs +2242 -484
- package/hedhog/frontend/app/accounts/_components/account-types.ts.ejs +51 -0
- package/hedhog/frontend/app/accounts/page.tsx.ejs +181 -16
- package/hedhog/frontend/app/contact-type/page.tsx.ejs +223 -29
- package/hedhog/frontend/app/document-type/page.tsx.ejs +248 -37
- package/hedhog/frontend/app/follow-ups/page.tsx.ejs +129 -19
- package/hedhog/frontend/app/person/_components/person-field-with-create.tsx.ejs +78 -212
- package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +760 -178
- package/hedhog/frontend/app/person/_components/person-import-sheet.tsx.ejs +1120 -0
- package/hedhog/frontend/app/person/_components/person-interaction-dialog.tsx.ejs +171 -4
- package/hedhog/frontend/app/person/page.tsx.ejs +17 -0
- package/hedhog/frontend/app/pipeline/_components/lead-proposals-tab.tsx.ejs +696 -82
- package/hedhog/frontend/messages/en.json +140 -2
- package/hedhog/frontend/messages/pt.json +147 -9
- package/package.json +5 -5
- package/src/person/dto/account.dto.ts +31 -0
- package/src/person/dto/import-preview.dto.ts +6 -0
- package/src/person/dto/import.dto.ts +61 -0
- package/src/person/person.controller.ts +74 -12
- package/src/person/person.service.ts +615 -68
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Button } from '@/components/ui/button';
|
|
4
|
-
import {
|
|
5
|
-
Command,
|
|
6
|
-
CommandEmpty,
|
|
7
|
-
CommandGroup,
|
|
8
|
-
CommandInput,
|
|
9
|
-
CommandItem,
|
|
10
|
-
CommandList,
|
|
11
|
-
} from '@/components/ui/command';
|
|
4
|
+
import { EntityPicker } from '@/components/ui/entity-picker';
|
|
12
5
|
import {
|
|
13
6
|
Form,
|
|
14
7
|
FormControl,
|
|
@@ -18,12 +11,6 @@ import {
|
|
|
18
11
|
FormMessage,
|
|
19
12
|
} from '@/components/ui/form';
|
|
20
13
|
import { Input } from '@/components/ui/input';
|
|
21
|
-
import { Label } from '@/components/ui/label';
|
|
22
|
-
import {
|
|
23
|
-
Popover,
|
|
24
|
-
PopoverContent,
|
|
25
|
-
PopoverTrigger,
|
|
26
|
-
} from '@/components/ui/popover';
|
|
27
14
|
import {
|
|
28
15
|
Select,
|
|
29
16
|
SelectContent,
|
|
@@ -40,16 +27,10 @@ import {
|
|
|
40
27
|
} from '@/components/ui/sheet';
|
|
41
28
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
42
29
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
43
|
-
import {
|
|
30
|
+
import { Plus } from 'lucide-react';
|
|
44
31
|
import { useTranslations } from 'next-intl';
|
|
45
32
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
46
|
-
import {
|
|
47
|
-
Controller,
|
|
48
|
-
FieldValues,
|
|
49
|
-
Path,
|
|
50
|
-
UseFormReturn,
|
|
51
|
-
useForm,
|
|
52
|
-
} from 'react-hook-form';
|
|
33
|
+
import { FieldValues, Path, UseFormReturn, useForm } from 'react-hook-form';
|
|
53
34
|
import { z } from 'zod';
|
|
54
35
|
|
|
55
36
|
type PersonOption = {
|
|
@@ -103,9 +84,10 @@ function usePersonFieldWithCreateTranslations() {
|
|
|
103
84
|
// Filter values to only include types compatible with next-intl
|
|
104
85
|
const filteredValues = values
|
|
105
86
|
? Object.fromEntries(
|
|
106
|
-
Object.entries(values).filter(
|
|
107
|
-
|
|
108
|
-
|
|
87
|
+
Object.entries(values).filter(([key, v]) => {
|
|
88
|
+
void key;
|
|
89
|
+
return typeof v === 'string' || typeof v === 'number';
|
|
90
|
+
})
|
|
109
91
|
)
|
|
110
92
|
: undefined;
|
|
111
93
|
if (tRoot.has(contactKey)) {
|
|
@@ -574,23 +556,12 @@ export function PersonFieldWithCreate<TFieldValues extends FieldValues>({
|
|
|
574
556
|
}) {
|
|
575
557
|
const { request } = useApp();
|
|
576
558
|
const t = usePersonFieldWithCreateTranslations();
|
|
577
|
-
const [personOpen, setPersonOpen] = useState(false);
|
|
578
|
-
const [personSearch, setPersonSearch] = useState('');
|
|
579
|
-
const [debouncedPersonSearch, setDebouncedPersonSearch] = useState('');
|
|
580
559
|
const [createPersonOpen, setCreatePersonOpen] = useState(false);
|
|
581
560
|
const [selectedPersonLabel, setSelectedPersonLabel] =
|
|
582
561
|
useState(initialSelectedLabel);
|
|
583
562
|
const parentScrollContainerRef = useRef<HTMLElement | null>(null);
|
|
584
563
|
const parentScrollTopRef = useRef(0);
|
|
585
564
|
|
|
586
|
-
useEffect(() => {
|
|
587
|
-
const timeout = setTimeout(() => {
|
|
588
|
-
setDebouncedPersonSearch(personSearch);
|
|
589
|
-
}, 300);
|
|
590
|
-
|
|
591
|
-
return () => clearTimeout(timeout);
|
|
592
|
-
}, [personSearch]);
|
|
593
|
-
|
|
594
565
|
useEffect(() => {
|
|
595
566
|
setSelectedPersonLabel(initialSelectedLabel);
|
|
596
567
|
}, [initialSelectedLabel]);
|
|
@@ -638,187 +609,83 @@ export function PersonFieldWithCreate<TFieldValues extends FieldValues>({
|
|
|
638
609
|
setTimeout(restore, 120);
|
|
639
610
|
};
|
|
640
611
|
|
|
641
|
-
const { data: personOptionsData = [], isLoading: isLoadingPersons } =
|
|
642
|
-
useQuery<PersonOption[]>({
|
|
643
|
-
queryKey: [
|
|
644
|
-
'person-autocomplete-generic',
|
|
645
|
-
entityLabel,
|
|
646
|
-
debouncedPersonSearch,
|
|
647
|
-
personTypeFilter,
|
|
648
|
-
],
|
|
649
|
-
queryFn: async () => {
|
|
650
|
-
const params = new URLSearchParams();
|
|
651
|
-
params.set('page', '1');
|
|
652
|
-
params.set('pageSize', '20');
|
|
653
|
-
if (personTypeFilter !== 'all') {
|
|
654
|
-
params.set('type', personTypeFilter);
|
|
655
|
-
}
|
|
656
|
-
if (debouncedPersonSearch.trim()) {
|
|
657
|
-
params.set('search', debouncedPersonSearch.trim());
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
const response = await request<
|
|
661
|
-
PaginatedResponse<PersonOption> | PersonOption[]
|
|
662
|
-
>({
|
|
663
|
-
url: `/person?${params.toString()}`,
|
|
664
|
-
method: 'GET',
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
const payload = response?.data;
|
|
668
|
-
if (Array.isArray(payload)) {
|
|
669
|
-
return payload as PersonOption[];
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
if (payload && 'data' in payload && Array.isArray(payload.data)) {
|
|
673
|
-
return payload.data as PersonOption[];
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
return [];
|
|
677
|
-
},
|
|
678
|
-
placeholderData: (old) => old ?? [],
|
|
679
|
-
});
|
|
680
|
-
|
|
681
612
|
const parseFieldValue = (personId: string | number) =>
|
|
682
613
|
valueType === 'number' ? Number(personId) : String(personId);
|
|
683
614
|
|
|
684
|
-
const fieldState = form.getFieldState(name, form.formState);
|
|
685
|
-
|
|
686
615
|
return (
|
|
687
616
|
<>
|
|
688
|
-
<div className="
|
|
689
|
-
<
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
setCreatePersonOpen(true);
|
|
756
|
-
}}
|
|
757
|
-
>
|
|
758
|
-
{t('actions.createNew')}
|
|
759
|
-
</Button>
|
|
760
|
-
</div>
|
|
761
|
-
)}
|
|
762
|
-
</CommandEmpty>
|
|
763
|
-
<CommandGroup>
|
|
764
|
-
{personOptionsData.map((person) => (
|
|
765
|
-
<CommandItem
|
|
766
|
-
key={String(person.id)}
|
|
767
|
-
value={`${person.name}-${person.id}`}
|
|
768
|
-
onSelect={() => {
|
|
769
|
-
field.onChange(parseFieldValue(person.id));
|
|
770
|
-
setSelectedPersonLabel(person.name);
|
|
771
|
-
setPersonOpen(false);
|
|
772
|
-
}}
|
|
773
|
-
>
|
|
774
|
-
{person.name}
|
|
775
|
-
</CommandItem>
|
|
776
|
-
))}
|
|
777
|
-
</CommandGroup>
|
|
778
|
-
</CommandList>
|
|
779
|
-
</Command>
|
|
780
|
-
</PopoverContent>
|
|
781
|
-
</Popover>
|
|
782
|
-
|
|
783
|
-
{hasValue ? (
|
|
784
|
-
<Button
|
|
785
|
-
type="button"
|
|
786
|
-
variant="outline"
|
|
787
|
-
size="icon"
|
|
788
|
-
className="shrink-0"
|
|
789
|
-
onClick={() => {
|
|
790
|
-
field.onChange(valueType === 'number' ? null : '');
|
|
791
|
-
setPersonSearch('');
|
|
792
|
-
setSelectedPersonLabel('');
|
|
793
|
-
setPersonOpen(false);
|
|
794
|
-
}}
|
|
795
|
-
aria-label={t('actions.clearSelection')}
|
|
796
|
-
>
|
|
797
|
-
<X className="h-4 w-4" />
|
|
798
|
-
</Button>
|
|
799
|
-
) : null}
|
|
800
|
-
|
|
801
|
-
<Button
|
|
802
|
-
type="button"
|
|
803
|
-
variant="outline"
|
|
804
|
-
size="icon"
|
|
805
|
-
className="shrink-0"
|
|
806
|
-
onClick={(event) => {
|
|
807
|
-
captureParentScrollPosition(event.currentTarget);
|
|
808
|
-
setPersonOpen(false);
|
|
809
|
-
setCreatePersonOpen(true);
|
|
810
|
-
}}
|
|
811
|
-
aria-label={t('actions.createEntityAria', { entityLabel })}
|
|
812
|
-
>
|
|
813
|
-
<Plus className="h-4 w-4" />
|
|
814
|
-
</Button>
|
|
815
|
-
</div>
|
|
816
|
-
);
|
|
617
|
+
<div className="flex w-full items-end gap-2">
|
|
618
|
+
<div className="min-w-0 flex-1">
|
|
619
|
+
<EntityPicker<PersonOption, TFieldValues>
|
|
620
|
+
form={form}
|
|
621
|
+
name={name}
|
|
622
|
+
label={label}
|
|
623
|
+
placeholder={selectPlaceholder}
|
|
624
|
+
entityLabel={entityLabel}
|
|
625
|
+
valueType={valueType}
|
|
626
|
+
initialSelectedLabel={selectedPersonLabel}
|
|
627
|
+
searchPlaceholder={t('search.placeholder')}
|
|
628
|
+
emptyStateDescription={t('search.noResults')}
|
|
629
|
+
loadingLabel={t('search.loading')}
|
|
630
|
+
showCreateButton={false}
|
|
631
|
+
clearable
|
|
632
|
+
loadOptions={async ({ page, pageSize, search }) => {
|
|
633
|
+
const params = new URLSearchParams();
|
|
634
|
+
params.set('page', String(page));
|
|
635
|
+
params.set('pageSize', String(pageSize));
|
|
636
|
+
|
|
637
|
+
if (personTypeFilter !== 'all') {
|
|
638
|
+
params.set('type', personTypeFilter);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (search.trim()) {
|
|
642
|
+
params.set('search', search.trim());
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const response = await request<
|
|
646
|
+
PaginatedResponse<PersonOption> | PersonOption[]
|
|
647
|
+
>({
|
|
648
|
+
url: `/person?${params.toString()}`,
|
|
649
|
+
method: 'GET',
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
const payload = response?.data;
|
|
653
|
+
if (Array.isArray(payload)) {
|
|
654
|
+
return {
|
|
655
|
+
items: payload as PersonOption[],
|
|
656
|
+
hasMore: false,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const items = Array.isArray(payload?.data) ? payload.data : [];
|
|
661
|
+
|
|
662
|
+
return {
|
|
663
|
+
items,
|
|
664
|
+
hasMore: items.length >= pageSize,
|
|
665
|
+
};
|
|
666
|
+
}}
|
|
667
|
+
getOptionValue={(person) => person.id}
|
|
668
|
+
getOptionLabel={(person) => person.name}
|
|
669
|
+
onChange={(value, option) => {
|
|
670
|
+
void value;
|
|
671
|
+
setSelectedPersonLabel(option?.name ?? '');
|
|
672
|
+
}}
|
|
673
|
+
/>
|
|
674
|
+
</div>
|
|
675
|
+
|
|
676
|
+
<Button
|
|
677
|
+
type="button"
|
|
678
|
+
variant="outline"
|
|
679
|
+
size="icon"
|
|
680
|
+
className="h-10 w-10 shrink-0"
|
|
681
|
+
onClick={(event) => {
|
|
682
|
+
captureParentScrollPosition(event.currentTarget);
|
|
683
|
+
setCreatePersonOpen(true);
|
|
817
684
|
}}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
<
|
|
821
|
-
|
|
685
|
+
aria-label={t('actions.createEntityAria', { entityLabel })}
|
|
686
|
+
>
|
|
687
|
+
<Plus className="h-4 w-4" />
|
|
688
|
+
</Button>
|
|
822
689
|
</div>
|
|
823
690
|
|
|
824
691
|
<CreatePersonSheet
|
|
@@ -839,7 +706,6 @@ export function PersonFieldWithCreate<TFieldValues extends FieldValues>({
|
|
|
839
706
|
shouldTouch: true,
|
|
840
707
|
});
|
|
841
708
|
setSelectedPersonLabel(person.name);
|
|
842
|
-
setPersonSearch(person.name);
|
|
843
709
|
setCreatePersonOpen(false);
|
|
844
710
|
}}
|
|
845
711
|
/>
|