@hed-hog/contact 0.0.270 → 0.0.274
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 +470 -0
- package/dist/person/dto/interaction-create.dto.d.ts +16 -0
- package/dist/person/dto/interaction-create.dto.d.ts.map +1 -0
- package/dist/person/dto/interaction-create.dto.js +57 -0
- package/dist/person/dto/interaction-create.dto.js.map +1 -0
- package/dist/person/person.controller.d.ts +4 -0
- package/dist/person/person.controller.d.ts.map +1 -1
- package/dist/person/person.controller.js +10 -0
- package/dist/person/person.controller.js.map +1 -1
- package/dist/person/person.service.d.ts +12 -3
- package/dist/person/person.service.d.ts.map +1 -1
- package/dist/person/person.service.js +62 -11
- package/dist/person/person.service.js.map +1 -1
- package/hedhog/data/role.yaml +9 -1
- package/hedhog/data/route.yaml +8 -0
- package/hedhog/frontend/app/person/_components/person-field-with-create.tsx.ejs +461 -461
- package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +865 -585
- package/hedhog/frontend/app/person/_components/person-types.ts.ejs +157 -115
- package/hedhog/frontend/app/person/page.tsx.ejs +1353 -1209
- package/hedhog/frontend/messages/pt.json +41 -0
- package/hedhog/table/person_individual.yaml +17 -17
- package/package.json +3 -3
- package/src/language/en.json +9 -9
- package/src/language/pt.json +9 -9
- package/src/person/dto/create.dto.ts +62 -62
- package/src/person/dto/update.dto.ts +147 -147
- package/src/person/person.controller.ts +48 -43
- package/src/person/person.service.ts +1137 -1071
|
@@ -26,21 +26,21 @@ import {
|
|
|
26
26
|
SelectValue,
|
|
27
27
|
} from '@/components/ui/select';
|
|
28
28
|
import { Separator } from '@/components/ui/separator';
|
|
29
|
-
import {
|
|
30
|
-
Sheet,
|
|
31
|
-
SheetContent,
|
|
32
|
-
SheetDescription,
|
|
33
|
-
SheetHeader,
|
|
34
|
-
SheetTitle,
|
|
35
|
-
} from '@/components/ui/sheet';
|
|
36
|
-
import {
|
|
37
|
-
Tooltip,
|
|
38
|
-
TooltipContent,
|
|
39
|
-
TooltipTrigger,
|
|
40
|
-
} from '@/components/ui/tooltip';
|
|
29
|
+
import {
|
|
30
|
+
Sheet,
|
|
31
|
+
SheetContent,
|
|
32
|
+
SheetDescription,
|
|
33
|
+
SheetHeader,
|
|
34
|
+
SheetTitle,
|
|
35
|
+
} from '@/components/ui/sheet';
|
|
36
|
+
import {
|
|
37
|
+
Tooltip,
|
|
38
|
+
TooltipContent,
|
|
39
|
+
TooltipTrigger,
|
|
40
|
+
} from '@/components/ui/tooltip';
|
|
41
41
|
import { COUNTRIES } from '@/constants/countries';
|
|
42
42
|
import { cn } from '@/lib/utils';
|
|
43
|
-
import { useApp } from '@hed-hog/next-app-provider';
|
|
43
|
+
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
44
44
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
45
45
|
import { format } from 'date-fns';
|
|
46
46
|
import { enUS, ptBR } from 'date-fns/locale';
|
|
@@ -75,6 +75,9 @@ import {
|
|
|
75
75
|
type PersonContact,
|
|
76
76
|
type PersonDocument,
|
|
77
77
|
type PersonGender,
|
|
78
|
+
type PersonLifecycleStage,
|
|
79
|
+
type PersonSource,
|
|
80
|
+
type UserOption,
|
|
78
81
|
} from './person-types';
|
|
79
82
|
|
|
80
83
|
type PersonFormSheetProps = {
|
|
@@ -97,6 +100,10 @@ type PersonFormValues = {
|
|
|
97
100
|
trade_name?: string | null;
|
|
98
101
|
foundation_date?: Date | null;
|
|
99
102
|
legal_nature?: string | null;
|
|
103
|
+
owner_user_id?: number | null;
|
|
104
|
+
source?: PersonSource | null;
|
|
105
|
+
lifecycle_stage?: PersonLifecycleStage | null;
|
|
106
|
+
next_action_at?: string | null;
|
|
100
107
|
};
|
|
101
108
|
|
|
102
109
|
type CreatePersonPayload = {
|
|
@@ -115,73 +122,81 @@ type OpenFilePayload = {
|
|
|
115
122
|
url?: string;
|
|
116
123
|
};
|
|
117
124
|
|
|
118
|
-
type EditablePersonContact = Omit<PersonContact, 'contact_type_id'> & {
|
|
119
|
-
clientId: string;
|
|
120
|
-
contact_type_id: number | null;
|
|
121
|
-
};
|
|
125
|
+
type EditablePersonContact = Omit<PersonContact, 'contact_type_id'> & {
|
|
126
|
+
clientId: string;
|
|
127
|
+
contact_type_id: number | null;
|
|
128
|
+
};
|
|
122
129
|
|
|
123
130
|
type EditablePersonAddress = PersonAddress & {
|
|
124
131
|
clientId: string;
|
|
125
132
|
};
|
|
126
133
|
|
|
127
|
-
type EditablePersonDocument = Omit<PersonDocument, 'document_type_id'> & {
|
|
128
|
-
clientId: string;
|
|
129
|
-
document_type_id: number | null;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
function createClientId(prefix: string) {
|
|
133
|
-
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function onlyDigits(value: string) {
|
|
137
|
-
return value.replace(/\D/g, '');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function applyPhoneMask(value: string) {
|
|
141
|
-
const digits = onlyDigits(value).slice(0, 11);
|
|
142
|
-
|
|
143
|
-
if (digits.length <= 2) {
|
|
144
|
-
return digits.length ? `(${digits}` : '';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (digits.length <= 6) {
|
|
148
|
-
return `(${digits.slice(0, 2)}) ${digits.slice(2)}`;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (digits.length <= 10) {
|
|
152
|
-
return `(${digits.slice(0, 2)}) ${digits.slice(2, 6)}-${digits.slice(6)}`;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return `(${digits.slice(0, 2)}) ${digits.slice(2, 7)}-${digits.slice(7)}`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function applyCpfMask(value: string) {
|
|
159
|
-
const digits = onlyDigits(value).slice(0, 11);
|
|
160
|
-
|
|
161
|
-
if (digits.length <= 3) return digits;
|
|
162
|
-
if (digits.length <= 6)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (digits.length <=
|
|
174
|
-
if (digits.length <=
|
|
175
|
-
return `${digits.slice(0, 2)}.${digits.slice(2)}`;
|
|
176
|
-
if (digits.length <=
|
|
177
|
-
return `${digits.slice(0, 2)}.${digits.slice(2, 5)}.${digits.slice(5)}`;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
134
|
+
type EditablePersonDocument = Omit<PersonDocument, 'document_type_id'> & {
|
|
135
|
+
clientId: string;
|
|
136
|
+
document_type_id: number | null;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
function createClientId(prefix: string) {
|
|
140
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function onlyDigits(value: string) {
|
|
144
|
+
return value.replace(/\D/g, '');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function applyPhoneMask(value: string) {
|
|
148
|
+
const digits = onlyDigits(value).slice(0, 11);
|
|
149
|
+
|
|
150
|
+
if (digits.length <= 2) {
|
|
151
|
+
return digits.length ? `(${digits}` : '';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (digits.length <= 6) {
|
|
155
|
+
return `(${digits.slice(0, 2)}) ${digits.slice(2)}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (digits.length <= 10) {
|
|
159
|
+
return `(${digits.slice(0, 2)}) ${digits.slice(2, 6)}-${digits.slice(6)}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return `(${digits.slice(0, 2)}) ${digits.slice(2, 7)}-${digits.slice(7)}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function applyCpfMask(value: string) {
|
|
166
|
+
const digits = onlyDigits(value).slice(0, 11);
|
|
167
|
+
|
|
168
|
+
if (digits.length <= 3) return digits;
|
|
169
|
+
if (digits.length <= 6) return `${digits.slice(0, 3)}.${digits.slice(3)}`;
|
|
170
|
+
if (digits.length <= 9)
|
|
171
|
+
return `${digits.slice(0, 3)}.${digits.slice(3, 6)}.${digits.slice(6)}`;
|
|
172
|
+
|
|
173
|
+
return `${digits.slice(0, 3)}.${digits.slice(3, 6)}.${digits.slice(6, 9)}-${digits.slice(9)}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function applyCnpjMask(value: string) {
|
|
177
|
+
const digits = onlyDigits(value).slice(0, 14);
|
|
178
|
+
|
|
179
|
+
if (digits.length <= 2) return digits;
|
|
180
|
+
if (digits.length <= 5) return `${digits.slice(0, 2)}.${digits.slice(2)}`;
|
|
181
|
+
if (digits.length <= 8)
|
|
182
|
+
return `${digits.slice(0, 2)}.${digits.slice(2, 5)}.${digits.slice(5)}`;
|
|
183
|
+
if (digits.length <= 12)
|
|
184
|
+
return `${digits.slice(0, 2)}.${digits.slice(2, 5)}.${digits.slice(5, 8)}/${digits.slice(8)}`;
|
|
185
|
+
|
|
186
|
+
return `${digits.slice(0, 2)}.${digits.slice(2, 5)}.${digits.slice(5, 8)}/${digits.slice(8, 12)}-${digits.slice(12)}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function toDatetimeLocalValue(value?: string | null) {
|
|
190
|
+
if (!value) return '';
|
|
191
|
+
const date = new Date(value);
|
|
192
|
+
if (Number.isNaN(date.getTime())) return '';
|
|
193
|
+
|
|
194
|
+
const offset = date.getTimezoneOffset();
|
|
195
|
+
const localDate = new Date(date.getTime() - offset * 60 * 1000);
|
|
196
|
+
return localDate.toISOString().slice(0, 16);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function fetchViaCep(cep: string) {
|
|
185
200
|
try {
|
|
186
201
|
const response = await fetch(`https://viacep.com.br/ws/${cep}/json/`);
|
|
187
202
|
if (!response.ok) return null;
|
|
@@ -253,7 +268,7 @@ function DatePickerWithYearMonth({
|
|
|
253
268
|
type="button"
|
|
254
269
|
variant="outline"
|
|
255
270
|
className={cn(
|
|
256
|
-
'h-9 w-full min-w-0 justify-start text-left font-normal',
|
|
271
|
+
'h-9 w-full min-w-0 justify-start text-left text-xs font-normal',
|
|
257
272
|
!date && 'text-muted-foreground'
|
|
258
273
|
)}
|
|
259
274
|
>
|
|
@@ -324,7 +339,7 @@ export function PersonFormSheet({
|
|
|
324
339
|
onSuccess,
|
|
325
340
|
}: PersonFormSheetProps) {
|
|
326
341
|
const t = useTranslations('contact.ContactPage');
|
|
327
|
-
const { request, currentLocaleCode, getSettingValue } = useApp();
|
|
342
|
+
const { request, currentLocaleCode, getSettingValue, user } = useApp();
|
|
328
343
|
const isEditing = Boolean(person);
|
|
329
344
|
const allowCompanyRegistration =
|
|
330
345
|
getSettingValue('contact-allow-company-registration') !== false;
|
|
@@ -343,6 +358,31 @@ export function PersonFormSheet({
|
|
|
343
358
|
trade_name: z.string().nullable().optional(),
|
|
344
359
|
foundation_date: z.date().nullable().optional(),
|
|
345
360
|
legal_nature: z.string().nullable().optional(),
|
|
361
|
+
owner_user_id: z.number().int().nullable().optional(),
|
|
362
|
+
source: z
|
|
363
|
+
.enum([
|
|
364
|
+
'referral',
|
|
365
|
+
'website',
|
|
366
|
+
'social',
|
|
367
|
+
'inbound',
|
|
368
|
+
'outbound',
|
|
369
|
+
'other',
|
|
370
|
+
])
|
|
371
|
+
.nullable()
|
|
372
|
+
.optional(),
|
|
373
|
+
lifecycle_stage: z
|
|
374
|
+
.enum([
|
|
375
|
+
'new',
|
|
376
|
+
'contacted',
|
|
377
|
+
'qualified',
|
|
378
|
+
'proposal',
|
|
379
|
+
'negotiation',
|
|
380
|
+
'customer',
|
|
381
|
+
'lost',
|
|
382
|
+
])
|
|
383
|
+
.nullable()
|
|
384
|
+
.optional(),
|
|
385
|
+
next_action_at: z.string().nullable().optional(),
|
|
346
386
|
}),
|
|
347
387
|
[t]
|
|
348
388
|
);
|
|
@@ -360,6 +400,10 @@ export function PersonFormSheet({
|
|
|
360
400
|
trade_name: '',
|
|
361
401
|
foundation_date: null,
|
|
362
402
|
legal_nature: '',
|
|
403
|
+
owner_user_id: null,
|
|
404
|
+
source: null,
|
|
405
|
+
lifecycle_stage: 'new',
|
|
406
|
+
next_action_at: '',
|
|
363
407
|
},
|
|
364
408
|
});
|
|
365
409
|
const {
|
|
@@ -383,32 +427,46 @@ export function PersonFormSheet({
|
|
|
383
427
|
const [persistedAvatarId, setPersistedAvatarId] = useState<number | null>(
|
|
384
428
|
null
|
|
385
429
|
);
|
|
386
|
-
const [avatarPreviewUrl, setAvatarPreviewUrl] =
|
|
387
|
-
useState<string>('/placeholder.png');
|
|
388
|
-
const [isUploadingAvatar, setIsUploadingAvatar] = useState(false);
|
|
389
|
-
const [avatarUploadProgress, setAvatarUploadProgress] = useState(0);
|
|
390
|
-
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
391
|
-
const hasSavedChangesRef = useRef(false);
|
|
392
|
-
const contactValueRefs = useRef<Record<string, HTMLInputElement | null>>({});
|
|
393
|
-
const addressLine1Refs = useRef<Record<string, HTMLInputElement | null>>({});
|
|
394
|
-
const documentValueRefs = useRef<Record<string, HTMLInputElement | null>>({});
|
|
395
|
-
|
|
396
|
-
const focusWhenAvailable = (getElement: () => HTMLInputElement | null) => {
|
|
397
|
-
requestAnimationFrame(() => {
|
|
398
|
-
const element = getElement();
|
|
399
|
-
if (!element) {
|
|
400
|
-
setTimeout(() => {
|
|
401
|
-
getElement()?.focus();
|
|
402
|
-
}, 0);
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
element.focus();
|
|
407
|
-
});
|
|
408
|
-
};
|
|
430
|
+
const [avatarPreviewUrl, setAvatarPreviewUrl] =
|
|
431
|
+
useState<string>('/placeholder.png');
|
|
432
|
+
const [isUploadingAvatar, setIsUploadingAvatar] = useState(false);
|
|
433
|
+
const [avatarUploadProgress, setAvatarUploadProgress] = useState(0);
|
|
434
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
435
|
+
const hasSavedChangesRef = useRef(false);
|
|
436
|
+
const contactValueRefs = useRef<Record<string, HTMLInputElement | null>>({});
|
|
437
|
+
const addressLine1Refs = useRef<Record<string, HTMLInputElement | null>>({});
|
|
438
|
+
const documentValueRefs = useRef<Record<string, HTMLInputElement | null>>({});
|
|
439
|
+
|
|
440
|
+
const focusWhenAvailable = (getElement: () => HTMLInputElement | null) => {
|
|
441
|
+
requestAnimationFrame(() => {
|
|
442
|
+
const element = getElement();
|
|
443
|
+
if (!element) {
|
|
444
|
+
setTimeout(() => {
|
|
445
|
+
getElement()?.focus();
|
|
446
|
+
}, 0);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
element.focus();
|
|
451
|
+
});
|
|
452
|
+
};
|
|
409
453
|
|
|
410
454
|
const watchType = watch('type');
|
|
411
455
|
|
|
456
|
+
const { data: ownerOptions = [] } = useQuery<UserOption[]>({
|
|
457
|
+
queryKey: ['contact-person-owner-options', currentLocaleCode],
|
|
458
|
+
queryFn: async () => {
|
|
459
|
+
const response = await request<UserOption[] | { data?: UserOption[] }>({
|
|
460
|
+
url: '/person/owner-options',
|
|
461
|
+
method: 'GET',
|
|
462
|
+
});
|
|
463
|
+
return Array.isArray(response.data)
|
|
464
|
+
? response.data
|
|
465
|
+
: response.data?.data || [];
|
|
466
|
+
},
|
|
467
|
+
placeholderData: (old: UserOption[] | undefined) => old ?? [],
|
|
468
|
+
});
|
|
469
|
+
|
|
412
470
|
useEffect(() => {
|
|
413
471
|
if (canUseCompanyType || watchType !== 'company') {
|
|
414
472
|
return;
|
|
@@ -436,6 +494,12 @@ export function PersonFormSheet({
|
|
|
436
494
|
? new Date(person.foundation_date)
|
|
437
495
|
: null,
|
|
438
496
|
legal_nature: person?.legal_nature || '',
|
|
497
|
+
owner_user_id:
|
|
498
|
+
person?.owner_user_id ??
|
|
499
|
+
(person ? null : Number(user?.id || 0) || null),
|
|
500
|
+
source: person?.source || null,
|
|
501
|
+
lifecycle_stage: person?.lifecycle_stage || 'new',
|
|
502
|
+
next_action_at: toDatetimeLocalValue(person?.next_action_at || null),
|
|
439
503
|
});
|
|
440
504
|
const initialAvatarId = person?.avatar_id ?? null;
|
|
441
505
|
setAvatarId(initialAvatarId);
|
|
@@ -444,17 +508,17 @@ export function PersonFormSheet({
|
|
|
444
508
|
setIsUploadingAvatar(false);
|
|
445
509
|
setAvatarUploadProgress(0);
|
|
446
510
|
|
|
447
|
-
setContacts(
|
|
448
|
-
(person?.contact || []).map((contact, index) => ({
|
|
449
|
-
...contact,
|
|
450
|
-
value: ['PHONE', 'MOBILE', 'WHATSAPP'].includes(
|
|
451
|
-
String(contact.contact_type?.code || '').toUpperCase()
|
|
452
|
-
)
|
|
453
|
-
? applyPhoneMask(contact.value || '')
|
|
454
|
-
: contact.value || '',
|
|
455
|
-
clientId: `contact-${contact.id ?? index}`,
|
|
456
|
-
}))
|
|
457
|
-
);
|
|
511
|
+
setContacts(
|
|
512
|
+
(person?.contact || []).map((contact, index) => ({
|
|
513
|
+
...contact,
|
|
514
|
+
value: ['PHONE', 'MOBILE', 'WHATSAPP'].includes(
|
|
515
|
+
String(contact.contact_type?.code || '').toUpperCase()
|
|
516
|
+
)
|
|
517
|
+
? applyPhoneMask(contact.value || '')
|
|
518
|
+
: contact.value || '',
|
|
519
|
+
clientId: `contact-${contact.id ?? index}`,
|
|
520
|
+
}))
|
|
521
|
+
);
|
|
458
522
|
|
|
459
523
|
setAddresses(
|
|
460
524
|
(person?.address || []).map((address, index) => ({
|
|
@@ -463,25 +527,25 @@ export function PersonFormSheet({
|
|
|
463
527
|
}))
|
|
464
528
|
);
|
|
465
529
|
|
|
466
|
-
setDocuments(
|
|
467
|
-
(person?.document || []).map((document, index) => ({
|
|
468
|
-
...document,
|
|
469
|
-
value:
|
|
470
|
-
String(document.document_type?.code || '').toUpperCase() === 'CPF'
|
|
471
|
-
? applyCpfMask(document.value || '')
|
|
472
|
-
: String(document.document_type?.code || '').toUpperCase() ===
|
|
473
|
-
'CNPJ'
|
|
474
|
-
? applyCnpjMask(document.value || '')
|
|
475
|
-
: document.value || '',
|
|
476
|
-
clientId: `document-${document.id ?? index}`,
|
|
477
|
-
}))
|
|
478
|
-
);
|
|
530
|
+
setDocuments(
|
|
531
|
+
(person?.document || []).map((document, index) => ({
|
|
532
|
+
...document,
|
|
533
|
+
value:
|
|
534
|
+
String(document.document_type?.code || '').toUpperCase() === 'CPF'
|
|
535
|
+
? applyCpfMask(document.value || '')
|
|
536
|
+
: String(document.document_type?.code || '').toUpperCase() ===
|
|
537
|
+
'CNPJ'
|
|
538
|
+
? applyCnpjMask(document.value || '')
|
|
539
|
+
: document.value || '',
|
|
540
|
+
clientId: `document-${document.id ?? index}`,
|
|
541
|
+
}))
|
|
542
|
+
);
|
|
479
543
|
|
|
480
544
|
setContactsOpen(true);
|
|
481
545
|
setAddressesOpen(true);
|
|
482
546
|
setDocumentsOpen(true);
|
|
483
547
|
setLoadingCEP({});
|
|
484
|
-
}, [allowCompanyRegistration, open, person, reset]);
|
|
548
|
+
}, [allowCompanyRegistration, open, person, reset, user?.id]);
|
|
485
549
|
|
|
486
550
|
const getPersonInitials = (name: string) =>
|
|
487
551
|
name
|
|
@@ -630,99 +694,128 @@ export function PersonFormSheet({
|
|
|
630
694
|
onOpenChange(false);
|
|
631
695
|
};
|
|
632
696
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
};
|
|
686
|
-
|
|
687
|
-
const
|
|
688
|
-
|
|
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
|
-
|
|
697
|
+
useEffect(() => {
|
|
698
|
+
if (!open) {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
703
|
+
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {
|
|
704
|
+
event.preventDefault();
|
|
705
|
+
const formElement = document.getElementById(
|
|
706
|
+
'person-form'
|
|
707
|
+
) as HTMLFormElement | null;
|
|
708
|
+
formElement?.requestSubmit();
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (event.key === 'Escape' && !isSubmitting && !isUploadingAvatar) {
|
|
713
|
+
event.preventDefault();
|
|
714
|
+
handleSheetOpenChange(false);
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
window.addEventListener('keydown', onKeyDown);
|
|
719
|
+
return () => window.removeEventListener('keydown', onKeyDown);
|
|
720
|
+
}, [open, isSubmitting, isUploadingAvatar, handleSheetOpenChange]);
|
|
721
|
+
|
|
722
|
+
const getContactTypeCode = (contactTypeId?: number | null) =>
|
|
723
|
+
String(
|
|
724
|
+
contactTypes.find(
|
|
725
|
+
(contactType) => contactType.contact_type_id === contactTypeId
|
|
726
|
+
)?.code || ''
|
|
727
|
+
).toUpperCase();
|
|
728
|
+
|
|
729
|
+
const getDocumentTypeCode = (documentTypeId?: number | null) =>
|
|
730
|
+
String(
|
|
731
|
+
documentTypes.find(
|
|
732
|
+
(documentType) => documentType.document_type_id === documentTypeId
|
|
733
|
+
)?.code || ''
|
|
734
|
+
).toUpperCase();
|
|
735
|
+
|
|
736
|
+
const maskContactValueByType = (
|
|
737
|
+
value: string,
|
|
738
|
+
contactTypeId?: number | null,
|
|
739
|
+
fallbackCode?: string
|
|
740
|
+
) => {
|
|
741
|
+
const code = (
|
|
742
|
+
fallbackCode || getContactTypeCode(contactTypeId)
|
|
743
|
+
).toUpperCase();
|
|
744
|
+
if (['PHONE', 'MOBILE', 'WHATSAPP'].includes(code)) {
|
|
745
|
+
return applyPhoneMask(value);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return value;
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
const maskDocumentValueByType = (
|
|
752
|
+
value: string,
|
|
753
|
+
documentTypeId?: number | null,
|
|
754
|
+
fallbackCode?: string
|
|
755
|
+
) => {
|
|
756
|
+
const code = (
|
|
757
|
+
fallbackCode || getDocumentTypeCode(documentTypeId)
|
|
758
|
+
).toUpperCase();
|
|
759
|
+
|
|
760
|
+
if (code === 'CPF') {
|
|
761
|
+
return applyCpfMask(value);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
if (code === 'CNPJ') {
|
|
765
|
+
return applyCnpjMask(value);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
return value;
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
const resolveContactTypeIdByCodes = (codes: string[]) => {
|
|
772
|
+
const normalizedCodes = codes.map((code) => code.toUpperCase());
|
|
773
|
+
return (
|
|
774
|
+
contactTypes.find((contactType) =>
|
|
775
|
+
normalizedCodes.includes(String(contactType.code || '').toUpperCase())
|
|
776
|
+
)?.contact_type_id || null
|
|
777
|
+
);
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
const resolveDocumentTypeIdByCodes = (codes: string[]) => {
|
|
781
|
+
const normalizedCodes = codes.map((code) => code.toUpperCase());
|
|
782
|
+
return (
|
|
783
|
+
documentTypes.find((documentType) =>
|
|
784
|
+
normalizedCodes.includes(String(documentType.code || '').toUpperCase())
|
|
785
|
+
)?.document_type_id || null
|
|
786
|
+
);
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
const addContact = (preset: 'email' | 'phone' | 'blank' = 'blank') => {
|
|
790
|
+
const presetTypeId =
|
|
791
|
+
preset === 'email'
|
|
792
|
+
? resolveContactTypeIdByCodes(['EMAIL'])
|
|
793
|
+
: preset === 'phone'
|
|
794
|
+
? resolveContactTypeIdByCodes(['PHONE', 'MOBILE', 'WHATSAPP'])
|
|
795
|
+
: null;
|
|
796
|
+
|
|
797
|
+
const selectedTypeId = presetTypeId;
|
|
798
|
+
const hasPrimaryForType = contacts.some(
|
|
799
|
+
(contact) =>
|
|
800
|
+
selectedTypeId !== null &&
|
|
801
|
+
contact.contact_type_id === selectedTypeId &&
|
|
802
|
+
contact.is_primary
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
const clientId = createClientId('contact');
|
|
806
|
+
|
|
807
|
+
setContactsOpen(true);
|
|
808
|
+
setContacts((previous) => [
|
|
809
|
+
...previous,
|
|
810
|
+
{
|
|
811
|
+
clientId,
|
|
812
|
+
value: '',
|
|
813
|
+
is_primary: selectedTypeId !== null ? !hasPrimaryForType : false,
|
|
814
|
+
contact_type_id: selectedTypeId,
|
|
815
|
+
},
|
|
816
|
+
]);
|
|
817
|
+
focusWhenAvailable(() => contactValueRefs.current[clientId] || null);
|
|
818
|
+
};
|
|
726
819
|
|
|
727
820
|
const updateContact = (
|
|
728
821
|
clientId: string,
|
|
@@ -747,9 +840,9 @@ export function PersonFormSheet({
|
|
|
747
840
|
});
|
|
748
841
|
};
|
|
749
842
|
|
|
750
|
-
const removeContact = (clientId: string) => {
|
|
751
|
-
delete contactValueRefs.current[clientId];
|
|
752
|
-
setContacts((previous) => {
|
|
843
|
+
const removeContact = (clientId: string) => {
|
|
844
|
+
delete contactValueRefs.current[clientId];
|
|
845
|
+
setContacts((previous) => {
|
|
753
846
|
const removed = previous.find((contact) => contact.clientId === clientId);
|
|
754
847
|
const next = previous.filter((contact) => contact.clientId !== clientId);
|
|
755
848
|
|
|
@@ -793,31 +886,31 @@ export function PersonFormSheet({
|
|
|
793
886
|
});
|
|
794
887
|
};
|
|
795
888
|
|
|
796
|
-
const addAddress = () => {
|
|
889
|
+
const addAddress = () => {
|
|
797
890
|
const defaultType = ADDRESS_TYPE_OPTIONS[0]?.value || 'residential';
|
|
798
891
|
const hasPrimaryForType = addresses.some(
|
|
799
892
|
(address) => address.address_type === defaultType && address.is_primary
|
|
800
893
|
);
|
|
801
894
|
|
|
802
|
-
const clientId = createClientId('address');
|
|
803
|
-
|
|
804
|
-
setAddressesOpen(true);
|
|
805
|
-
setAddresses((previous) => [
|
|
806
|
-
...previous,
|
|
807
|
-
{
|
|
808
|
-
clientId,
|
|
809
|
-
line1: '',
|
|
810
|
-
line2: '',
|
|
811
|
-
city: '',
|
|
895
|
+
const clientId = createClientId('address');
|
|
896
|
+
|
|
897
|
+
setAddressesOpen(true);
|
|
898
|
+
setAddresses((previous) => [
|
|
899
|
+
...previous,
|
|
900
|
+
{
|
|
901
|
+
clientId,
|
|
902
|
+
line1: '',
|
|
903
|
+
line2: '',
|
|
904
|
+
city: '',
|
|
812
905
|
state: '',
|
|
813
906
|
is_primary: !hasPrimaryForType,
|
|
814
907
|
address_type: defaultType,
|
|
815
908
|
postal_code: '',
|
|
816
|
-
country_code: 'BRA',
|
|
817
|
-
},
|
|
818
|
-
]);
|
|
819
|
-
focusWhenAvailable(() => addressLine1Refs.current[clientId] || null);
|
|
820
|
-
};
|
|
909
|
+
country_code: 'BRA',
|
|
910
|
+
},
|
|
911
|
+
]);
|
|
912
|
+
focusWhenAvailable(() => addressLine1Refs.current[clientId] || null);
|
|
913
|
+
};
|
|
821
914
|
|
|
822
915
|
const updateAddress = (
|
|
823
916
|
clientId: string,
|
|
@@ -842,9 +935,9 @@ export function PersonFormSheet({
|
|
|
842
935
|
});
|
|
843
936
|
};
|
|
844
937
|
|
|
845
|
-
const removeAddress = (clientId: string) => {
|
|
846
|
-
delete addressLine1Refs.current[clientId];
|
|
847
|
-
setAddresses((previous) => {
|
|
938
|
+
const removeAddress = (clientId: string) => {
|
|
939
|
+
delete addressLine1Refs.current[clientId];
|
|
940
|
+
setAddresses((previous) => {
|
|
848
941
|
const removed = previous.find((address) => address.clientId === clientId);
|
|
849
942
|
const next = previous.filter((address) => address.clientId !== clientId);
|
|
850
943
|
|
|
@@ -888,29 +981,29 @@ export function PersonFormSheet({
|
|
|
888
981
|
});
|
|
889
982
|
};
|
|
890
983
|
|
|
891
|
-
const addDocument = (preset: 'cpf' | 'cnpj' | 'rg' | 'blank' = 'blank') => {
|
|
892
|
-
const presetTypeId =
|
|
893
|
-
preset === 'cpf'
|
|
894
|
-
? resolveDocumentTypeIdByCodes(['CPF'])
|
|
895
|
-
: preset === 'cnpj'
|
|
896
|
-
? resolveDocumentTypeIdByCodes(['CNPJ'])
|
|
897
|
-
: preset === 'rg'
|
|
898
|
-
? resolveDocumentTypeIdByCodes(['RG'])
|
|
899
|
-
: null;
|
|
900
|
-
|
|
901
|
-
const clientId = createClientId('document');
|
|
902
|
-
|
|
903
|
-
setDocumentsOpen(true);
|
|
904
|
-
setDocuments((previous) => [
|
|
905
|
-
...previous,
|
|
906
|
-
{
|
|
907
|
-
clientId,
|
|
908
|
-
value: '',
|
|
909
|
-
document_type_id: presetTypeId,
|
|
910
|
-
},
|
|
911
|
-
]);
|
|
912
|
-
focusWhenAvailable(() => documentValueRefs.current[clientId] || null);
|
|
913
|
-
};
|
|
984
|
+
const addDocument = (preset: 'cpf' | 'cnpj' | 'rg' | 'blank' = 'blank') => {
|
|
985
|
+
const presetTypeId =
|
|
986
|
+
preset === 'cpf'
|
|
987
|
+
? resolveDocumentTypeIdByCodes(['CPF'])
|
|
988
|
+
: preset === 'cnpj'
|
|
989
|
+
? resolveDocumentTypeIdByCodes(['CNPJ'])
|
|
990
|
+
: preset === 'rg'
|
|
991
|
+
? resolveDocumentTypeIdByCodes(['RG'])
|
|
992
|
+
: null;
|
|
993
|
+
|
|
994
|
+
const clientId = createClientId('document');
|
|
995
|
+
|
|
996
|
+
setDocumentsOpen(true);
|
|
997
|
+
setDocuments((previous) => [
|
|
998
|
+
...previous,
|
|
999
|
+
{
|
|
1000
|
+
clientId,
|
|
1001
|
+
value: '',
|
|
1002
|
+
document_type_id: presetTypeId,
|
|
1003
|
+
},
|
|
1004
|
+
]);
|
|
1005
|
+
focusWhenAvailable(() => documentValueRefs.current[clientId] || null);
|
|
1006
|
+
};
|
|
914
1007
|
|
|
915
1008
|
const updateDocument = (
|
|
916
1009
|
clientId: string,
|
|
@@ -923,12 +1016,12 @@ export function PersonFormSheet({
|
|
|
923
1016
|
);
|
|
924
1017
|
};
|
|
925
1018
|
|
|
926
|
-
const removeDocument = (clientId: string) => {
|
|
927
|
-
delete documentValueRefs.current[clientId];
|
|
928
|
-
setDocuments((previous) =>
|
|
929
|
-
previous.filter((document) => document.clientId !== clientId)
|
|
930
|
-
);
|
|
931
|
-
};
|
|
1019
|
+
const removeDocument = (clientId: string) => {
|
|
1020
|
+
delete documentValueRefs.current[clientId];
|
|
1021
|
+
setDocuments((previous) =>
|
|
1022
|
+
previous.filter((document) => document.clientId !== clientId)
|
|
1023
|
+
);
|
|
1024
|
+
};
|
|
932
1025
|
|
|
933
1026
|
const handleCEP = async (
|
|
934
1027
|
event: ChangeEvent<HTMLInputElement>,
|
|
@@ -985,17 +1078,17 @@ export function PersonFormSheet({
|
|
|
985
1078
|
? values.foundation_date.toISOString()
|
|
986
1079
|
: null,
|
|
987
1080
|
legal_nature: values.legal_nature?.trim() || null,
|
|
988
|
-
contacts: contacts
|
|
989
|
-
.filter(
|
|
990
|
-
(contact) =>
|
|
991
|
-
contact.value.trim().length > 0 && !!contact.contact_type_id
|
|
992
|
-
)
|
|
993
|
-
.map((contact) => ({
|
|
994
|
-
id: contact.id,
|
|
995
|
-
value: contact.value.trim(),
|
|
996
|
-
is_primary: contact.is_primary,
|
|
997
|
-
contact_type_id: contact.contact_type_id as number,
|
|
998
|
-
})),
|
|
1081
|
+
contacts: contacts
|
|
1082
|
+
.filter(
|
|
1083
|
+
(contact) =>
|
|
1084
|
+
contact.value.trim().length > 0 && !!contact.contact_type_id
|
|
1085
|
+
)
|
|
1086
|
+
.map((contact) => ({
|
|
1087
|
+
id: contact.id,
|
|
1088
|
+
value: contact.value.trim(),
|
|
1089
|
+
is_primary: contact.is_primary,
|
|
1090
|
+
contact_type_id: contact.contact_type_id as number,
|
|
1091
|
+
})),
|
|
999
1092
|
addresses: addresses
|
|
1000
1093
|
.filter(
|
|
1001
1094
|
(address) =>
|
|
@@ -1015,18 +1108,64 @@ export function PersonFormSheet({
|
|
|
1015
1108
|
is_primary: address.is_primary,
|
|
1016
1109
|
address_type: address.address_type,
|
|
1017
1110
|
})),
|
|
1018
|
-
documents: documents
|
|
1019
|
-
.filter(
|
|
1020
|
-
(document) =>
|
|
1021
|
-
document.value.trim().length > 0 && !!document.document_type_id
|
|
1022
|
-
)
|
|
1023
|
-
.map((document) => ({
|
|
1024
|
-
id: document.id,
|
|
1025
|
-
value: document.value.trim(),
|
|
1026
|
-
document_type_id: document.document_type_id as number,
|
|
1027
|
-
})),
|
|
1111
|
+
documents: documents
|
|
1112
|
+
.filter(
|
|
1113
|
+
(document) =>
|
|
1114
|
+
document.value.trim().length > 0 && !!document.document_type_id
|
|
1115
|
+
)
|
|
1116
|
+
.map((document) => ({
|
|
1117
|
+
id: document.id,
|
|
1118
|
+
value: document.value.trim(),
|
|
1119
|
+
document_type_id: document.document_type_id as number,
|
|
1120
|
+
})),
|
|
1028
1121
|
};
|
|
1029
1122
|
|
|
1123
|
+
const primaryEmail = payload.contacts.find((contact) =>
|
|
1124
|
+
['EMAIL'].includes(getContactTypeCode(contact.contact_type_id))
|
|
1125
|
+
);
|
|
1126
|
+
const primaryPhone = payload.contacts.find((contact) =>
|
|
1127
|
+
['PHONE', 'MOBILE', 'WHATSAPP'].includes(
|
|
1128
|
+
getContactTypeCode(contact.contact_type_id)
|
|
1129
|
+
)
|
|
1130
|
+
);
|
|
1131
|
+
const firstDocument = payload.documents[0];
|
|
1132
|
+
|
|
1133
|
+
if (primaryEmail || primaryPhone || firstDocument) {
|
|
1134
|
+
const duplicateParams = new URLSearchParams();
|
|
1135
|
+
if (person?.id) duplicateParams.set('person_id', String(person.id));
|
|
1136
|
+
if (primaryEmail?.value)
|
|
1137
|
+
duplicateParams.set('email', primaryEmail.value);
|
|
1138
|
+
if (primaryPhone?.value)
|
|
1139
|
+
duplicateParams.set('phone', primaryPhone.value);
|
|
1140
|
+
if (firstDocument?.value && firstDocument?.document_type_id) {
|
|
1141
|
+
duplicateParams.set('document_value', firstDocument.value);
|
|
1142
|
+
duplicateParams.set(
|
|
1143
|
+
'document_type_id',
|
|
1144
|
+
String(firstDocument.document_type_id)
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
if (duplicateParams.size > 0) {
|
|
1149
|
+
const duplicateResponse = await request<{
|
|
1150
|
+
hasDuplicates: boolean;
|
|
1151
|
+
matches: Array<{ id: number; name: string; reasons: string[] }>;
|
|
1152
|
+
}>({
|
|
1153
|
+
url: `/person/duplicates?${duplicateParams.toString()}`,
|
|
1154
|
+
method: 'GET',
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
if (duplicateResponse.data.hasDuplicates) {
|
|
1158
|
+
toast.warning(
|
|
1159
|
+
t('duplicateWarning', {
|
|
1160
|
+
names: duplicateResponse.data.matches
|
|
1161
|
+
.map((item) => item.name)
|
|
1162
|
+
.join(', '),
|
|
1163
|
+
})
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1030
1169
|
if (person) {
|
|
1031
1170
|
await request({
|
|
1032
1171
|
url: `/person/${person.id}`,
|
|
@@ -1084,7 +1223,7 @@ export function PersonFormSheet({
|
|
|
1084
1223
|
|
|
1085
1224
|
return (
|
|
1086
1225
|
<Sheet open={open} onOpenChange={handleSheetOpenChange}>
|
|
1087
|
-
<SheetContent className="flex h-full w-full flex-col overflow-hidden p-0
|
|
1226
|
+
<SheetContent className="flex h-full w-full max-w-full flex-col overflow-hidden p-0 lg:max-w-4xl xl:max-w-5xl">
|
|
1088
1227
|
<SheetHeader className="shrink-0 border-b p-4">
|
|
1089
1228
|
<div className="flex items-center gap-3">
|
|
1090
1229
|
<div
|
|
@@ -1120,7 +1259,7 @@ export function PersonFormSheet({
|
|
|
1120
1259
|
<form
|
|
1121
1260
|
id="person-form"
|
|
1122
1261
|
onSubmit={handleSubmit(handleFormSubmit)}
|
|
1123
|
-
className="space-y-4 px-4"
|
|
1262
|
+
className="space-y-4 px-4 [&_button[role='combobox']]:text-xs [&_button[role='combobox']_span]:truncate [&_input]:text-xs"
|
|
1124
1263
|
>
|
|
1125
1264
|
<div className="space-y-3">
|
|
1126
1265
|
<h3 className="text-sm font-semibold tracking-wider text-muted-foreground uppercase">
|
|
@@ -1220,7 +1359,7 @@ export function PersonFormSheet({
|
|
|
1220
1359
|
className={cn(
|
|
1221
1360
|
'grid gap-3',
|
|
1222
1361
|
watchType === 'individual'
|
|
1223
|
-
? 'grid-cols-1 sm:grid-cols-2 xl:grid-cols-
|
|
1362
|
+
? 'grid-cols-1 sm:grid-cols-2 xl:grid-cols-4'
|
|
1224
1363
|
: 'grid-cols-1 sm:grid-cols-2'
|
|
1225
1364
|
)}
|
|
1226
1365
|
>
|
|
@@ -1314,6 +1453,122 @@ export function PersonFormSheet({
|
|
|
1314
1453
|
) : null}
|
|
1315
1454
|
</div>
|
|
1316
1455
|
|
|
1456
|
+
<div className="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
|
1457
|
+
<div className="space-y-1.5">
|
|
1458
|
+
<Label className="text-xs font-medium">{t('owner')}</Label>
|
|
1459
|
+
<Select
|
|
1460
|
+
value={
|
|
1461
|
+
watch('owner_user_id')
|
|
1462
|
+
? String(watch('owner_user_id'))
|
|
1463
|
+
: 'none'
|
|
1464
|
+
}
|
|
1465
|
+
onValueChange={(value) =>
|
|
1466
|
+
setValue(
|
|
1467
|
+
'owner_user_id',
|
|
1468
|
+
value === 'none' ? null : Number(value)
|
|
1469
|
+
)
|
|
1470
|
+
}
|
|
1471
|
+
>
|
|
1472
|
+
<SelectTrigger className="h-9 w-full min-w-0">
|
|
1473
|
+
<SelectValue placeholder={t('unassigned')} />
|
|
1474
|
+
</SelectTrigger>
|
|
1475
|
+
<SelectContent>
|
|
1476
|
+
<SelectItem value="none">{t('unassigned')}</SelectItem>
|
|
1477
|
+
{ownerOptions.map((owner) => (
|
|
1478
|
+
<SelectItem key={owner.id} value={String(owner.id)}>
|
|
1479
|
+
{owner.name}
|
|
1480
|
+
</SelectItem>
|
|
1481
|
+
))}
|
|
1482
|
+
</SelectContent>
|
|
1483
|
+
</Select>
|
|
1484
|
+
</div>
|
|
1485
|
+
|
|
1486
|
+
<div className="space-y-1.5">
|
|
1487
|
+
<Label className="text-xs font-medium">{t('source')}</Label>
|
|
1488
|
+
<Select
|
|
1489
|
+
value={watch('source') || 'none'}
|
|
1490
|
+
onValueChange={(value) =>
|
|
1491
|
+
setValue(
|
|
1492
|
+
'source',
|
|
1493
|
+
value === 'none' ? null : (value as PersonSource)
|
|
1494
|
+
)
|
|
1495
|
+
}
|
|
1496
|
+
>
|
|
1497
|
+
<SelectTrigger className="h-9 w-full min-w-0">
|
|
1498
|
+
<SelectValue placeholder={t('source')} />
|
|
1499
|
+
</SelectTrigger>
|
|
1500
|
+
<SelectContent>
|
|
1501
|
+
<SelectItem value="none">{t('all')}</SelectItem>
|
|
1502
|
+
<SelectItem value="referral">
|
|
1503
|
+
{t('sourceReferral')}
|
|
1504
|
+
</SelectItem>
|
|
1505
|
+
<SelectItem value="website">
|
|
1506
|
+
{t('sourceWebsite')}
|
|
1507
|
+
</SelectItem>
|
|
1508
|
+
<SelectItem value="social">
|
|
1509
|
+
{t('sourceSocial')}
|
|
1510
|
+
</SelectItem>
|
|
1511
|
+
<SelectItem value="inbound">
|
|
1512
|
+
{t('sourceInbound')}
|
|
1513
|
+
</SelectItem>
|
|
1514
|
+
<SelectItem value="outbound">
|
|
1515
|
+
{t('sourceOutbound')}
|
|
1516
|
+
</SelectItem>
|
|
1517
|
+
<SelectItem value="other">{t('sourceOther')}</SelectItem>
|
|
1518
|
+
</SelectContent>
|
|
1519
|
+
</Select>
|
|
1520
|
+
</div>
|
|
1521
|
+
|
|
1522
|
+
<div className="space-y-1.5">
|
|
1523
|
+
<Label className="text-xs font-medium">
|
|
1524
|
+
{t('lifecycleStage')}
|
|
1525
|
+
</Label>
|
|
1526
|
+
<Select
|
|
1527
|
+
value={watch('lifecycle_stage') || 'new'}
|
|
1528
|
+
onValueChange={(value) =>
|
|
1529
|
+
setValue('lifecycle_stage', value as PersonLifecycleStage)
|
|
1530
|
+
}
|
|
1531
|
+
>
|
|
1532
|
+
<SelectTrigger className="h-9 w-full min-w-0">
|
|
1533
|
+
<SelectValue />
|
|
1534
|
+
</SelectTrigger>
|
|
1535
|
+
<SelectContent>
|
|
1536
|
+
<SelectItem value="new">{t('lifecycleNew')}</SelectItem>
|
|
1537
|
+
<SelectItem value="contacted">
|
|
1538
|
+
{t('lifecycleContacted')}
|
|
1539
|
+
</SelectItem>
|
|
1540
|
+
<SelectItem value="qualified">
|
|
1541
|
+
{t('lifecycleQualified')}
|
|
1542
|
+
</SelectItem>
|
|
1543
|
+
<SelectItem value="proposal">
|
|
1544
|
+
{t('lifecycleProposal')}
|
|
1545
|
+
</SelectItem>
|
|
1546
|
+
<SelectItem value="negotiation">
|
|
1547
|
+
{t('lifecycleNegotiation')}
|
|
1548
|
+
</SelectItem>
|
|
1549
|
+
<SelectItem value="customer">
|
|
1550
|
+
{t('lifecycleCustomer')}
|
|
1551
|
+
</SelectItem>
|
|
1552
|
+
<SelectItem value="lost">{t('lifecycleLost')}</SelectItem>
|
|
1553
|
+
</SelectContent>
|
|
1554
|
+
</Select>
|
|
1555
|
+
</div>
|
|
1556
|
+
|
|
1557
|
+
<div className="space-y-1.5">
|
|
1558
|
+
<Label className="text-xs font-medium">
|
|
1559
|
+
{t('nextActionAt')}
|
|
1560
|
+
</Label>
|
|
1561
|
+
<Input
|
|
1562
|
+
type="datetime-local"
|
|
1563
|
+
value={watch('next_action_at') || ''}
|
|
1564
|
+
onChange={(event) =>
|
|
1565
|
+
setValue('next_action_at', event.target.value)
|
|
1566
|
+
}
|
|
1567
|
+
className="h-9"
|
|
1568
|
+
/>
|
|
1569
|
+
</div>
|
|
1570
|
+
</div>
|
|
1571
|
+
|
|
1317
1572
|
{watchType === 'individual' ? (
|
|
1318
1573
|
<>
|
|
1319
1574
|
<div className="grid grid-cols-2 gap-3">
|
|
@@ -1412,44 +1667,44 @@ export function PersonFormSheet({
|
|
|
1412
1667
|
) : null}
|
|
1413
1668
|
</div>
|
|
1414
1669
|
|
|
1415
|
-
<div className="flex items-center gap-1">
|
|
1416
|
-
<Button
|
|
1417
|
-
type="button"
|
|
1418
|
-
variant="ghost"
|
|
1419
|
-
size="sm"
|
|
1420
|
-
className="h-7 px-2 text-xs"
|
|
1421
|
-
onClick={(event) => {
|
|
1422
|
-
event.stopPropagation();
|
|
1423
|
-
addContact('email');
|
|
1424
|
-
}}
|
|
1425
|
-
>
|
|
1426
|
-
<Plus className="mr-1 h-3.5 w-3.5" />
|
|
1427
|
-
{t('addEmail')}
|
|
1428
|
-
</Button>
|
|
1429
|
-
<Button
|
|
1430
|
-
type="button"
|
|
1431
|
-
variant="ghost"
|
|
1432
|
-
size="sm"
|
|
1433
|
-
className="h-7 px-2 text-xs"
|
|
1434
|
-
onClick={(event) => {
|
|
1435
|
-
event.stopPropagation();
|
|
1436
|
-
addContact('phone');
|
|
1437
|
-
}}
|
|
1438
|
-
>
|
|
1439
|
-
<Plus className="mr-1 h-3.5 w-3.5" />
|
|
1440
|
-
{t('addPhone')}
|
|
1441
|
-
</Button>
|
|
1442
|
-
<Button
|
|
1443
|
-
type="button"
|
|
1444
|
-
variant="ghost"
|
|
1445
|
-
size="sm"
|
|
1446
|
-
className="h-7 px-2 text-xs"
|
|
1447
|
-
onClick={(event) => {
|
|
1448
|
-
event.stopPropagation();
|
|
1449
|
-
addContact('blank');
|
|
1450
|
-
}}
|
|
1451
|
-
>
|
|
1452
|
-
<Plus className="mr-1 h-3.5 w-3.5" />
|
|
1670
|
+
<div className="flex items-center gap-1">
|
|
1671
|
+
<Button
|
|
1672
|
+
type="button"
|
|
1673
|
+
variant="ghost"
|
|
1674
|
+
size="sm"
|
|
1675
|
+
className="h-7 px-2 text-xs"
|
|
1676
|
+
onClick={(event) => {
|
|
1677
|
+
event.stopPropagation();
|
|
1678
|
+
addContact('email');
|
|
1679
|
+
}}
|
|
1680
|
+
>
|
|
1681
|
+
<Plus className="mr-1 h-3.5 w-3.5" />
|
|
1682
|
+
{t('addEmail')}
|
|
1683
|
+
</Button>
|
|
1684
|
+
<Button
|
|
1685
|
+
type="button"
|
|
1686
|
+
variant="ghost"
|
|
1687
|
+
size="sm"
|
|
1688
|
+
className="h-7 px-2 text-xs"
|
|
1689
|
+
onClick={(event) => {
|
|
1690
|
+
event.stopPropagation();
|
|
1691
|
+
addContact('phone');
|
|
1692
|
+
}}
|
|
1693
|
+
>
|
|
1694
|
+
<Plus className="mr-1 h-3.5 w-3.5" />
|
|
1695
|
+
{t('addPhone')}
|
|
1696
|
+
</Button>
|
|
1697
|
+
<Button
|
|
1698
|
+
type="button"
|
|
1699
|
+
variant="ghost"
|
|
1700
|
+
size="sm"
|
|
1701
|
+
className="h-7 px-2 text-xs"
|
|
1702
|
+
onClick={(event) => {
|
|
1703
|
+
event.stopPropagation();
|
|
1704
|
+
addContact('blank');
|
|
1705
|
+
}}
|
|
1706
|
+
>
|
|
1707
|
+
<Plus className="mr-1 h-3.5 w-3.5" />
|
|
1453
1708
|
{t('addContact')}
|
|
1454
1709
|
</Button>
|
|
1455
1710
|
{contactsOpen ? (
|
|
@@ -1476,26 +1731,26 @@ export function PersonFormSheet({
|
|
|
1476
1731
|
)}
|
|
1477
1732
|
>
|
|
1478
1733
|
<div className="flex items-center gap-2">
|
|
1479
|
-
<Select
|
|
1480
|
-
value={
|
|
1481
|
-
contact.contact_type_id
|
|
1482
|
-
? String(contact.contact_type_id)
|
|
1483
|
-
: undefined
|
|
1484
|
-
}
|
|
1485
|
-
onValueChange={(value) => {
|
|
1486
|
-
const nextTypeId = Number(value);
|
|
1487
|
-
updateContact(contact.clientId, {
|
|
1488
|
-
contact_type_id: nextTypeId,
|
|
1489
|
-
value: maskContactValueByType(
|
|
1490
|
-
contact.value || '',
|
|
1491
|
-
nextTypeId
|
|
1492
|
-
),
|
|
1493
|
-
});
|
|
1494
|
-
}}
|
|
1495
|
-
>
|
|
1496
|
-
<SelectTrigger className="h-8 w-36 text-xs">
|
|
1497
|
-
<SelectValue placeholder={t('selectContactType')} />
|
|
1498
|
-
</SelectTrigger>
|
|
1734
|
+
<Select
|
|
1735
|
+
value={
|
|
1736
|
+
contact.contact_type_id
|
|
1737
|
+
? String(contact.contact_type_id)
|
|
1738
|
+
: undefined
|
|
1739
|
+
}
|
|
1740
|
+
onValueChange={(value) => {
|
|
1741
|
+
const nextTypeId = Number(value);
|
|
1742
|
+
updateContact(contact.clientId, {
|
|
1743
|
+
contact_type_id: nextTypeId,
|
|
1744
|
+
value: maskContactValueByType(
|
|
1745
|
+
contact.value || '',
|
|
1746
|
+
nextTypeId
|
|
1747
|
+
),
|
|
1748
|
+
});
|
|
1749
|
+
}}
|
|
1750
|
+
>
|
|
1751
|
+
<SelectTrigger className="h-8 w-36 text-xs">
|
|
1752
|
+
<SelectValue placeholder={t('selectContactType')} />
|
|
1753
|
+
</SelectTrigger>
|
|
1499
1754
|
<SelectContent>
|
|
1500
1755
|
{contactTypes.map((contactType) => (
|
|
1501
1756
|
<SelectItem
|
|
@@ -1508,75 +1763,76 @@ export function PersonFormSheet({
|
|
|
1508
1763
|
</SelectContent>
|
|
1509
1764
|
</Select>
|
|
1510
1765
|
|
|
1511
|
-
<Input
|
|
1512
|
-
ref={(element) => {
|
|
1513
|
-
contactValueRefs.current[contact.clientId] =
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1766
|
+
<Input
|
|
1767
|
+
ref={(element) => {
|
|
1768
|
+
contactValueRefs.current[contact.clientId] =
|
|
1769
|
+
element;
|
|
1770
|
+
}}
|
|
1771
|
+
placeholder={(() => {
|
|
1772
|
+
const contactTypeCode = getContactTypeCode(
|
|
1773
|
+
contact.contact_type_id
|
|
1774
|
+
);
|
|
1775
|
+
if (contactTypeCode === 'EMAIL') {
|
|
1776
|
+
return 'email@exemplo.com';
|
|
1777
|
+
}
|
|
1778
|
+
if (contactTypeCode.length === 0) {
|
|
1779
|
+
return t('contactValue');
|
|
1780
|
+
}
|
|
1781
|
+
return '(00) 00000-0000';
|
|
1782
|
+
})()}
|
|
1783
|
+
value={contact.value}
|
|
1784
|
+
onChange={(event) =>
|
|
1785
|
+
updateContact(contact.clientId, {
|
|
1786
|
+
value: maskContactValueByType(
|
|
1787
|
+
event.target.value,
|
|
1788
|
+
contact.contact_type_id
|
|
1789
|
+
),
|
|
1790
|
+
})
|
|
1791
|
+
}
|
|
1792
|
+
className="h-8 flex-1 text-xs"
|
|
1537
1793
|
/>
|
|
1538
1794
|
|
|
1539
|
-
<Tooltip>
|
|
1540
|
-
<TooltipTrigger asChild>
|
|
1541
|
-
<Button
|
|
1542
|
-
type="button"
|
|
1543
|
-
variant="ghost"
|
|
1544
|
-
size="icon"
|
|
1545
|
-
className={cn(
|
|
1546
|
-
'h-8 w-8',
|
|
1547
|
-
contact.is_primary && 'text-amber-500'
|
|
1548
|
-
)}
|
|
1549
|
-
onClick={() =>
|
|
1550
|
-
setPrimaryContact(contact.clientId)
|
|
1551
|
-
}
|
|
1552
|
-
aria-label={t('main')}
|
|
1553
|
-
>
|
|
1554
|
-
<Star
|
|
1555
|
-
className={cn(
|
|
1556
|
-
'h-4 w-4',
|
|
1557
|
-
contact.is_primary && 'fill-current'
|
|
1558
|
-
)}
|
|
1559
|
-
/>
|
|
1560
|
-
</Button>
|
|
1561
|
-
</TooltipTrigger>
|
|
1562
|
-
<TooltipContent>{t('main')}</TooltipContent>
|
|
1563
|
-
</Tooltip>
|
|
1564
|
-
|
|
1565
|
-
<Tooltip>
|
|
1566
|
-
<TooltipTrigger asChild>
|
|
1567
|
-
<Button
|
|
1568
|
-
type="button"
|
|
1569
|
-
variant="ghost"
|
|
1570
|
-
size="icon"
|
|
1571
|
-
className="h-8 w-8 text-red-500 hover:text-red-600"
|
|
1572
|
-
onClick={() => removeContact(contact.clientId)}
|
|
1573
|
-
aria-label={t('remove')}
|
|
1574
|
-
>
|
|
1575
|
-
<Trash2 className="h-4 w-4" />
|
|
1576
|
-
</Button>
|
|
1577
|
-
</TooltipTrigger>
|
|
1578
|
-
<TooltipContent>{t('remove')}</TooltipContent>
|
|
1579
|
-
</Tooltip>
|
|
1795
|
+
<Tooltip>
|
|
1796
|
+
<TooltipTrigger asChild>
|
|
1797
|
+
<Button
|
|
1798
|
+
type="button"
|
|
1799
|
+
variant="ghost"
|
|
1800
|
+
size="icon"
|
|
1801
|
+
className={cn(
|
|
1802
|
+
'h-8 w-8',
|
|
1803
|
+
contact.is_primary && 'text-amber-500'
|
|
1804
|
+
)}
|
|
1805
|
+
onClick={() =>
|
|
1806
|
+
setPrimaryContact(contact.clientId)
|
|
1807
|
+
}
|
|
1808
|
+
aria-label={t('main')}
|
|
1809
|
+
>
|
|
1810
|
+
<Star
|
|
1811
|
+
className={cn(
|
|
1812
|
+
'h-4 w-4',
|
|
1813
|
+
contact.is_primary && 'fill-current'
|
|
1814
|
+
)}
|
|
1815
|
+
/>
|
|
1816
|
+
</Button>
|
|
1817
|
+
</TooltipTrigger>
|
|
1818
|
+
<TooltipContent>{t('main')}</TooltipContent>
|
|
1819
|
+
</Tooltip>
|
|
1820
|
+
|
|
1821
|
+
<Tooltip>
|
|
1822
|
+
<TooltipTrigger asChild>
|
|
1823
|
+
<Button
|
|
1824
|
+
type="button"
|
|
1825
|
+
variant="ghost"
|
|
1826
|
+
size="icon"
|
|
1827
|
+
className="h-8 w-8 text-red-500 hover:text-red-600"
|
|
1828
|
+
onClick={() => removeContact(contact.clientId)}
|
|
1829
|
+
aria-label={t('remove')}
|
|
1830
|
+
>
|
|
1831
|
+
<Trash2 className="h-4 w-4" />
|
|
1832
|
+
</Button>
|
|
1833
|
+
</TooltipTrigger>
|
|
1834
|
+
<TooltipContent>{t('remove')}</TooltipContent>
|
|
1835
|
+
</Tooltip>
|
|
1580
1836
|
</div>
|
|
1581
1837
|
</div>
|
|
1582
1838
|
))
|
|
@@ -1677,57 +1933,58 @@ export function PersonFormSheet({
|
|
|
1677
1933
|
className="h-8 w-28 text-xs"
|
|
1678
1934
|
/>
|
|
1679
1935
|
|
|
1680
|
-
<Tooltip>
|
|
1681
|
-
<TooltipTrigger asChild>
|
|
1682
|
-
<Button
|
|
1683
|
-
type="button"
|
|
1684
|
-
variant="ghost"
|
|
1685
|
-
size="icon"
|
|
1686
|
-
className={cn(
|
|
1687
|
-
'h-8 w-8',
|
|
1688
|
-
address.is_primary && 'text-amber-500'
|
|
1689
|
-
)}
|
|
1690
|
-
onClick={() =>
|
|
1691
|
-
setPrimaryAddress(address.clientId)
|
|
1692
|
-
}
|
|
1693
|
-
aria-label={t('main')}
|
|
1694
|
-
>
|
|
1695
|
-
<Star
|
|
1696
|
-
className={cn(
|
|
1697
|
-
'h-4 w-4',
|
|
1698
|
-
address.is_primary && 'fill-current'
|
|
1699
|
-
)}
|
|
1700
|
-
/>
|
|
1701
|
-
</Button>
|
|
1702
|
-
</TooltipTrigger>
|
|
1703
|
-
<TooltipContent>{t('main')}</TooltipContent>
|
|
1704
|
-
</Tooltip>
|
|
1705
|
-
|
|
1706
|
-
<Tooltip>
|
|
1707
|
-
<TooltipTrigger asChild>
|
|
1708
|
-
<Button
|
|
1709
|
-
type="button"
|
|
1710
|
-
variant="ghost"
|
|
1711
|
-
size="icon"
|
|
1712
|
-
className="h-8 w-8 text-red-500 hover:text-red-600"
|
|
1713
|
-
onClick={() => removeAddress(address.clientId)}
|
|
1714
|
-
aria-label={t('remove')}
|
|
1715
|
-
>
|
|
1716
|
-
<Trash2 className="h-4 w-4" />
|
|
1717
|
-
</Button>
|
|
1718
|
-
</TooltipTrigger>
|
|
1719
|
-
<TooltipContent>{t('remove')}</TooltipContent>
|
|
1720
|
-
</Tooltip>
|
|
1936
|
+
<Tooltip>
|
|
1937
|
+
<TooltipTrigger asChild>
|
|
1938
|
+
<Button
|
|
1939
|
+
type="button"
|
|
1940
|
+
variant="ghost"
|
|
1941
|
+
size="icon"
|
|
1942
|
+
className={cn(
|
|
1943
|
+
'h-8 w-8',
|
|
1944
|
+
address.is_primary && 'text-amber-500'
|
|
1945
|
+
)}
|
|
1946
|
+
onClick={() =>
|
|
1947
|
+
setPrimaryAddress(address.clientId)
|
|
1948
|
+
}
|
|
1949
|
+
aria-label={t('main')}
|
|
1950
|
+
>
|
|
1951
|
+
<Star
|
|
1952
|
+
className={cn(
|
|
1953
|
+
'h-4 w-4',
|
|
1954
|
+
address.is_primary && 'fill-current'
|
|
1955
|
+
)}
|
|
1956
|
+
/>
|
|
1957
|
+
</Button>
|
|
1958
|
+
</TooltipTrigger>
|
|
1959
|
+
<TooltipContent>{t('main')}</TooltipContent>
|
|
1960
|
+
</Tooltip>
|
|
1961
|
+
|
|
1962
|
+
<Tooltip>
|
|
1963
|
+
<TooltipTrigger asChild>
|
|
1964
|
+
<Button
|
|
1965
|
+
type="button"
|
|
1966
|
+
variant="ghost"
|
|
1967
|
+
size="icon"
|
|
1968
|
+
className="h-8 w-8 text-red-500 hover:text-red-600"
|
|
1969
|
+
onClick={() => removeAddress(address.clientId)}
|
|
1970
|
+
aria-label={t('remove')}
|
|
1971
|
+
>
|
|
1972
|
+
<Trash2 className="h-4 w-4" />
|
|
1973
|
+
</Button>
|
|
1974
|
+
</TooltipTrigger>
|
|
1975
|
+
<TooltipContent>{t('remove')}</TooltipContent>
|
|
1976
|
+
</Tooltip>
|
|
1721
1977
|
</div>
|
|
1722
1978
|
|
|
1723
1979
|
<div className="relative">
|
|
1724
|
-
<Input
|
|
1725
|
-
ref={(element) => {
|
|
1726
|
-
addressLine1Refs.current[address.clientId] =
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1980
|
+
<Input
|
|
1981
|
+
ref={(element) => {
|
|
1982
|
+
addressLine1Refs.current[address.clientId] =
|
|
1983
|
+
element;
|
|
1984
|
+
}}
|
|
1985
|
+
placeholder={t('addressPlaceholder')}
|
|
1986
|
+
value={address.line1}
|
|
1987
|
+
disabled={Boolean(loadingCEP[address.clientId])}
|
|
1731
1988
|
onChange={(event) =>
|
|
1732
1989
|
updateAddress(address.clientId, {
|
|
1733
1990
|
line1: event.target.value,
|
|
@@ -1807,8 +2064,8 @@ export function PersonFormSheet({
|
|
|
1807
2064
|
|
|
1808
2065
|
<Collapsible open={documentsOpen} onOpenChange={setDocumentsOpen}>
|
|
1809
2066
|
<CollapsibleTrigger asChild>
|
|
1810
|
-
<div className="group flex cursor-pointer items-center
|
|
1811
|
-
<div className="flex items-center gap-2">
|
|
2067
|
+
<div className="group flex cursor-pointer flex-wrap items-center gap-2">
|
|
2068
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
1812
2069
|
<FileText className="h-4 w-4 text-amber-500" />
|
|
1813
2070
|
<h3 className="text-sm font-semibold">
|
|
1814
2071
|
{t('tabDocuments')}
|
|
@@ -1823,59 +2080,61 @@ export function PersonFormSheet({
|
|
|
1823
2080
|
) : null}
|
|
1824
2081
|
</div>
|
|
1825
2082
|
|
|
1826
|
-
<div className="flex items-center gap-1">
|
|
1827
|
-
<Button
|
|
1828
|
-
type="button"
|
|
1829
|
-
variant="ghost"
|
|
1830
|
-
size="sm"
|
|
1831
|
-
className="h-7 px-2 text-xs"
|
|
1832
|
-
onClick={(event) => {
|
|
1833
|
-
event.stopPropagation();
|
|
1834
|
-
addDocument('cpf');
|
|
1835
|
-
}}
|
|
1836
|
-
>
|
|
1837
|
-
<Plus className="mr-1 h-3.5 w-3.5" />
|
|
1838
|
-
CPF
|
|
1839
|
-
</Button>
|
|
1840
|
-
<Button
|
|
1841
|
-
type="button"
|
|
1842
|
-
variant="ghost"
|
|
1843
|
-
size="sm"
|
|
1844
|
-
className="h-7 px-2 text-xs"
|
|
1845
|
-
onClick={(event) => {
|
|
1846
|
-
event.stopPropagation();
|
|
1847
|
-
addDocument('cnpj');
|
|
1848
|
-
}}
|
|
1849
|
-
>
|
|
1850
|
-
<Plus className="mr-1 h-3.5 w-3.5" />
|
|
1851
|
-
CNPJ
|
|
1852
|
-
</Button>
|
|
1853
|
-
<Button
|
|
1854
|
-
type="button"
|
|
1855
|
-
variant="ghost"
|
|
1856
|
-
size="sm"
|
|
1857
|
-
className="h-7 px-2 text-xs"
|
|
1858
|
-
onClick={(event) => {
|
|
1859
|
-
event.stopPropagation();
|
|
1860
|
-
addDocument('rg');
|
|
1861
|
-
}}
|
|
1862
|
-
>
|
|
1863
|
-
<Plus className="mr-1 h-3.5 w-3.5" />
|
|
1864
|
-
RG
|
|
1865
|
-
</Button>
|
|
1866
|
-
<Button
|
|
1867
|
-
type="button"
|
|
1868
|
-
variant="ghost"
|
|
1869
|
-
size="sm"
|
|
1870
|
-
className="h-7 px-2 text-xs"
|
|
1871
|
-
onClick={(event) => {
|
|
1872
|
-
event.stopPropagation();
|
|
1873
|
-
addDocument('blank');
|
|
1874
|
-
}}
|
|
1875
|
-
>
|
|
1876
|
-
<Plus className="mr-1 h-3.5 w-3.5" />
|
|
1877
|
-
|
|
1878
|
-
|
|
2083
|
+
<div className="ml-auto flex max-w-full flex-wrap items-center justify-end gap-1 sm:flex-nowrap">
|
|
2084
|
+
<Button
|
|
2085
|
+
type="button"
|
|
2086
|
+
variant="ghost"
|
|
2087
|
+
size="sm"
|
|
2088
|
+
className="h-7 shrink-0 px-2 text-xs"
|
|
2089
|
+
onClick={(event) => {
|
|
2090
|
+
event.stopPropagation();
|
|
2091
|
+
addDocument('cpf');
|
|
2092
|
+
}}
|
|
2093
|
+
>
|
|
2094
|
+
<Plus className="mr-1 h-3.5 w-3.5" />
|
|
2095
|
+
CPF
|
|
2096
|
+
</Button>
|
|
2097
|
+
<Button
|
|
2098
|
+
type="button"
|
|
2099
|
+
variant="ghost"
|
|
2100
|
+
size="sm"
|
|
2101
|
+
className="h-7 shrink-0 px-2 text-xs"
|
|
2102
|
+
onClick={(event) => {
|
|
2103
|
+
event.stopPropagation();
|
|
2104
|
+
addDocument('cnpj');
|
|
2105
|
+
}}
|
|
2106
|
+
>
|
|
2107
|
+
<Plus className="mr-1 h-3.5 w-3.5" />
|
|
2108
|
+
CNPJ
|
|
2109
|
+
</Button>
|
|
2110
|
+
<Button
|
|
2111
|
+
type="button"
|
|
2112
|
+
variant="ghost"
|
|
2113
|
+
size="sm"
|
|
2114
|
+
className="h-7 shrink-0 px-2 text-xs"
|
|
2115
|
+
onClick={(event) => {
|
|
2116
|
+
event.stopPropagation();
|
|
2117
|
+
addDocument('rg');
|
|
2118
|
+
}}
|
|
2119
|
+
>
|
|
2120
|
+
<Plus className="mr-1 h-3.5 w-3.5" />
|
|
2121
|
+
RG
|
|
2122
|
+
</Button>
|
|
2123
|
+
<Button
|
|
2124
|
+
type="button"
|
|
2125
|
+
variant="ghost"
|
|
2126
|
+
size="sm"
|
|
2127
|
+
className="h-7 shrink-0 px-2 text-xs"
|
|
2128
|
+
onClick={(event) => {
|
|
2129
|
+
event.stopPropagation();
|
|
2130
|
+
addDocument('blank');
|
|
2131
|
+
}}
|
|
2132
|
+
>
|
|
2133
|
+
<Plus className="mr-1 h-3.5 w-3.5" />
|
|
2134
|
+
<span className="max-w-20 truncate sm:max-w-none">
|
|
2135
|
+
{t('addDocument')}
|
|
2136
|
+
</span>
|
|
2137
|
+
</Button>
|
|
1879
2138
|
{documentsOpen ? (
|
|
1880
2139
|
<ChevronUp className="h-4 w-4" />
|
|
1881
2140
|
) : (
|
|
@@ -1897,23 +2156,23 @@ export function PersonFormSheet({
|
|
|
1897
2156
|
className="rounded-lg border p-2"
|
|
1898
2157
|
>
|
|
1899
2158
|
<div className="flex items-center gap-2">
|
|
1900
|
-
<Select
|
|
1901
|
-
value={
|
|
1902
|
-
document.document_type_id
|
|
1903
|
-
? String(document.document_type_id)
|
|
1904
|
-
: undefined
|
|
1905
|
-
}
|
|
1906
|
-
onValueChange={(value) => {
|
|
1907
|
-
const nextTypeId = Number(value);
|
|
1908
|
-
updateDocument(document.clientId, {
|
|
1909
|
-
document_type_id: nextTypeId,
|
|
1910
|
-
value: maskDocumentValueByType(
|
|
1911
|
-
document.value || '',
|
|
1912
|
-
nextTypeId
|
|
1913
|
-
),
|
|
1914
|
-
});
|
|
1915
|
-
}}
|
|
1916
|
-
>
|
|
2159
|
+
<Select
|
|
2160
|
+
value={
|
|
2161
|
+
document.document_type_id
|
|
2162
|
+
? String(document.document_type_id)
|
|
2163
|
+
: undefined
|
|
2164
|
+
}
|
|
2165
|
+
onValueChange={(value) => {
|
|
2166
|
+
const nextTypeId = Number(value);
|
|
2167
|
+
updateDocument(document.clientId, {
|
|
2168
|
+
document_type_id: nextTypeId,
|
|
2169
|
+
value: maskDocumentValueByType(
|
|
2170
|
+
document.value || '',
|
|
2171
|
+
nextTypeId
|
|
2172
|
+
),
|
|
2173
|
+
});
|
|
2174
|
+
}}
|
|
2175
|
+
>
|
|
1917
2176
|
<SelectTrigger className="h-8 w-36 text-xs">
|
|
1918
2177
|
<SelectValue
|
|
1919
2178
|
placeholder={t('selectDocumentType')}
|
|
@@ -1931,38 +2190,39 @@ export function PersonFormSheet({
|
|
|
1931
2190
|
</SelectContent>
|
|
1932
2191
|
</Select>
|
|
1933
2192
|
|
|
1934
|
-
<Input
|
|
1935
|
-
ref={(element) => {
|
|
1936
|
-
documentValueRefs.current[document.clientId] =
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
2193
|
+
<Input
|
|
2194
|
+
ref={(element) => {
|
|
2195
|
+
documentValueRefs.current[document.clientId] =
|
|
2196
|
+
element;
|
|
2197
|
+
}}
|
|
2198
|
+
placeholder={t('documentValuePlaceholder')}
|
|
2199
|
+
value={document.value}
|
|
2200
|
+
onChange={(event) =>
|
|
2201
|
+
updateDocument(document.clientId, {
|
|
2202
|
+
value: maskDocumentValueByType(
|
|
2203
|
+
event.target.value,
|
|
2204
|
+
document.document_type_id
|
|
2205
|
+
),
|
|
2206
|
+
})
|
|
2207
|
+
}
|
|
2208
|
+
className="h-8 flex-1 text-xs"
|
|
1949
2209
|
/>
|
|
1950
2210
|
|
|
1951
|
-
<Tooltip>
|
|
1952
|
-
<TooltipTrigger asChild>
|
|
1953
|
-
<Button
|
|
1954
|
-
type="button"
|
|
1955
|
-
variant="ghost"
|
|
1956
|
-
size="icon"
|
|
1957
|
-
className="h-8 w-8 text-red-500 hover:text-red-600"
|
|
1958
|
-
onClick={() => removeDocument(document.clientId)}
|
|
1959
|
-
aria-label={t('remove')}
|
|
1960
|
-
>
|
|
1961
|
-
<Trash2 className="h-4 w-4" />
|
|
1962
|
-
</Button>
|
|
1963
|
-
</TooltipTrigger>
|
|
1964
|
-
<TooltipContent>{t('remove')}</TooltipContent>
|
|
1965
|
-
</Tooltip>
|
|
2211
|
+
<Tooltip>
|
|
2212
|
+
<TooltipTrigger asChild>
|
|
2213
|
+
<Button
|
|
2214
|
+
type="button"
|
|
2215
|
+
variant="ghost"
|
|
2216
|
+
size="icon"
|
|
2217
|
+
className="h-8 w-8 text-red-500 hover:text-red-600"
|
|
2218
|
+
onClick={() => removeDocument(document.clientId)}
|
|
2219
|
+
aria-label={t('remove')}
|
|
2220
|
+
>
|
|
2221
|
+
<Trash2 className="h-4 w-4" />
|
|
2222
|
+
</Button>
|
|
2223
|
+
</TooltipTrigger>
|
|
2224
|
+
<TooltipContent>{t('remove')}</TooltipContent>
|
|
2225
|
+
</Tooltip>
|
|
1966
2226
|
</div>
|
|
1967
2227
|
</div>
|
|
1968
2228
|
))
|
|
@@ -1973,23 +2233,43 @@ export function PersonFormSheet({
|
|
|
1973
2233
|
</div>
|
|
1974
2234
|
|
|
1975
2235
|
<div className="shrink-0 space-y-2 border-t p-4">
|
|
1976
|
-
<
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
2236
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
2237
|
+
{!isEditing ? (
|
|
2238
|
+
<Button
|
|
2239
|
+
type="button"
|
|
2240
|
+
variant="outline"
|
|
2241
|
+
onClick={() => {
|
|
2242
|
+
const formElement = document.getElementById(
|
|
2243
|
+
'person-form'
|
|
2244
|
+
) as HTMLFormElement | null;
|
|
2245
|
+
formElement?.requestSubmit();
|
|
2246
|
+
}}
|
|
2247
|
+
disabled={isSubmitting || isUploadingAvatar}
|
|
2248
|
+
>
|
|
2249
|
+
{t('saveAndNew')}
|
|
2250
|
+
</Button>
|
|
2251
|
+
) : null}
|
|
2252
|
+
<Button
|
|
2253
|
+
type="submit"
|
|
2254
|
+
form="person-form"
|
|
2255
|
+
className="w-full"
|
|
2256
|
+
disabled={isSubmitting || isUploadingAvatar}
|
|
2257
|
+
>
|
|
2258
|
+
{isSubmitting ? (
|
|
2259
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
2260
|
+
) : (
|
|
2261
|
+
<Save className="mr-2 h-4 w-4" />
|
|
2262
|
+
)}
|
|
2263
|
+
{isSubmitting
|
|
2264
|
+
? t('saving')
|
|
2265
|
+
: isEditing
|
|
2266
|
+
? t('saveChanges')
|
|
2267
|
+
: t('createPerson')}
|
|
2268
|
+
</Button>
|
|
2269
|
+
</div>
|
|
2270
|
+
<p className="text-xs text-muted-foreground">
|
|
2271
|
+
{t('formShortcutsHint')}
|
|
2272
|
+
</p>
|
|
1993
2273
|
</div>
|
|
1994
2274
|
</SheetContent>
|
|
1995
2275
|
</Sheet>
|