@hed-hog/contact 0.0.332 → 0.0.338
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 +6 -7
- 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 +5 -2
- package/dist/person/person.service.d.ts.map +1 -1
- package/dist/person/person.service.js +57 -5
- package/dist/person/person.service.js.map +1 -1
- package/hedhog/data/role.yaml +9 -1
- package/hedhog/frontend/app/_components/person-picker.tsx.ejs +4 -4
- 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 +14 -24
- 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 +94 -25
package/hedhog/data/role.yaml
CHANGED
|
@@ -20,4 +20,12 @@
|
|
|
20
20
|
pt: Aprovador de Propostas
|
|
21
21
|
description:
|
|
22
22
|
en: User authorized to approve proposals.
|
|
23
|
-
pt: Usuario autorizado a aprovar propostas.
|
|
23
|
+
pt: Usuario autorizado a aprovar propostas.
|
|
24
|
+
|
|
25
|
+
- slug: person-user-linker
|
|
26
|
+
name:
|
|
27
|
+
en: Person User Linker
|
|
28
|
+
pt: Vinculador de Pessoa a Usuario
|
|
29
|
+
description:
|
|
30
|
+
en: User allowed to relate a person to a system user.
|
|
31
|
+
pt: Usuario permitido para relacionar uma pessoa com um usuario do sistema.
|
|
@@ -921,7 +921,7 @@ export function PersonPicker({
|
|
|
921
921
|
</PopoverContent>
|
|
922
922
|
</Popover>
|
|
923
923
|
|
|
924
|
-
{
|
|
924
|
+
{canEditSelection && clearable ? (
|
|
925
925
|
<Button
|
|
926
926
|
type="button"
|
|
927
927
|
variant="outline"
|
|
@@ -941,15 +941,15 @@ export function PersonPicker({
|
|
|
941
941
|
</Button>
|
|
942
942
|
) : null}
|
|
943
943
|
|
|
944
|
-
{showEditButton ? (
|
|
944
|
+
{showEditButton && canEditSelection ? (
|
|
945
945
|
<Button
|
|
946
946
|
type="button"
|
|
947
947
|
variant="outline"
|
|
948
948
|
size="icon"
|
|
949
949
|
className="shrink-0"
|
|
950
|
-
disabled={disabled
|
|
950
|
+
disabled={disabled}
|
|
951
951
|
onClick={() => {
|
|
952
|
-
if (!
|
|
952
|
+
if (!onEditSelection) {
|
|
953
953
|
return;
|
|
954
954
|
}
|
|
955
955
|
onEditSelection(selectedPersonId);
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
} from '@/components/ui/table';
|
|
41
41
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
42
42
|
import { getFormDraftOwnerKey } from '@/hooks/use-form-draft';
|
|
43
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
43
44
|
import { formatDate } from '@/lib/format-date';
|
|
44
45
|
import { cn } from '@/lib/utils';
|
|
45
46
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
@@ -163,7 +164,11 @@ export default function AccountsPage() {
|
|
|
163
164
|
|
|
164
165
|
const [sorting, setSorting] = useState<SortingState>([]);
|
|
165
166
|
const [page, setPage] = useState(1);
|
|
166
|
-
const [pageSize, setPageSize] =
|
|
167
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
168
|
+
storageKey: 'pagination:global:pageSize',
|
|
169
|
+
defaultValue: 12,
|
|
170
|
+
allowedValues: [6, 12, 24, 48],
|
|
171
|
+
});
|
|
167
172
|
const [searchInput, setSearchInput] = useState('');
|
|
168
173
|
const [debouncedSearch, setDebouncedSearch] = useState('');
|
|
169
174
|
const [statusFilter, setStatusFilter] = useState('all');
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
TableRow,
|
|
23
23
|
} from '@/components/ui/table';
|
|
24
24
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
25
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
25
26
|
import { formatDateTime } from '@/lib/format-date';
|
|
26
27
|
import { cn } from '@/lib/utils';
|
|
27
28
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
@@ -95,7 +96,11 @@ export default function CrmActivitiesPage() {
|
|
|
95
96
|
const [typeFilter, setTypeFilter] = useState('all');
|
|
96
97
|
const [priorityFilter, setPriorityFilter] = useState('all');
|
|
97
98
|
const [page, setPage] = useState(1);
|
|
98
|
-
const [pageSize, setPageSize] =
|
|
99
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
100
|
+
storageKey: 'pagination:global:pageSize',
|
|
101
|
+
defaultValue: 12,
|
|
102
|
+
allowedValues: [6, 12, 24, 48],
|
|
103
|
+
});
|
|
99
104
|
const [viewMode, setViewMode] = useState<ActivityViewMode>('table');
|
|
100
105
|
const [selectedActivityId, setSelectedActivityId] = useState<number | null>(
|
|
101
106
|
null
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
TableRow,
|
|
53
53
|
} from '@/components/ui/table';
|
|
54
54
|
import { useFormDraft } from '@/hooks/use-form-draft';
|
|
55
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
55
56
|
import { formatDate, formatDateTime } from '@/lib/format-date';
|
|
56
57
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
57
58
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
@@ -126,7 +127,11 @@ export default function ContactTypePage() {
|
|
|
126
127
|
useState<ContactType | null>(null);
|
|
127
128
|
|
|
128
129
|
const [page, setPage] = useState(1);
|
|
129
|
-
const [pageSize, setPageSize] =
|
|
130
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
131
|
+
storageKey: 'pagination:global:pageSize',
|
|
132
|
+
defaultValue: 10,
|
|
133
|
+
allowedValues: [10, 20, 30, 40, 50],
|
|
134
|
+
});
|
|
130
135
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
131
136
|
const [deletingId, setDeletingId] = useState<number | null>(null);
|
|
132
137
|
|
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
} from '@/components/ui/table';
|
|
62
62
|
import { COUNTRIES } from '@/constants/countries';
|
|
63
63
|
import { useFormDraft } from '@/hooks/use-form-draft';
|
|
64
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
64
65
|
import { formatDate, formatDateTime } from '@/lib/format-date';
|
|
65
66
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
66
67
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
@@ -143,7 +144,11 @@ export default function DocumentTypePage() {
|
|
|
143
144
|
useState<DocumentType | null>(null);
|
|
144
145
|
|
|
145
146
|
const [page, setPage] = useState(1);
|
|
146
|
-
const [pageSize, setPageSize] =
|
|
147
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
148
|
+
storageKey: 'pagination:global:pageSize',
|
|
149
|
+
defaultValue: 10,
|
|
150
|
+
allowedValues: [10, 20, 30, 40, 50],
|
|
151
|
+
});
|
|
147
152
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
148
153
|
const [deletingId, setDeletingId] = useState<number | null>(null);
|
|
149
154
|
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
import { Textarea } from '@/components/ui/textarea';
|
|
55
55
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
56
56
|
import { useFormDraft } from '@/hooks/use-form-draft';
|
|
57
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
57
58
|
import { formatDateTime } from '@/lib/format-date';
|
|
58
59
|
import { cn } from '@/lib/utils';
|
|
59
60
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
@@ -206,7 +207,11 @@ export default function CrmFollowupsPage() {
|
|
|
206
207
|
const [dateFrom, setDateFrom] = useState('');
|
|
207
208
|
const [dateTo, setDateTo] = useState('');
|
|
208
209
|
const [page, setPage] = useState(1);
|
|
209
|
-
const [pageSize, setPageSize] =
|
|
210
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
211
|
+
storageKey: 'pagination:global:pageSize',
|
|
212
|
+
defaultValue: 12,
|
|
213
|
+
allowedValues: [6, 12, 24, 48],
|
|
214
|
+
});
|
|
210
215
|
const [viewMode, setViewMode] = useState<FollowupViewMode>('table');
|
|
211
216
|
const [sheetOpen, setSheetOpen] = useState(false);
|
|
212
217
|
const [personPickerOpen, setPersonPickerOpen] = useState(false);
|
|
@@ -708,8 +708,10 @@ export function PersonFormSheet({
|
|
|
708
708
|
allowedTypes,
|
|
709
709
|
}: PersonFormSheetProps) {
|
|
710
710
|
const t = useTranslations('contact.ContactPage');
|
|
711
|
-
const { request, currentLocaleCode, getSettingValue, user } =
|
|
711
|
+
const { request, currentLocaleCode, getSettingValue, user, hasRole } =
|
|
712
|
+
useApp();
|
|
712
713
|
const isEditing = Boolean(person);
|
|
714
|
+
const canEditLinkedUser = hasRole('person-user-linker');
|
|
713
715
|
const allowCompanyRegistration =
|
|
714
716
|
getSettingValue('contact-allow-company-registration') !== false;
|
|
715
717
|
const effectiveAllowedTypes = useMemo<Array<Person['type']>>(() => {
|
|
@@ -1146,11 +1148,6 @@ export function PersonFormSheet({
|
|
|
1146
1148
|
? `${process.env.NEXT_PUBLIC_API_BASE_URL}/person/avatar/${fileId}`
|
|
1147
1149
|
: '/placeholder.png';
|
|
1148
1150
|
|
|
1149
|
-
const getUserPhotoUrl = (photoId?: number | null) =>
|
|
1150
|
-
typeof photoId === 'number' && photoId > 0
|
|
1151
|
-
? `${process.env.NEXT_PUBLIC_API_BASE_URL}/user/avatar/${photoId}`
|
|
1152
|
-
: undefined;
|
|
1153
|
-
|
|
1154
1151
|
const resolveApiUrl = (url: string) =>
|
|
1155
1152
|
/^https?:\/\//i.test(url)
|
|
1156
1153
|
? url
|
|
@@ -2500,10 +2497,17 @@ export function PersonFormSheet({
|
|
|
2500
2497
|
</div>
|
|
2501
2498
|
</CollapsibleTrigger>
|
|
2502
2499
|
|
|
2503
|
-
<CollapsibleContent
|
|
2500
|
+
<CollapsibleContent
|
|
2501
|
+
className={cn(
|
|
2502
|
+
'mt-2',
|
|
2503
|
+
!canEditLinkedUser && 'opacity-60 pointer-events-none'
|
|
2504
|
+
)}
|
|
2505
|
+
>
|
|
2504
2506
|
<EntityPicker
|
|
2507
|
+
disabled={!canEditLinkedUser}
|
|
2505
2508
|
value={linkedUserId}
|
|
2506
2509
|
onChange={(val, opt) => {
|
|
2510
|
+
if (!canEditLinkedUser) return;
|
|
2507
2511
|
setLinkedUserId(val as number | null);
|
|
2508
2512
|
setLinkedUserLabel(
|
|
2509
2513
|
opt
|
|
@@ -2513,31 +2517,17 @@ export function PersonFormSheet({
|
|
|
2513
2517
|
'')
|
|
2514
2518
|
: ''
|
|
2515
2519
|
);
|
|
2516
|
-
const userPhotoId = (
|
|
2517
|
-
opt as { photo_id?: number | null } | null
|
|
2518
|
-
)?.photo_id;
|
|
2519
|
-
if (val != null && userPhotoId && !avatarId) {
|
|
2520
|
-
setAvatarId(userPhotoId);
|
|
2521
|
-
setAvatarPreviewUrl(
|
|
2522
|
-
getUserPhotoUrl(userPhotoId) ?? '/placeholder.png'
|
|
2523
|
-
);
|
|
2524
|
-
}
|
|
2525
2520
|
}}
|
|
2526
2521
|
placeholder={t('selectLinkedUser')}
|
|
2527
2522
|
entityLabel={t('user')}
|
|
2528
|
-
clearable
|
|
2523
|
+
clearable={canEditLinkedUser}
|
|
2529
2524
|
valueType="number"
|
|
2530
2525
|
initialSelectedLabel={linkedUserLabel}
|
|
2531
2526
|
loadOptions={async ({ search }) => {
|
|
2532
2527
|
const params = new URLSearchParams();
|
|
2533
2528
|
if (search) params.set('search', search);
|
|
2534
2529
|
const response = await request<
|
|
2535
|
-
Array<{
|
|
2536
|
-
id: number;
|
|
2537
|
-
name: string;
|
|
2538
|
-
email?: string;
|
|
2539
|
-
photo_id?: number | null;
|
|
2540
|
-
}>
|
|
2530
|
+
Array<{ id: number; name: string; email?: string }>
|
|
2541
2531
|
>({
|
|
2542
2532
|
url: `/person/linked-user-options?${params.toString()}`,
|
|
2543
2533
|
method: 'GET',
|
|
@@ -2554,7 +2544,7 @@ export function PersonFormSheet({
|
|
|
2554
2544
|
getOptionDescription={(opt) =>
|
|
2555
2545
|
(opt as { email?: string }).email
|
|
2556
2546
|
}
|
|
2557
|
-
showCreateButton
|
|
2547
|
+
showCreateButton={canEditLinkedUser}
|
|
2558
2548
|
createTitle={t('createUserTitle')}
|
|
2559
2549
|
mapSearchToCreateValues={(search) => ({
|
|
2560
2550
|
name: search,
|
|
@@ -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');
|