@hed-hog/contact 0.0.347 → 0.0.350

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.
@@ -1,14 +1,6 @@
1
1
  'use client';
2
2
 
3
3
  import { Button } from '@/components/ui/button';
4
- import {
5
- Dialog,
6
- DialogContent,
7
- DialogDescription,
8
- DialogFooter,
9
- DialogHeader,
10
- DialogTitle,
11
- } from '@/components/ui/dialog';
12
4
  import {
13
5
  Form,
14
6
  FormControl,
@@ -25,6 +17,14 @@ import {
25
17
  SelectTrigger,
26
18
  SelectValue,
27
19
  } from '@/components/ui/select';
20
+ import {
21
+ Sheet,
22
+ SheetContent,
23
+ SheetDescription,
24
+ SheetFooter,
25
+ SheetHeader,
26
+ SheetTitle,
27
+ } from '@/components/ui/sheet';
28
28
  import { Textarea } from '@/components/ui/textarea';
29
29
  import { useFormDraft } from '@/hooks/use-form-draft';
30
30
  import { formatDateTime } from '@/lib/format-date';
@@ -290,152 +290,158 @@ export function PersonInteractionDialog({
290
290
  };
291
291
 
292
292
  return (
293
- <Dialog open={open} onOpenChange={onOpenChange}>
294
- <DialogContent className="sm:max-w-md">
295
- <DialogHeader>
296
- <DialogTitle>
293
+ <Sheet open={open} onOpenChange={onOpenChange}>
294
+ <SheetContent
295
+ side="right"
296
+ className="flex h-full w-full flex-col gap-0 overflow-hidden p-0 sm:max-w-lg"
297
+ >
298
+ <SheetHeader className="shrink-0 border-b px-6 py-4 text-left">
299
+ <SheetTitle>
297
300
  {mode === 'interaction'
298
301
  ? t('registerInteraction')
299
302
  : t('scheduleFollowup')}
300
- </DialogTitle>
301
- {person ? <DialogDescription>{person.name}</DialogDescription> : null}
302
- </DialogHeader>
303
-
304
- {mode === 'interaction' ? (
305
- <Form {...interactionForm}>
306
- <form
307
- onSubmit={interactionForm.handleSubmit(handleInteractionSubmit)}
308
- className="space-y-4"
309
- >
310
- <FormField
311
- control={interactionForm.control}
312
- name="type"
313
- render={({ field }) => (
314
- <FormItem>
315
- <FormLabel>{t('interactionType')}</FormLabel>
316
- <Select onValueChange={field.onChange} value={field.value}>
303
+ </SheetTitle>
304
+ {person ? <SheetDescription>{person.name}</SheetDescription> : null}
305
+ </SheetHeader>
306
+
307
+ <div className="flex-1 overflow-y-auto px-6 py-4">
308
+ {mode === 'interaction' ? (
309
+ <Form {...interactionForm}>
310
+ <form
311
+ onSubmit={interactionForm.handleSubmit(handleInteractionSubmit)}
312
+ className="space-y-4"
313
+ >
314
+ <FormField
315
+ control={interactionForm.control}
316
+ name="type"
317
+ render={({ field }) => (
318
+ <FormItem className="min-w-0">
319
+ <FormLabel>{t('interactionType')}</FormLabel>
320
+ <Select
321
+ onValueChange={field.onChange}
322
+ value={field.value}
323
+ >
324
+ <FormControl>
325
+ <SelectTrigger className="h-9 w-full min-w-0">
326
+ <SelectValue />
327
+ </SelectTrigger>
328
+ </FormControl>
329
+ <SelectContent
330
+ position="popper"
331
+ className="w-[--radix-select-trigger-width] min-w-(--radix-select-trigger-width)"
332
+ >
333
+ {INTERACTION_TYPES.map((type) => (
334
+ <SelectItem key={type} value={type}>
335
+ {t(
336
+ `interactionType_${type}` as Parameters<
337
+ typeof t
338
+ >[0]
339
+ )}
340
+ </SelectItem>
341
+ ))}
342
+ </SelectContent>
343
+ </Select>
344
+ <FormMessage />
345
+ </FormItem>
346
+ )}
347
+ />
348
+ <FormField
349
+ control={interactionForm.control}
350
+ name="notes"
351
+ render={({ field }) => (
352
+ <FormItem>
353
+ <FormLabel>{t('notes')}</FormLabel>
354
+ <FormControl>
355
+ <Textarea
356
+ {...field}
357
+ placeholder={t('notesPlaceholder')}
358
+ rows={4}
359
+ />
360
+ </FormControl>
361
+ <FormMessage />
362
+ </FormItem>
363
+ )}
364
+ />
365
+ {interactionDraftStatus ? (
366
+ <p className="text-xs text-muted-foreground">
367
+ {interactionDraftStatus}
368
+ </p>
369
+ ) : null}
370
+ </form>
371
+ </Form>
372
+ ) : (
373
+ <Form {...followupForm}>
374
+ <form
375
+ onSubmit={followupForm.handleSubmit(handleFollowupSubmit)}
376
+ className="space-y-4"
377
+ >
378
+ <FormField
379
+ control={followupForm.control}
380
+ name="next_action_at"
381
+ render={({ field }) => (
382
+ <FormItem>
383
+ <FormLabel>{t('followupDate')}</FormLabel>
384
+ <FormControl>
385
+ <Input type="datetime-local" {...field} />
386
+ </FormControl>
387
+ <FormMessage />
388
+ </FormItem>
389
+ )}
390
+ />
391
+ <FormField
392
+ control={followupForm.control}
393
+ name="notes"
394
+ render={({ field }) => (
395
+ <FormItem>
396
+ <FormLabel>{t('notes')}</FormLabel>
317
397
  <FormControl>
318
- <SelectTrigger>
319
- <SelectValue />
320
- </SelectTrigger>
398
+ <Textarea
399
+ {...field}
400
+ placeholder={t('notesPlaceholder')}
401
+ rows={4}
402
+ />
321
403
  </FormControl>
322
- <SelectContent>
323
- {INTERACTION_TYPES.map((type) => (
324
- <SelectItem key={type} value={type}>
325
- {t(
326
- `interactionType_${type}` as Parameters<
327
- typeof t
328
- >[0]
329
- )}
330
- </SelectItem>
331
- ))}
332
- </SelectContent>
333
- </Select>
334
- <FormMessage />
335
- </FormItem>
336
- )}
337
- />
338
- <FormField
339
- control={interactionForm.control}
340
- name="notes"
341
- render={({ field }) => (
342
- <FormItem>
343
- <FormLabel>{t('notes')}</FormLabel>
344
- <FormControl>
345
- <Textarea
346
- {...field}
347
- placeholder={t('notesPlaceholder')}
348
- rows={4}
349
- />
350
- </FormControl>
351
- <FormMessage />
352
- </FormItem>
353
- )}
354
- />
355
- {interactionDraftStatus ? (
356
- <p className="text-xs text-muted-foreground">
357
- {interactionDraftStatus}
358
- </p>
359
- ) : null}
360
- <DialogFooter>
361
- <Button
362
- type="button"
363
- variant="outline"
364
- onClick={() => onOpenChange(false)}
365
- disabled={isSubmitting}
366
- >
367
- {t('cancel')}
368
- </Button>
369
- <Button type="submit" disabled={isSubmitting}>
370
- {isSubmitting ? (
371
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
372
- ) : null}
373
- {t('register')}
374
- </Button>
375
- </DialogFooter>
376
- </form>
377
- </Form>
378
- ) : (
379
- <Form {...followupForm}>
380
- <form
381
- onSubmit={followupForm.handleSubmit(handleFollowupSubmit)}
382
- className="space-y-4"
383
- >
384
- <FormField
385
- control={followupForm.control}
386
- name="next_action_at"
387
- render={({ field }) => (
388
- <FormItem>
389
- <FormLabel>{t('followupDate')}</FormLabel>
390
- <FormControl>
391
- <Input type="datetime-local" {...field} />
392
- </FormControl>
393
- <FormMessage />
394
- </FormItem>
395
- )}
396
- />
397
- <FormField
398
- control={followupForm.control}
399
- name="notes"
400
- render={({ field }) => (
401
- <FormItem>
402
- <FormLabel>{t('notes')}</FormLabel>
403
- <FormControl>
404
- <Textarea
405
- {...field}
406
- placeholder={t('notesPlaceholder')}
407
- rows={4}
408
- />
409
- </FormControl>
410
- <FormMessage />
411
- </FormItem>
412
- )}
413
- />
414
- {followupDraftStatus ? (
415
- <p className="text-xs text-muted-foreground">
416
- {followupDraftStatus}
417
- </p>
418
- ) : null}
419
- <DialogFooter>
420
- <Button
421
- type="button"
422
- variant="outline"
423
- onClick={() => onOpenChange(false)}
424
- disabled={isSubmitting}
425
- >
426
- {t('cancel')}
427
- </Button>
428
- <Button type="submit" disabled={isSubmitting}>
429
- {isSubmitting ? (
430
- <Loader2 className="mr-2 h-4 w-4 animate-spin" />
431
- ) : null}
432
- {t('save')}
433
- </Button>
434
- </DialogFooter>
435
- </form>
436
- </Form>
437
- )}
438
- </DialogContent>
439
- </Dialog>
404
+ <FormMessage />
405
+ </FormItem>
406
+ )}
407
+ />
408
+ {followupDraftStatus ? (
409
+ <p className="text-xs text-muted-foreground">
410
+ {followupDraftStatus}
411
+ </p>
412
+ ) : null}
413
+ </form>
414
+ </Form>
415
+ )}
416
+ </div>
417
+
418
+ <SheetFooter className="shrink-0 border-t px-6 py-3 sm:justify-end">
419
+ <Button
420
+ type="button"
421
+ variant="outline"
422
+ onClick={() => onOpenChange(false)}
423
+ disabled={isSubmitting}
424
+ >
425
+ {t('cancel')}
426
+ </Button>
427
+ <Button
428
+ type="button"
429
+ disabled={isSubmitting}
430
+ onClick={() => {
431
+ if (mode === 'interaction') {
432
+ void interactionForm.handleSubmit(handleInteractionSubmit)();
433
+ return;
434
+ }
435
+ void followupForm.handleSubmit(handleFollowupSubmit)();
436
+ }}
437
+ >
438
+ {isSubmitting ? (
439
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
440
+ ) : null}
441
+ {mode === 'interaction' ? t('register') : t('save')}
442
+ </Button>
443
+ </SheetFooter>
444
+ </SheetContent>
445
+ </Sheet>
440
446
  );
441
447
  }
@@ -604,9 +604,19 @@ export function ProposalsManagementPage({
604
604
  proposal.person?.trade_name ||
605
605
  proposal.person?.name ||
606
606
  `#${proposal.person_id}`;
607
+ const editable = canEditProposal(proposal.status);
607
608
 
608
609
  return (
609
- <TableRow key={proposal.id}>
610
+ <TableRow
611
+ key={proposal.id}
612
+ onDoubleClick={() => {
613
+ if (!editable) return;
614
+ handleEdit(proposal);
615
+ }}
616
+ className={cn(
617
+ editable && 'cursor-pointer hover:bg-muted/20'
618
+ )}
619
+ >
610
620
  <TableCell>
611
621
  <div className="space-y-1">
612
622
  <div className="font-medium text-foreground">
@@ -776,11 +786,19 @@ export function ProposalsManagementPage({
776
786
  proposal.person?.trade_name ||
777
787
  proposal.person?.name ||
778
788
  `#${proposal.person_id}`;
789
+ const editable = canEditProposal(proposal.status);
779
790
 
780
791
  return (
781
792
  <Card
782
793
  key={proposal.id}
783
- className="h-full overflow-hidden border-border/70 py-0"
794
+ onDoubleClick={() => {
795
+ if (!editable) return;
796
+ handleEdit(proposal);
797
+ }}
798
+ className={cn(
799
+ 'h-full overflow-hidden border-border/70 py-0',
800
+ editable && 'cursor-pointer'
801
+ )}
784
802
  >
785
803
  <CardContent className="flex h-full flex-col gap-3 p-4">
786
804
  <div className="flex items-start justify-between gap-3">
@@ -117,8 +117,8 @@
117
117
  "selectType": "Selecione o tipo",
118
118
  "birthDate": "Data de Nascimento",
119
119
  "selectDate": "Selecionar data",
120
- "gender": "Genero",
121
- "selectGender": "Selecione o genero",
120
+ "gender": "Gênero",
121
+ "selectGender": "Selecione o gênero",
122
122
  "genderMale": "Masculino",
123
123
  "genderFemale": "Feminino",
124
124
  "genderOther": "Outro",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/contact",
3
- "version": "0.0.347",
3
+ "version": "0.0.350",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "dependencies": {
@@ -10,13 +10,13 @@
10
10
  "@nestjs/jwt": "^11",
11
11
  "@nestjs/mapped-types": "*",
12
12
  "playwright": "^1.49.0",
13
- "@hed-hog/address": "0.0.347",
14
- "@hed-hog/core": "0.0.347",
15
13
  "@hed-hog/api": "0.0.8",
16
- "@hed-hog/api-prisma": "0.0.6",
14
+ "@hed-hog/address": "0.0.350",
17
15
  "@hed-hog/api-pagination": "0.0.7",
18
16
  "@hed-hog/api-mail": "0.0.9",
19
- "@hed-hog/api-locale": "0.0.14"
17
+ "@hed-hog/api-prisma": "0.0.6",
18
+ "@hed-hog/api-locale": "0.0.14",
19
+ "@hed-hog/core": "0.0.350"
20
20
  },
21
21
  "exports": {
22
22
  ".": {
@@ -1,6 +1,7 @@
1
1
  import { Type } from 'class-transformer';
2
2
  import {
3
3
  IsArray,
4
+ IsDateString,
4
5
  IsEmail,
5
6
  IsIn,
6
7
  IsInt,
@@ -56,6 +57,14 @@ export class CreateAccountDTO {
56
57
  @IsString()
57
58
  trade_name?: string | null;
58
59
 
60
+ @IsOptional()
61
+ @IsDateString()
62
+ foundation_date?: string | null;
63
+
64
+ @IsOptional()
65
+ @IsString()
66
+ legal_nature?: string | null;
67
+
59
68
  @IsIn([PersonStatus.ACTIVE, PersonStatus.INACTIVE])
60
69
  status: PersonStatus;
61
70
 
@@ -4,40 +4,40 @@ import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
4
4
  import { Prisma, PrismaService } from '@hed-hog/api-prisma';
5
5
  import { FileService, IntegrationDeveloperApiService, SettingService } from '@hed-hog/core';
6
6
  import {
7
- BadRequestException,
8
- Inject,
9
- Injectable,
10
- Logger,
11
- NotFoundException,
12
- forwardRef,
7
+ BadRequestException,
8
+ Inject,
9
+ Injectable,
10
+ Logger,
11
+ NotFoundException,
12
+ forwardRef,
13
13
  } from '@nestjs/common';
14
14
  import {
15
- ACCOUNT_LIFECYCLE_STAGES,
16
- type AccountLifecycleStage,
17
- type CreateAccountDTO,
18
- type UpdateAccountDTO,
15
+ ACCOUNT_LIFECYCLE_STAGES,
16
+ type AccountLifecycleStage,
17
+ type CreateAccountDTO,
18
+ type UpdateAccountDTO,
19
19
  } from './dto/account.dto';
20
20
  import {
21
- type ActivityListQueryDTO,
22
- type CrmActivityPriority,
23
- type CrmActivitySourceKind,
24
- type CrmActivityStatus,
25
- type CrmActivityType,
21
+ type ActivityListQueryDTO,
22
+ type CrmActivityPriority,
23
+ type CrmActivitySourceKind,
24
+ type CrmActivityStatus,
25
+ type CrmActivityType,
26
26
  } from './dto/activity.dto';
27
27
  import { CreateFollowupDTO } from './dto/create-followup.dto';
28
28
  import {
29
- CreateInteractionDTO,
30
- PersonInteractionTypeDTO,
29
+ CreateInteractionDTO,
30
+ PersonInteractionTypeDTO,
31
31
  } from './dto/create-interaction.dto';
32
32
  import { CreateDTO } from './dto/create.dto';
33
33
  import {
34
- type CrmDashboardPeriod,
35
- type DashboardQueryDTO,
34
+ type CrmDashboardPeriod,
35
+ type DashboardQueryDTO,
36
36
  } from './dto/dashboard-query.dto';
37
37
  import { CheckPersonDuplicatesQueryDTO } from './dto/duplicates-query.dto';
38
38
  import {
39
- FollowupListQueryDTO,
40
- FollowupStatsQueryDTO,
39
+ FollowupListQueryDTO,
40
+ FollowupStatsQueryDTO,
41
41
  } from './dto/followup-query.dto';
42
42
  import { CRM_IMPORT_FIELDS } from './dto/import.dto';
43
43
  import { MergePersonDTO } from './dto/merge.dto';
@@ -117,6 +117,8 @@ type AccountListItem = {
117
117
  id: number;
118
118
  name: string;
119
119
  trade_name: string | null;
120
+ foundation_date: string | null;
121
+ legal_nature: string | null;
120
122
  status: 'active' | 'inactive';
121
123
  industry: string | null;
122
124
  website: string | null;
@@ -4326,6 +4328,21 @@ export class PersonService {
4326
4328
  return Number.isNaN(date.getTime()) ? null : date.toISOString();
4327
4329
  }
4328
4330
 
4331
+ private normalizeDateOrNull(value: unknown): string | null {
4332
+ if (!value) return null;
4333
+
4334
+ if (value instanceof Date) {
4335
+ return Number.isNaN(value.getTime()) ? null : value.toISOString().slice(0, 10);
4336
+ }
4337
+
4338
+ const normalized = this.normalizeTextOrNull(value);
4339
+ if (!normalized) return null;
4340
+ if (/^\d{4}-\d{2}-\d{2}$/.test(normalized)) return normalized;
4341
+
4342
+ const date = new Date(normalized);
4343
+ return Number.isNaN(date.getTime()) ? null : date.toISOString().slice(0, 10);
4344
+ }
4345
+
4329
4346
  private coerceNumber(value: unknown): number {
4330
4347
  const parsed = Number(value);
4331
4348
  return Number.isFinite(parsed) ? parsed : 0;
@@ -4622,6 +4639,8 @@ export class PersonService {
4622
4639
  id: person.id,
4623
4640
  name: person.name,
4624
4641
  trade_name: company.trade_name ?? null,
4642
+ foundation_date: this.normalizeDateOrNull(company.foundation_date),
4643
+ legal_nature: this.normalizeTextOrNull(company.legal_nature),
4625
4644
  status: person.status,
4626
4645
  industry: company.industry ?? null,
4627
4646
  website: company.website ?? null,