@hed-hog/contact 0.0.330 → 0.0.331

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.
@@ -41,7 +41,7 @@ import {
41
41
  } from '@/components/ui/sheet';
42
42
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
43
43
  import { zodResolver } from '@hookform/resolvers/zod';
44
- import { ChevronsUpDown, Loader2, Plus, X } from 'lucide-react';
44
+ import { ChevronsUpDown, Loader2, Pencil, Plus, X } from 'lucide-react';
45
45
  import { useTranslations } from 'next-intl';
46
46
  import { useEffect, useMemo, useRef, useState } from 'react';
47
47
  import {
@@ -60,6 +60,10 @@ type PersonOption = {
60
60
  avatar_id?: number | null;
61
61
  };
62
62
 
63
+ type PersonOptionApi = PersonOption & {
64
+ avatar_id?: number | null;
65
+ };
66
+
63
67
  type CreatePersonValues = {
64
68
  name: string;
65
69
  type: 'individual' | 'company';
@@ -563,6 +567,8 @@ export function PersonPicker({
563
567
  entityLabel,
564
568
  value,
565
569
  onChange,
570
+ onEditSelection,
571
+ onCreateNew,
566
572
  selectPlaceholder,
567
573
  personTypeFilter = 'all',
568
574
  createType = 'individual',
@@ -570,11 +576,15 @@ export function PersonPicker({
570
576
  initialSelectedLabel = '',
571
577
  disabled = false,
572
578
  clearable = true,
579
+ showEditButton = false,
580
+ editAriaLabel,
573
581
  }: {
574
582
  label: string;
575
583
  entityLabel: string;
576
584
  value?: string | number | null;
577
585
  onChange: (personId: number | null, personName: string) => void;
586
+ onEditSelection?: (personId: number) => void;
587
+ onCreateNew?: (initialName?: string) => void;
578
588
  selectPlaceholder: string;
579
589
  personTypeFilter?: PersonTypeFilter;
580
590
  createType?: Exclude<PersonTypeFilter, 'all'>;
@@ -582,6 +592,8 @@ export function PersonPicker({
582
592
  initialSelectedLabel?: string;
583
593
  disabled?: boolean;
584
594
  clearable?: boolean;
595
+ showEditButton?: boolean;
596
+ editAriaLabel?: string;
585
597
  }) {
586
598
  const { request } = useApp();
587
599
  const t = usePersonPickerTranslations();
@@ -685,9 +697,11 @@ export function PersonPicker({
685
697
  }
686
698
 
687
699
  if (payload && 'data' in payload && Array.isArray(payload.data)) {
688
- return payload.data.map((p) => ({
700
+ const options = payload.data as PersonOptionApi[];
701
+
702
+ return options.map((p) => ({
689
703
  ...p,
690
- avatarId: (p as any).avatar_id ?? p.avatarId ?? null,
704
+ avatarId: p.avatar_id ?? p.avatarId ?? null,
691
705
  })) as PersonOption[];
692
706
  }
693
707
 
@@ -701,6 +715,9 @@ export function PersonPicker({
701
715
  ? String(value)
702
716
  : '';
703
717
  const hasValue = normalizedValue.length > 0;
718
+ const selectedPersonId = hasValue ? Number(normalizedValue) : Number.NaN;
719
+ const canEditSelection =
720
+ Number.isFinite(selectedPersonId) && selectedPersonId > 0;
704
721
 
705
722
  const selectedPersonFromList = personOptionsData.find(
706
723
  (p) => String(p.id) === normalizedValue
@@ -750,7 +767,7 @@ export function PersonPicker({
750
767
  <div className="grid min-w-0 gap-2">
751
768
  {label ? <Label>{label}</Label> : null}
752
769
 
753
- <div className="grid min-w-0 grid-cols-[minmax(0,1fr)_auto] gap-2 sm:grid-cols-[minmax(0,1fr)_auto_auto]">
770
+ <div className="grid min-w-0 grid-cols-[minmax(0,1fr)_auto] gap-2 sm:grid-cols-[minmax(0,1fr)_auto_auto_auto]">
754
771
  <Popover
755
772
  open={!disabled && personOpen}
756
773
  onOpenChange={(open) => {
@@ -816,10 +833,15 @@ export function PersonPicker({
816
833
  personOptionsData.length === 0
817
834
  ) {
818
835
  e.preventDefault();
819
- openCreateWithPrefill(
820
- e.currentTarget,
821
- personSearch.trim()
822
- );
836
+ if (onCreateNew) {
837
+ setPersonOpen(false);
838
+ onCreateNew(personSearch.trim());
839
+ } else {
840
+ openCreateWithPrefill(
841
+ e.currentTarget,
842
+ personSearch.trim()
843
+ );
844
+ }
823
845
  }
824
846
  }}
825
847
  />
@@ -837,10 +859,15 @@ export function PersonPicker({
837
859
  variant="outline"
838
860
  className="w-full"
839
861
  onClick={(event) => {
840
- openCreateWithPrefill(
841
- event.currentTarget,
842
- personSearch.trim()
843
- );
862
+ if (onCreateNew) {
863
+ setPersonOpen(false);
864
+ onCreateNew(personSearch.trim());
865
+ } else {
866
+ openCreateWithPrefill(
867
+ event.currentTarget,
868
+ personSearch.trim()
869
+ );
870
+ }
844
871
  }}
845
872
  >
846
873
  {t('actions.createNew')}
@@ -914,6 +941,27 @@ export function PersonPicker({
914
941
  </Button>
915
942
  ) : null}
916
943
 
944
+ {showEditButton ? (
945
+ <Button
946
+ type="button"
947
+ variant="outline"
948
+ size="icon"
949
+ className="shrink-0"
950
+ disabled={disabled || !canEditSelection}
951
+ onClick={() => {
952
+ if (!canEditSelection || !onEditSelection) {
953
+ return;
954
+ }
955
+ onEditSelection(selectedPersonId);
956
+ }}
957
+ aria-label={
958
+ editAriaLabel ?? t('actions.createEntityAria', { entityLabel })
959
+ }
960
+ >
961
+ <Pencil className="h-4 w-4" />
962
+ </Button>
963
+ ) : null}
964
+
917
965
  <Button
918
966
  type="button"
919
967
  variant="outline"
@@ -921,9 +969,14 @@ export function PersonPicker({
921
969
  className="shrink-0"
922
970
  disabled={disabled}
923
971
  onClick={(event) => {
924
- captureParentScrollPosition(event.currentTarget);
925
- setPersonOpen(false);
926
- setCreatePersonOpen(true);
972
+ if (onCreateNew) {
973
+ setPersonOpen(false);
974
+ onCreateNew(personSearch.trim());
975
+ } else {
976
+ captureParentScrollPosition(event.currentTarget);
977
+ setPersonOpen(false);
978
+ setCreatePersonOpen(true);
979
+ }
927
980
  }}
928
981
  aria-label={t('actions.createEntityAria', { entityLabel })}
929
982
  >
@@ -801,7 +801,7 @@ export default function AccountsPage() {
801
801
  <Page>
802
802
  <PageHeader
803
803
  breadcrumbs={[
804
- { label: 'Home', href: '/' },
804
+ { label: crmT('breadcrumbs.home'), href: '/' },
805
805
  { label: crmT('breadcrumbs.crm'), href: '/contact/dashboard' },
806
806
  { label: t('title') },
807
807
  ]}
@@ -311,7 +311,7 @@ export default function CrmActivitiesPage() {
311
311
  <Page>
312
312
  <PageHeader
313
313
  breadcrumbs={[
314
- { label: 'Home', href: '/' },
314
+ { label: crmT('breadcrumbs.home'), href: '/' },
315
315
  { label: crmT('breadcrumbs.crm'), href: '/contact/dashboard' },
316
316
  { label: t('title') },
317
317
  ]}
@@ -619,7 +619,7 @@ export default function CrmFollowupsPage() {
619
619
  <Page>
620
620
  <PageHeader
621
621
  breadcrumbs={[
622
- { label: 'Home', href: '/' },
622
+ { label: crmT('breadcrumbs.home'), href: '/' },
623
623
  { label: crmT('breadcrumbs.crm'), href: '/contact/dashboard' },
624
624
  { label: t('title') },
625
625
  ]}
@@ -791,7 +791,7 @@ export default function PeoplePage() {
791
791
  <Page>
792
792
  <PageHeader
793
793
  breadcrumbs={[
794
- { label: 'Home', href: '/' },
794
+ { label: crmT('breadcrumbs.home'), href: '/' },
795
795
  { label: crmT('breadcrumbs.crm'), href: '/contact/dashboard' },
796
796
  {
797
797
  label: allowCompanyRegistration
@@ -1,50 +1,50 @@
1
1
  'use client';
2
2
 
3
3
  import {
4
- EmptyState,
5
- Page,
6
- PageHeader,
7
- PaginationFooter,
8
- SearchBar,
9
- type SearchBarControl,
4
+ EmptyState,
5
+ Page,
6
+ PageHeader,
7
+ PaginationFooter,
8
+ SearchBar,
9
+ type SearchBarControl,
10
10
  } from '@/components/entity-list';
11
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
11
12
  import { Badge } from '@/components/ui/badge';
12
13
  import { Button } from '@/components/ui/button';
13
14
  import { Card, CardContent } from '@/components/ui/card';
14
15
  import {
15
- DropdownMenu,
16
- DropdownMenuContent,
17
- DropdownMenuItem,
18
- DropdownMenuSeparator,
19
- DropdownMenuTrigger,
16
+ DropdownMenu,
17
+ DropdownMenuContent,
18
+ DropdownMenuItem,
19
+ DropdownMenuSeparator,
20
+ DropdownMenuTrigger,
20
21
  } from '@/components/ui/dropdown-menu';
21
- import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
22
22
  import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
23
23
  import { Skeleton } from '@/components/ui/skeleton';
24
24
  import {
25
- Table,
26
- TableBody,
27
- TableCell,
28
- TableHead,
29
- TableHeader,
30
- TableRow,
25
+ Table,
26
+ TableBody,
27
+ TableCell,
28
+ TableHead,
29
+ TableHeader,
30
+ TableRow,
31
31
  } from '@/components/ui/table';
32
32
  import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
33
33
  import { cn } from '@/lib/utils';
34
34
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
35
35
  import {
36
- CheckCircle2,
37
- Clock3,
38
- FileCheck2,
39
- FileText,
40
- LayoutGrid,
41
- List,
42
- Loader2,
43
- MoreHorizontal,
44
- Pencil,
45
- RefreshCcw,
46
- Send,
47
- XCircle,
36
+ CheckCircle2,
37
+ Clock3,
38
+ FileCheck2,
39
+ FileText,
40
+ LayoutGrid,
41
+ List,
42
+ Loader2,
43
+ MoreHorizontal,
44
+ Pencil,
45
+ RefreshCcw,
46
+ Send,
47
+ XCircle,
48
48
  } from 'lucide-react';
49
49
  import { useTranslations } from 'next-intl';
50
50
  import { useEffect, useMemo, useState } from 'react';
@@ -53,8 +53,8 @@ import { toast } from 'sonner';
53
53
  import type { PaginatedResult } from '../../person/_components/person-types';
54
54
  import { ProposalFormSheet } from './proposal-form-sheet';
55
55
  import {
56
- openStoredFile,
57
- type GenerateProposalDocumentResponse,
56
+ openStoredFile,
57
+ type GenerateProposalDocumentResponse,
58
58
  } from './proposal-types';
59
59
 
60
60
  type ProposalStatus =
@@ -161,7 +161,9 @@ function getAvatarInitials(name?: string | null) {
161
161
  if (!name) return '?';
162
162
  const parts = name.trim().split(/\s+/);
163
163
  if (parts.length === 1) return (parts[0] ?? '').slice(0, 2).toUpperCase();
164
- return ((parts[0]?.[0] ?? '') + (parts[parts.length - 1]?.[0] ?? '')).toUpperCase();
164
+ return (
165
+ (parts[0]?.[0] ?? '') + (parts[parts.length - 1]?.[0] ?? '')
166
+ ).toUpperCase();
165
167
  }
166
168
 
167
169
  function canSubmitProposal(status?: ProposalStatus | null) {
@@ -199,7 +201,9 @@ export function ProposalsManagementPage({
199
201
  const [viewMode, setViewMode] = useState<ProposalViewMode>('table');
200
202
  const [actionKey, setActionKey] = useState<string | null>(null);
201
203
  const [editSheetOpen, setEditSheetOpen] = useState(false);
202
- const [editingProposalId, setEditingProposalId] = useState<number | null>(null);
204
+ const [editingProposalId, setEditingProposalId] = useState<number | null>(
205
+ null
206
+ );
203
207
  const [editingPersonId, setEditingPersonId] = useState<number | null>(null);
204
208
 
205
209
  useEffect(() => {
@@ -452,7 +456,7 @@ export function ProposalsManagementPage({
452
456
  <Page>
453
457
  <PageHeader
454
458
  breadcrumbs={[
455
- { label: 'Home', href: '/' },
459
+ { label: crmT('breadcrumbs.home'), href: '/' },
456
460
  { label: crmT('breadcrumbs.crm'), href: '/contact/dashboard' },
457
461
  { label: pageTitle },
458
462
  ]}
@@ -615,7 +619,9 @@ export function ProposalsManagementPage({
615
619
  <TableCell>
616
620
  <div className="flex items-center gap-3">
617
621
  <Avatar className="h-8 w-8 shrink-0">
618
- <AvatarImage src={getPersonAvatarUrl(proposal.person?.avatar_id)} />
622
+ <AvatarImage
623
+ src={getPersonAvatarUrl(proposal.person?.avatar_id)}
624
+ />
619
625
  <AvatarFallback className="bg-primary/10 text-[11px] font-semibold text-primary">
620
626
  {getAvatarInitials(customerName)}
621
627
  </AvatarFallback>
@@ -673,8 +679,16 @@ export function ProposalsManagementPage({
673
679
  <TableCell className="text-right">
674
680
  <DropdownMenu>
675
681
  <DropdownMenuTrigger asChild>
676
- <Button variant="ghost" size="icon" className="h-8 w-8">
677
- {actionKey?.startsWith(`${proposal.id}-`) || actionKey === `generate-pdf-${proposal.id}` || actionKey === `submit-${proposal.id}` || actionKey === `approve-${proposal.id}` || actionKey === `reject-${proposal.id}` ? (
682
+ <Button
683
+ variant="ghost"
684
+ size="icon"
685
+ className="h-8 w-8"
686
+ >
687
+ {actionKey?.startsWith(`${proposal.id}-`) ||
688
+ actionKey === `generate-pdf-${proposal.id}` ||
689
+ actionKey === `submit-${proposal.id}` ||
690
+ actionKey === `approve-${proposal.id}` ||
691
+ actionKey === `reject-${proposal.id}` ? (
678
692
  <Loader2 className="h-4 w-4 animate-spin" />
679
693
  ) : (
680
694
  <MoreHorizontal className="h-4 w-4" />
@@ -683,7 +697,9 @@ export function ProposalsManagementPage({
683
697
  </DropdownMenuTrigger>
684
698
  <DropdownMenuContent align="end">
685
699
  {canEditProposal(proposal.status) ? (
686
- <DropdownMenuItem onClick={() => handleEdit(proposal)}>
700
+ <DropdownMenuItem
701
+ onClick={() => handleEdit(proposal)}
702
+ >
687
703
  <Pencil className="mr-2 h-4 w-4" />
688
704
  {proposalT('actions.edit')}
689
705
  </DropdownMenuItem>
@@ -692,7 +708,9 @@ export function ProposalsManagementPage({
692
708
  {canSubmitProposal(proposal.status) ? (
693
709
  <DropdownMenuItem
694
710
  disabled={actionKey === `submit-${proposal.id}`}
695
- onClick={() => void handleStatusAction(proposal, 'submit')}
711
+ onClick={() =>
712
+ void handleStatusAction(proposal, 'submit')
713
+ }
696
714
  >
697
715
  <Send className="mr-2 h-4 w-4" />
698
716
  {proposalT('actions.submit')}
@@ -703,8 +721,12 @@ export function ProposalsManagementPage({
703
721
  !proposal.current_user_has_approved ? (
704
722
  <>
705
723
  <DropdownMenuItem
706
- disabled={actionKey === `approve-${proposal.id}`}
707
- onClick={() => void handleStatusAction(proposal, 'approve')}
724
+ disabled={
725
+ actionKey === `approve-${proposal.id}`
726
+ }
727
+ onClick={() =>
728
+ void handleStatusAction(proposal, 'approve')
729
+ }
708
730
  >
709
731
  <CheckCircle2 className="mr-2 h-4 w-4" />
710
732
  {proposalT('actions.approve')}
@@ -712,7 +734,9 @@ export function ProposalsManagementPage({
712
734
  <DropdownMenuItem
713
735
  disabled={actionKey === `reject-${proposal.id}`}
714
736
  className="text-destructive focus:text-destructive"
715
- onClick={() => void handleStatusAction(proposal, 'reject')}
737
+ onClick={() =>
738
+ void handleStatusAction(proposal, 'reject')
739
+ }
716
740
  >
717
741
  <XCircle className="mr-2 h-4 w-4" />
718
742
  {proposalT('actions.reject')}
@@ -723,7 +747,9 @@ export function ProposalsManagementPage({
723
747
  <DropdownMenuSeparator />
724
748
 
725
749
  <DropdownMenuItem
726
- disabled={actionKey === `generate-pdf-${proposal.id}`}
750
+ disabled={
751
+ actionKey === `generate-pdf-${proposal.id}`
752
+ }
727
753
  onClick={() => void handleGeneratePdf(proposal)}
728
754
  >
729
755
  <FileText className="mr-2 h-4 w-4" />
@@ -755,11 +781,13 @@ export function ProposalsManagementPage({
755
781
  <div className="flex items-start justify-between gap-3">
756
782
  <div className="flex min-w-0 items-start gap-2.5">
757
783
  <Avatar className="mt-0.5 h-8 w-8 shrink-0">
758
- <AvatarImage src={getPersonAvatarUrl(proposal.person?.avatar_id)} />
759
- <AvatarFallback className="bg-primary/10 text-[11px] font-semibold text-primary">
760
- {getAvatarInitials(customerName)}
761
- </AvatarFallback>
762
- </Avatar>
784
+ <AvatarImage
785
+ src={getPersonAvatarUrl(proposal.person?.avatar_id)}
786
+ />
787
+ <AvatarFallback className="bg-primary/10 text-[11px] font-semibold text-primary">
788
+ {getAvatarInitials(customerName)}
789
+ </AvatarFallback>
790
+ </Avatar>
763
791
  <div className="min-w-0 space-y-1">
764
792
  <p className="line-clamp-2 text-sm font-semibold text-foreground">
765
793
  {proposal.title}
@@ -828,7 +856,10 @@ export function ProposalsManagementPage({
828
856
  <DropdownMenu>
829
857
  <DropdownMenuTrigger asChild>
830
858
  <Button variant="outline" size="sm" className="gap-1.5">
831
- {actionKey === `generate-pdf-${proposal.id}` || actionKey === `submit-${proposal.id}` || actionKey === `approve-${proposal.id}` || actionKey === `reject-${proposal.id}` ? (
859
+ {actionKey === `generate-pdf-${proposal.id}` ||
860
+ actionKey === `submit-${proposal.id}` ||
861
+ actionKey === `approve-${proposal.id}` ||
862
+ actionKey === `reject-${proposal.id}` ? (
832
863
  <Loader2 className="h-3.5 w-3.5 animate-spin" />
833
864
  ) : (
834
865
  <MoreHorizontal className="h-3.5 w-3.5" />
@@ -838,7 +869,9 @@ export function ProposalsManagementPage({
838
869
  </DropdownMenuTrigger>
839
870
  <DropdownMenuContent align="end">
840
871
  {canEditProposal(proposal.status) ? (
841
- <DropdownMenuItem onClick={() => handleEdit(proposal)}>
872
+ <DropdownMenuItem
873
+ onClick={() => handleEdit(proposal)}
874
+ >
842
875
  <Pencil className="mr-2 h-4 w-4" />
843
876
  {proposalT('actions.edit')}
844
877
  </DropdownMenuItem>
@@ -847,7 +880,9 @@ export function ProposalsManagementPage({
847
880
  {canSubmitProposal(proposal.status) ? (
848
881
  <DropdownMenuItem
849
882
  disabled={actionKey === `submit-${proposal.id}`}
850
- onClick={() => void handleStatusAction(proposal, 'submit')}
883
+ onClick={() =>
884
+ void handleStatusAction(proposal, 'submit')
885
+ }
851
886
  >
852
887
  <Send className="mr-2 h-4 w-4" />
853
888
  {proposalT('actions.submit')}
@@ -859,7 +894,9 @@ export function ProposalsManagementPage({
859
894
  <>
860
895
  <DropdownMenuItem
861
896
  disabled={actionKey === `approve-${proposal.id}`}
862
- onClick={() => void handleStatusAction(proposal, 'approve')}
897
+ onClick={() =>
898
+ void handleStatusAction(proposal, 'approve')
899
+ }
863
900
  >
864
901
  <CheckCircle2 className="mr-2 h-4 w-4" />
865
902
  {proposalT('actions.approve')}
@@ -867,7 +904,9 @@ export function ProposalsManagementPage({
867
904
  <DropdownMenuItem
868
905
  disabled={actionKey === `reject-${proposal.id}`}
869
906
  className="text-destructive focus:text-destructive"
870
- onClick={() => void handleStatusAction(proposal, 'reject')}
907
+ onClick={() =>
908
+ void handleStatusAction(proposal, 'reject')
909
+ }
871
910
  >
872
911
  <XCircle className="mr-2 h-4 w-4" />
873
912
  {proposalT('actions.reject')}
@@ -501,6 +501,7 @@
501
501
  },
502
502
  "CrmMenu": {
503
503
  "breadcrumbs": {
504
+ "home": "Home",
504
505
  "crm": "CRM"
505
506
  },
506
507
  "current": "Current",
@@ -500,6 +500,7 @@
500
500
  },
501
501
  "CrmMenu": {
502
502
  "breadcrumbs": {
503
+ "home": "Home",
503
504
  "crm": "CRM"
504
505
  },
505
506
  "current": "Atual",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/contact",
3
- "version": "0.0.330",
3
+ "version": "0.0.331",
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/api-locale": "0.0.14",
13
14
  "@hed-hog/api": "0.0.8",
14
- "@hed-hog/api-pagination": "0.0.7",
15
- "@hed-hog/api-prisma": "0.0.6",
16
- "@hed-hog/address": "0.0.330",
17
- "@hed-hog/core": "0.0.330",
18
15
  "@hed-hog/api-mail": "0.0.9",
19
- "@hed-hog/api-locale": "0.0.14"
16
+ "@hed-hog/api-prisma": "0.0.6",
17
+ "@hed-hog/address": "0.0.331",
18
+ "@hed-hog/core": "0.0.331",
19
+ "@hed-hog/api-pagination": "0.0.7"
20
20
  },
21
21
  "exports": {
22
22
  ".": {