@hed-hog/contact 0.0.333 → 0.0.347
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/person/person.controller.d.ts +4 -5
- package/dist/person/person.controller.d.ts.map +1 -1
- package/dist/person/person.controller.js +7 -3
- package/dist/person/person.controller.js.map +1 -1
- package/dist/person/person.service.d.ts +6 -3
- package/dist/person/person.service.d.ts.map +1 -1
- package/dist/person/person.service.js +104 -23
- package/dist/person/person.service.js.map +1 -1
- package/hedhog/data/role.yaml +9 -1
- package/hedhog/endpoint/person.yaml +263 -0
- package/hedhog/frontend/app/_components/person-picker.tsx.ejs +7 -5
- package/hedhog/frontend/app/accounts/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/activities/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/contact-type/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/document-type/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/follow-ups/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +24 -23
- package/hedhog/frontend/app/person/_components/person-import-sheet.tsx.ejs +102 -102
- package/hedhog/frontend/app/person/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/proposals/_components/proposal-form-sheet.tsx.ejs +1306 -1306
- package/hedhog/frontend/app/proposals/_components/proposal-types.ts.ejs +172 -172
- package/hedhog/frontend/app/proposals/_components/proposals-management-page.tsx.ejs +74 -95
- package/package.json +5 -5
- package/src/person/person.controller.ts +29 -22
- package/src/person/person.service.ts +128 -26
|
@@ -63,30 +63,30 @@ type ImportResult = {
|
|
|
63
63
|
|
|
64
64
|
type ColumnMapping = Record<string, string>;
|
|
65
65
|
|
|
66
|
-
type CompanyOption = {
|
|
67
|
-
id: number;
|
|
68
|
-
name: string;
|
|
69
|
-
trade_name?: string | null;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
type WizardStep = 'upload' | 'preview' | 'mapping' | 'confirm' | 'result';
|
|
73
|
-
|
|
74
|
-
function getImportErrorMessage(error: unknown, fallback: string) {
|
|
75
|
-
if (typeof error === 'object' && error !== null) {
|
|
76
|
-
const response = 'response' in error ? error.response : undefined;
|
|
77
|
-
if (typeof response === 'object' && response !== null && 'data' in response) {
|
|
78
|
-
const data = response.data;
|
|
79
|
-
if (typeof data === 'object' && data !== null && 'message' in data) {
|
|
80
|
-
const message = data.message;
|
|
81
|
-
if (typeof message === 'string') return message;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if ('message' in error && typeof error.message === 'string') {
|
|
85
|
-
return error.message;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return fallback;
|
|
89
|
-
}
|
|
66
|
+
type CompanyOption = {
|
|
67
|
+
id: number;
|
|
68
|
+
name: string;
|
|
69
|
+
trade_name?: string | null;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type WizardStep = 'upload' | 'preview' | 'mapping' | 'confirm' | 'result';
|
|
73
|
+
|
|
74
|
+
function getImportErrorMessage(error: unknown, fallback: string) {
|
|
75
|
+
if (typeof error === 'object' && error !== null) {
|
|
76
|
+
const response = 'response' in error ? error.response : undefined;
|
|
77
|
+
if (typeof response === 'object' && response !== null && 'data' in response) {
|
|
78
|
+
const data = response.data;
|
|
79
|
+
if (typeof data === 'object' && data !== null && 'message' in data) {
|
|
80
|
+
const message = data.message;
|
|
81
|
+
if (typeof message === 'string') return message;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if ('message' in error && typeof error.message === 'string') {
|
|
85
|
+
return error.message;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return fallback;
|
|
89
|
+
}
|
|
90
90
|
|
|
91
91
|
// ─── CRM Field Definitions ───────────────────────────────────────────────────
|
|
92
92
|
|
|
@@ -164,7 +164,7 @@ function StepIndicator({ current }: { current: WizardStep }) {
|
|
|
164
164
|
<Icon className="size-3.5" />
|
|
165
165
|
)}
|
|
166
166
|
<span className="text-[10px] font-medium leading-tight hidden sm:block">
|
|
167
|
-
{t(step.labelKey as never)}
|
|
167
|
+
{t(step.labelKey as never)}
|
|
168
168
|
</span>
|
|
169
169
|
</div>
|
|
170
170
|
);
|
|
@@ -436,7 +436,7 @@ function MappingStep({
|
|
|
436
436
|
{duplicateFields
|
|
437
437
|
.map((field) => {
|
|
438
438
|
const fieldDef = CRM_FIELDS.find((f) => f.value === field);
|
|
439
|
-
const label = fieldDef ? t(fieldDef.labelKey as never) : field;
|
|
439
|
+
const label = fieldDef ? t(fieldDef.labelKey as never) : field;
|
|
440
440
|
return t('importMappingDuplicateWarning', { field: label });
|
|
441
441
|
})
|
|
442
442
|
.join(' ')}
|
|
@@ -498,7 +498,7 @@ function MappingStep({
|
|
|
498
498
|
value={field.value}
|
|
499
499
|
className="text-xs"
|
|
500
500
|
>
|
|
501
|
-
{t(field.labelKey as never)}
|
|
501
|
+
{t(field.labelKey as never)}
|
|
502
502
|
</SelectItem>
|
|
503
503
|
))}
|
|
504
504
|
</SelectContent>
|
|
@@ -581,7 +581,7 @@ function ConfirmStep({
|
|
|
581
581
|
variant="outline"
|
|
582
582
|
className="text-[10px] border-primary/30 bg-primary/5 text-primary px-1.5 py-0"
|
|
583
583
|
>
|
|
584
|
-
{fieldDef ? t(fieldDef.labelKey as never) : field}
|
|
584
|
+
{fieldDef ? t(fieldDef.labelKey as never) : field}
|
|
585
585
|
</Badge>
|
|
586
586
|
</div>
|
|
587
587
|
);
|
|
@@ -605,11 +605,11 @@ function ConfirmStep({
|
|
|
605
605
|
onChange={(val) => {
|
|
606
606
|
onCompanyChange(val ? Number(val) : null);
|
|
607
607
|
}}
|
|
608
|
-
getOptionValue={(opt) => (opt as CompanyOption).id}
|
|
609
|
-
getOptionLabel={(opt) => (opt as CompanyOption).name ?? ''}
|
|
610
|
-
getOptionDescription={(opt) =>
|
|
611
|
-
(opt as CompanyOption).trade_name ?? undefined
|
|
612
|
-
}
|
|
608
|
+
getOptionValue={(opt) => (opt as CompanyOption).id}
|
|
609
|
+
getOptionLabel={(opt) => (opt as CompanyOption).name ?? ''}
|
|
610
|
+
getOptionDescription={(opt) =>
|
|
611
|
+
(opt as CompanyOption).trade_name ?? undefined
|
|
612
|
+
}
|
|
613
613
|
loadOptions={async ({ page, pageSize, search }) => {
|
|
614
614
|
const params = new URLSearchParams({
|
|
615
615
|
page: String(page),
|
|
@@ -816,29 +816,29 @@ function ResultStep({
|
|
|
816
816
|
|
|
817
817
|
// ─── Main Sheet ──────────────────────────────────────────────────────────────
|
|
818
818
|
|
|
819
|
-
export type PersonImportSheetProps = {
|
|
820
|
-
open: boolean;
|
|
821
|
-
onOpenChange: (open: boolean) => void;
|
|
822
|
-
onSuccess: (result?: ImportResult) => void;
|
|
823
|
-
initialCompanyId?: number | null;
|
|
824
|
-
previewUrl?: string;
|
|
825
|
-
importUrl?: string;
|
|
826
|
-
title?: string;
|
|
827
|
-
description?: string;
|
|
828
|
-
nameRequired?: boolean;
|
|
829
|
-
};
|
|
830
|
-
|
|
831
|
-
export function PersonImportSheet({
|
|
832
|
-
open,
|
|
833
|
-
onOpenChange,
|
|
834
|
-
onSuccess,
|
|
835
|
-
initialCompanyId = null,
|
|
836
|
-
previewUrl = '/person/import/preview',
|
|
837
|
-
importUrl = '/person/import',
|
|
838
|
-
title,
|
|
839
|
-
description,
|
|
840
|
-
nameRequired = true,
|
|
841
|
-
}: PersonImportSheetProps) {
|
|
819
|
+
export type PersonImportSheetProps = {
|
|
820
|
+
open: boolean;
|
|
821
|
+
onOpenChange: (open: boolean) => void;
|
|
822
|
+
onSuccess: (result?: ImportResult) => void;
|
|
823
|
+
initialCompanyId?: number | null;
|
|
824
|
+
previewUrl?: string;
|
|
825
|
+
importUrl?: string;
|
|
826
|
+
title?: string;
|
|
827
|
+
description?: string;
|
|
828
|
+
nameRequired?: boolean;
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
export function PersonImportSheet({
|
|
832
|
+
open,
|
|
833
|
+
onOpenChange,
|
|
834
|
+
onSuccess,
|
|
835
|
+
initialCompanyId = null,
|
|
836
|
+
previewUrl = '/person/import/preview',
|
|
837
|
+
importUrl = '/person/import',
|
|
838
|
+
title,
|
|
839
|
+
description,
|
|
840
|
+
nameRequired = true,
|
|
841
|
+
}: PersonImportSheetProps) {
|
|
842
842
|
const t = useTranslations('contact.ContactPage');
|
|
843
843
|
const { request } = useApp();
|
|
844
844
|
|
|
@@ -886,8 +886,8 @@ export function PersonImportSheet({
|
|
|
886
886
|
}
|
|
887
887
|
onOpenChange(nextOpen);
|
|
888
888
|
},
|
|
889
|
-
[initialCompanyId, onOpenChange]
|
|
890
|
-
);
|
|
889
|
+
[initialCompanyId, onOpenChange]
|
|
890
|
+
);
|
|
891
891
|
|
|
892
892
|
// ── Auto-initialise mapping from columns ──
|
|
893
893
|
const initMapping = useCallback((columns: string[]) => {
|
|
@@ -899,16 +899,16 @@ export function PersonImportSheet({
|
|
|
899
899
|
}, []);
|
|
900
900
|
|
|
901
901
|
// ── Navigation ──
|
|
902
|
-
const canGoNext = (): boolean => {
|
|
903
|
-
if (step === 'upload') return !!file;
|
|
904
|
-
if (step === 'preview') return !!preview && !previewError;
|
|
905
|
-
if (step === 'mapping') {
|
|
906
|
-
const hasName = Object.values(mapping).includes('name');
|
|
907
|
-
const hasEmail = Object.values(mapping).includes('email');
|
|
908
|
-
return nameRequired ? hasName : hasEmail;
|
|
909
|
-
}
|
|
910
|
-
if (step === 'confirm') return true;
|
|
911
|
-
return false;
|
|
902
|
+
const canGoNext = (): boolean => {
|
|
903
|
+
if (step === 'upload') return !!file;
|
|
904
|
+
if (step === 'preview') return !!preview && !previewError;
|
|
905
|
+
if (step === 'mapping') {
|
|
906
|
+
const hasName = Object.values(mapping).includes('name');
|
|
907
|
+
const hasEmail = Object.values(mapping).includes('email');
|
|
908
|
+
return nameRequired ? hasName : hasEmail;
|
|
909
|
+
}
|
|
910
|
+
if (step === 'confirm') return true;
|
|
911
|
+
return false;
|
|
912
912
|
};
|
|
913
913
|
|
|
914
914
|
const handleNext = async () => {
|
|
@@ -926,19 +926,19 @@ export function PersonImportSheet({
|
|
|
926
926
|
await fetchPreview();
|
|
927
927
|
} else if (step === 'preview') {
|
|
928
928
|
setStep('mapping');
|
|
929
|
-
} else if (step === 'mapping') {
|
|
930
|
-
const hasName = Object.values(mapping).includes('name');
|
|
931
|
-
const hasEmail = Object.values(mapping).includes('email');
|
|
932
|
-
if (nameRequired && !hasName) {
|
|
933
|
-
setMappingError(t('importMappingNameRequired'));
|
|
934
|
-
return;
|
|
935
|
-
}
|
|
936
|
-
if (!nameRequired && !hasEmail) {
|
|
937
|
-
setMappingError('Mapeie uma coluna para Email.');
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
setMappingError(null);
|
|
941
|
-
setStep('confirm');
|
|
929
|
+
} else if (step === 'mapping') {
|
|
930
|
+
const hasName = Object.values(mapping).includes('name');
|
|
931
|
+
const hasEmail = Object.values(mapping).includes('email');
|
|
932
|
+
if (nameRequired && !hasName) {
|
|
933
|
+
setMappingError(t('importMappingNameRequired'));
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
if (!nameRequired && !hasEmail) {
|
|
937
|
+
setMappingError('Mapeie uma coluna para Email.');
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
setMappingError(null);
|
|
941
|
+
setStep('confirm');
|
|
942
942
|
} else if (step === 'confirm') {
|
|
943
943
|
await runImport();
|
|
944
944
|
}
|
|
@@ -961,17 +961,17 @@ export function PersonImportSheet({
|
|
|
961
961
|
const formData = new FormData();
|
|
962
962
|
formData.append('file', file);
|
|
963
963
|
|
|
964
|
-
const res = await request<ImportPreview>({
|
|
965
|
-
url: previewUrl,
|
|
966
|
-
method: 'POST',
|
|
967
|
-
data: formData,
|
|
964
|
+
const res = await request<ImportPreview>({
|
|
965
|
+
url: previewUrl,
|
|
966
|
+
method: 'POST',
|
|
967
|
+
data: formData,
|
|
968
968
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
969
969
|
});
|
|
970
970
|
|
|
971
971
|
setPreview(res.data);
|
|
972
972
|
initMapping(res.data.columns);
|
|
973
|
-
} catch (err: unknown) {
|
|
974
|
-
setPreviewError(getImportErrorMessage(err, t('importErrorGeneric')));
|
|
973
|
+
} catch (err: unknown) {
|
|
974
|
+
setPreviewError(getImportErrorMessage(err, t('importErrorGeneric')));
|
|
975
975
|
} finally {
|
|
976
976
|
setPreviewLoading(false);
|
|
977
977
|
}
|
|
@@ -990,17 +990,17 @@ export function PersonImportSheet({
|
|
|
990
990
|
formData.append('mapping', JSON.stringify(mapping));
|
|
991
991
|
if (companyId) formData.append('company_id', String(companyId));
|
|
992
992
|
|
|
993
|
-
const res = await request<ImportResult>({
|
|
994
|
-
url: importUrl,
|
|
995
|
-
method: 'POST',
|
|
996
|
-
data: formData,
|
|
993
|
+
const res = await request<ImportResult>({
|
|
994
|
+
url: importUrl,
|
|
995
|
+
method: 'POST',
|
|
996
|
+
data: formData,
|
|
997
997
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
998
998
|
});
|
|
999
|
-
|
|
1000
|
-
setResult(res.data);
|
|
1001
|
-
onSuccess(res.data);
|
|
1002
|
-
} catch (err: unknown) {
|
|
1003
|
-
setImportError(getImportErrorMessage(err, t('importErrorGeneric')));
|
|
999
|
+
|
|
1000
|
+
setResult(res.data);
|
|
1001
|
+
onSuccess(res.data);
|
|
1002
|
+
} catch (err: unknown) {
|
|
1003
|
+
setImportError(getImportErrorMessage(err, t('importErrorGeneric')));
|
|
1004
1004
|
} finally {
|
|
1005
1005
|
setImportLoading(false);
|
|
1006
1006
|
}
|
|
@@ -1019,12 +1019,12 @@ export function PersonImportSheet({
|
|
|
1019
1019
|
<Upload className="h-4 w-4 text-primary" />
|
|
1020
1020
|
</div>
|
|
1021
1021
|
<div>
|
|
1022
|
-
<SheetTitle className="text-base">
|
|
1023
|
-
{title ?? t('importSheetTitle')}
|
|
1024
|
-
</SheetTitle>
|
|
1025
|
-
<SheetDescription className="text-xs">
|
|
1026
|
-
{description ?? t('importSheetDescription')}
|
|
1027
|
-
</SheetDescription>
|
|
1022
|
+
<SheetTitle className="text-base">
|
|
1023
|
+
{title ?? t('importSheetTitle')}
|
|
1024
|
+
</SheetTitle>
|
|
1025
|
+
<SheetDescription className="text-xs">
|
|
1026
|
+
{description ?? t('importSheetDescription')}
|
|
1027
|
+
</SheetDescription>
|
|
1028
1028
|
</div>
|
|
1029
1029
|
</div>
|
|
1030
1030
|
</SheetHeader>
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
34
34
|
import { formatDate } from '@/lib/format-date';
|
|
35
35
|
import { cn } from '@/lib/utils';
|
|
36
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
36
37
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
37
38
|
import {
|
|
38
39
|
type ColumnDef,
|
|
@@ -328,7 +329,11 @@ export default function PeoplePage() {
|
|
|
328
329
|
|
|
329
330
|
const [sorting, setSorting] = useState<SortingState>([]);
|
|
330
331
|
const [page, setPage] = useState(1);
|
|
331
|
-
const [pageSize, setPageSize] =
|
|
332
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
333
|
+
storageKey: 'pagination:global:pageSize',
|
|
334
|
+
defaultValue: 12,
|
|
335
|
+
allowedValues: [12, 20, 30, 40, 50],
|
|
336
|
+
});
|
|
332
337
|
const [searchInput, setSearchInput] = useState('');
|
|
333
338
|
const [debouncedSearch, setDebouncedSearch] = useState('');
|
|
334
339
|
const [typeFilter, setTypeFilter] = useState('all');
|