@hed-hog/contact 0.0.333 → 0.0.347

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,172 +1,172 @@
1
- export type ProposalStatus =
2
- | 'draft'
3
- | 'pending_approval'
4
- | 'approved'
5
- | 'rejected'
6
- | 'cancelled'
7
- | 'expired'
8
- | 'contract_generated';
9
-
10
- export type ProposalContractCategory =
11
- | 'employee'
12
- | 'contractor'
13
- | 'client'
14
- | 'supplier'
15
- | 'vendor'
16
- | 'partner'
17
- | 'internal'
18
- | 'other';
19
-
20
- export type ProposalContractType =
21
- | 'clt'
22
- | 'pj'
23
- | 'freelancer_agreement'
24
- | 'service_agreement'
25
- | 'fixed_term'
26
- | 'recurring_service'
27
- | 'nda'
28
- | 'amendment'
29
- | 'addendum'
30
- | 'other';
31
-
32
- export type ProposalBillingModel =
33
- | 'time_and_material'
34
- | 'monthly_retainer'
35
- | 'fixed_price';
36
-
37
- export type ProposalItemType =
38
- | 'service'
39
- | 'product'
40
- | 'fee'
41
- | 'discount'
42
- | 'note'
43
- | 'other';
44
-
45
- export type ProposalRecurrence =
46
- | 'one_time'
47
- | 'monthly'
48
- | 'quarterly'
49
- | 'yearly'
50
- | 'other';
51
-
52
- export type ProposalItem = {
53
- id?: number;
54
- name: string;
55
- description?: string | null;
56
- quantity?: number | null;
57
- unit_amount_cents?: number | null;
58
- total_amount_cents?: number | null;
59
- item_type?: ProposalItemType | string | null;
60
- term_type?: string | null;
61
- recurrence?: ProposalRecurrence | string | null;
62
- start_date?: string | null;
63
- end_date?: string | null;
64
- due_day?: number | null;
65
- };
66
-
67
- export type ProposalRevision = {
68
- id: number;
69
- revision_number: number;
70
- is_current?: boolean;
71
- title: string;
72
- summary?: string | null;
73
- valid_until?: string | null;
74
- status?: ProposalStatus | string;
75
- proposal_item?: ProposalItem[];
76
- };
77
-
78
- export type ProposalApproval = {
79
- id: number;
80
- status?: string | null;
81
- decision_note?: string | null;
82
- submitted_at?: string | null;
83
- decided_at?: string | null;
84
- };
85
-
86
- export type ProposalDocument = {
87
- id: number;
88
- file_id?: number | null;
89
- file_name?: string | null;
90
- mime_type?: string | null;
91
- document_type?: string | null;
92
- is_current?: boolean | null;
93
- created_at?: string | null;
94
- };
95
-
96
- export type ProposalIntegrationLink = {
97
- targetModule?: string;
98
- targetEntityType?: string;
99
- targetEntityId?: string;
100
- };
101
-
102
- export type ProposalRecord = {
103
- id: number;
104
- person_id: number;
105
- code?: string | null;
106
- title: string;
107
- status: ProposalStatus;
108
- contract_category?: ProposalContractCategory | null;
109
- contract_type?: ProposalContractType | null;
110
- billing_model?: ProposalBillingModel | null;
111
- currency_code?: string | null;
112
- subtotal_amount_cents?: number | null;
113
- discount_amount_cents?: number | null;
114
- tax_amount_cents?: number | null;
115
- total_amount_cents?: number | null;
116
- valid_until?: string | null;
117
- current_revision_number?: number | null;
118
- approval_count?: number | null;
119
- required_approvals?: number | null;
120
- current_user_has_approved?: boolean | null;
121
- notes?: string | null;
122
- created_at?: string | null;
123
- updated_at?: string | null;
124
- approved_at?: string | null;
125
- person?: {
126
- id: number;
127
- name?: string | null;
128
- trade_name?: string | null;
129
- email?: string | null;
130
- phone?: string | null;
131
- avatar_id?: number | null;
132
- } | null;
133
- proposal_revision?: ProposalRevision[];
134
- proposal_approval?: ProposalApproval[];
135
- proposal_document?: ProposalDocument[];
136
- integration_links?: ProposalIntegrationLink[];
137
- };
138
-
139
- export type GenerateProposalDocumentResponse = {
140
- fileId?: number | null;
141
- fileName?: string | null;
142
- downloadUrl?: string | null;
143
- };
144
-
145
- // ─── Shared utilities ────────────────────────────────────────────────────────
146
-
147
- export function formatEnumLabel(value?: string | null) {
148
- if (!value) return '—';
149
- return value
150
- .split('_')
151
- .filter(Boolean)
152
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
153
- .join(' ');
154
- }
155
-
156
- export function getCurrentRevision(proposal?: ProposalRecord | null) {
157
- if (!proposal?.proposal_revision?.length) return null;
158
- return (
159
- proposal.proposal_revision.find((revision) => revision.is_current) ??
160
- proposal.proposal_revision[0]
161
- );
162
- }
163
-
164
- export function openStoredFile(fileId?: number | null) {
165
- if (!fileId) return;
166
- const baseUrl = String(process.env.NEXT_PUBLIC_API_BASE_URL || '');
167
- window.open(
168
- `${baseUrl}/file/open/${fileId}`,
169
- '_blank',
170
- 'noopener,noreferrer'
171
- );
172
- }
1
+ export type ProposalStatus =
2
+ | 'draft'
3
+ | 'pending_approval'
4
+ | 'approved'
5
+ | 'rejected'
6
+ | 'cancelled'
7
+ | 'expired'
8
+ | 'contract_generated';
9
+
10
+ export type ProposalContractCategory =
11
+ | 'employee'
12
+ | 'contractor'
13
+ | 'client'
14
+ | 'supplier'
15
+ | 'vendor'
16
+ | 'partner'
17
+ | 'internal'
18
+ | 'other';
19
+
20
+ export type ProposalContractType =
21
+ | 'clt'
22
+ | 'pj'
23
+ | 'freelancer_agreement'
24
+ | 'service_agreement'
25
+ | 'fixed_term'
26
+ | 'recurring_service'
27
+ | 'nda'
28
+ | 'amendment'
29
+ | 'addendum'
30
+ | 'other';
31
+
32
+ export type ProposalBillingModel =
33
+ | 'time_and_material'
34
+ | 'monthly_retainer'
35
+ | 'fixed_price';
36
+
37
+ export type ProposalItemType =
38
+ | 'service'
39
+ | 'product'
40
+ | 'fee'
41
+ | 'discount'
42
+ | 'note'
43
+ | 'other';
44
+
45
+ export type ProposalRecurrence =
46
+ | 'one_time'
47
+ | 'monthly'
48
+ | 'quarterly'
49
+ | 'yearly'
50
+ | 'other';
51
+
52
+ export type ProposalItem = {
53
+ id?: number;
54
+ name: string;
55
+ description?: string | null;
56
+ quantity?: number | null;
57
+ unit_amount_cents?: number | null;
58
+ total_amount_cents?: number | null;
59
+ item_type?: ProposalItemType | string | null;
60
+ term_type?: string | null;
61
+ recurrence?: ProposalRecurrence | string | null;
62
+ start_date?: string | null;
63
+ end_date?: string | null;
64
+ due_day?: number | null;
65
+ };
66
+
67
+ export type ProposalRevision = {
68
+ id: number;
69
+ revision_number: number;
70
+ is_current?: boolean;
71
+ title: string;
72
+ summary?: string | null;
73
+ valid_until?: string | null;
74
+ status?: ProposalStatus | string;
75
+ proposal_item?: ProposalItem[];
76
+ };
77
+
78
+ export type ProposalApproval = {
79
+ id: number;
80
+ status?: string | null;
81
+ decision_note?: string | null;
82
+ submitted_at?: string | null;
83
+ decided_at?: string | null;
84
+ };
85
+
86
+ export type ProposalDocument = {
87
+ id: number;
88
+ file_id?: number | null;
89
+ file_name?: string | null;
90
+ mime_type?: string | null;
91
+ document_type?: string | null;
92
+ is_current?: boolean | null;
93
+ created_at?: string | null;
94
+ };
95
+
96
+ export type ProposalIntegrationLink = {
97
+ targetModule?: string;
98
+ targetEntityType?: string;
99
+ targetEntityId?: string;
100
+ };
101
+
102
+ export type ProposalRecord = {
103
+ id: number;
104
+ person_id: number;
105
+ code?: string | null;
106
+ title: string;
107
+ status: ProposalStatus;
108
+ contract_category?: ProposalContractCategory | null;
109
+ contract_type?: ProposalContractType | null;
110
+ billing_model?: ProposalBillingModel | null;
111
+ currency_code?: string | null;
112
+ subtotal_amount_cents?: number | null;
113
+ discount_amount_cents?: number | null;
114
+ tax_amount_cents?: number | null;
115
+ total_amount_cents?: number | null;
116
+ valid_until?: string | null;
117
+ current_revision_number?: number | null;
118
+ approval_count?: number | null;
119
+ required_approvals?: number | null;
120
+ current_user_has_approved?: boolean | null;
121
+ notes?: string | null;
122
+ created_at?: string | null;
123
+ updated_at?: string | null;
124
+ approved_at?: string | null;
125
+ person?: {
126
+ id: number;
127
+ name?: string | null;
128
+ trade_name?: string | null;
129
+ email?: string | null;
130
+ phone?: string | null;
131
+ avatar_id?: number | null;
132
+ } | null;
133
+ proposal_revision?: ProposalRevision[];
134
+ proposal_approval?: ProposalApproval[];
135
+ proposal_document?: ProposalDocument[];
136
+ integration_links?: ProposalIntegrationLink[];
137
+ };
138
+
139
+ export type GenerateProposalDocumentResponse = {
140
+ fileId?: number | null;
141
+ fileName?: string | null;
142
+ downloadUrl?: string | null;
143
+ };
144
+
145
+ // ─── Shared utilities ────────────────────────────────────────────────────────
146
+
147
+ export function formatEnumLabel(value?: string | null) {
148
+ if (!value) return '—';
149
+ return value
150
+ .split('_')
151
+ .filter(Boolean)
152
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
153
+ .join(' ');
154
+ }
155
+
156
+ export function getCurrentRevision(proposal?: ProposalRecord | null) {
157
+ if (!proposal?.proposal_revision?.length) return null;
158
+ return (
159
+ proposal.proposal_revision.find((revision) => revision.is_current) ??
160
+ proposal.proposal_revision[0]
161
+ );
162
+ }
163
+
164
+ export function openStoredFile(fileId?: number | null) {
165
+ if (!fileId) return;
166
+ const baseUrl = String(process.env.NEXT_PUBLIC_API_BASE_URL || '');
167
+ window.open(
168
+ `${baseUrl}/file/open/${fileId}`,
169
+ '_blank',
170
+ 'noopener,noreferrer'
171
+ );
172
+ }
@@ -30,6 +30,7 @@ import {
30
30
  TableRow,
31
31
  } from '@/components/ui/table';
32
32
  import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
33
+ import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
33
34
  import { cn } from '@/lib/utils';
34
35
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
35
36
  import {
@@ -197,7 +198,11 @@ export function ProposalsManagementPage({
197
198
  const [statusFilter, setStatusFilter] =
198
199
  useState<ProposalStatusFilter>(defaultStatus);
199
200
  const [page, setPage] = useState(1);
200
- const [pageSize, setPageSize] = useState(12);
201
+ const [pageSize, setPageSize] = usePersistedPageSize({
202
+ storageKey: 'pagination:global:pageSize',
203
+ defaultValue: 12,
204
+ allowedValues: [12, 24, 36, 48],
205
+ });
201
206
  const [viewMode, setViewMode] = useState<ProposalViewMode>('table');
202
207
  const [actionKey, setActionKey] = useState<string | null>(null);
203
208
  const [editSheetOpen, setEditSheetOpen] = useState(false);
@@ -677,101 +682,86 @@ export function ProposalsManagementPage({
677
682
  </TableCell>
678
683
 
679
684
  <TableCell className="text-right">
680
- <div className="flex items-center justify-end gap-2">
681
- {canEditProposal(proposal.status) ? (
685
+ <DropdownMenu>
686
+ <DropdownMenuTrigger asChild>
682
687
  <Button
683
- variant="outline"
684
- size="sm"
685
- className="gap-1.5"
686
- onClick={() => handleEdit(proposal)}
688
+ variant="ghost"
689
+ size="icon"
690
+ className="h-8 w-8"
687
691
  >
688
- <Pencil className="h-3.5 w-3.5" />
689
- {proposalT('actions.edit')}
692
+ {actionKey?.startsWith(`${proposal.id}-`) ||
693
+ actionKey === `generate-pdf-${proposal.id}` ||
694
+ actionKey === `submit-${proposal.id}` ||
695
+ actionKey === `approve-${proposal.id}` ||
696
+ actionKey === `reject-${proposal.id}` ? (
697
+ <Loader2 className="h-4 w-4 animate-spin" />
698
+ ) : (
699
+ <MoreHorizontal className="h-4 w-4" />
700
+ )}
690
701
  </Button>
691
- ) : null}
692
- <DropdownMenu>
693
- <DropdownMenuTrigger asChild>
694
- <Button
695
- variant="ghost"
696
- size="icon"
697
- className="h-8 w-8"
702
+ </DropdownMenuTrigger>
703
+ <DropdownMenuContent align="end">
704
+ {canEditProposal(proposal.status) ? (
705
+ <DropdownMenuItem
706
+ onClick={() => handleEdit(proposal)}
698
707
  >
699
- {actionKey?.startsWith(`${proposal.id}-`) ||
700
- actionKey === `generate-pdf-${proposal.id}` ||
701
- actionKey === `submit-${proposal.id}` ||
702
- actionKey === `approve-${proposal.id}` ||
703
- actionKey === `reject-${proposal.id}` ? (
704
- <Loader2 className="h-4 w-4 animate-spin" />
705
- ) : (
706
- <MoreHorizontal className="h-4 w-4" />
707
- )}
708
- </Button>
709
- </DropdownMenuTrigger>
710
- <DropdownMenuContent align="end">
711
- {canEditProposal(proposal.status) ? (
708
+ <Pencil className="mr-2 h-4 w-4" />
709
+ {proposalT('actions.edit')}
710
+ </DropdownMenuItem>
711
+ ) : null}
712
+
713
+ {canSubmitProposal(proposal.status) ? (
714
+ <DropdownMenuItem
715
+ disabled={actionKey === `submit-${proposal.id}`}
716
+ onClick={() =>
717
+ void handleStatusAction(proposal, 'submit')
718
+ }
719
+ >
720
+ <Send className="mr-2 h-4 w-4" />
721
+ {proposalT('actions.submit')}
722
+ </DropdownMenuItem>
723
+ ) : null}
724
+
725
+ {proposal.status === 'pending_approval' &&
726
+ !proposal.current_user_has_approved ? (
727
+ <>
712
728
  <DropdownMenuItem
713
- onClick={() => handleEdit(proposal)}
729
+ disabled={
730
+ actionKey === `approve-${proposal.id}`
731
+ }
732
+ onClick={() =>
733
+ void handleStatusAction(proposal, 'approve')
734
+ }
714
735
  >
715
- <Pencil className="mr-2 h-4 w-4" />
716
- {proposalT('actions.edit')}
736
+ <CheckCircle2 className="mr-2 h-4 w-4" />
737
+ {proposalT('actions.approve')}
717
738
  </DropdownMenuItem>
718
- ) : null}
719
-
720
- {canSubmitProposal(proposal.status) ? (
721
739
  <DropdownMenuItem
722
- disabled={actionKey === `submit-${proposal.id}`}
740
+ disabled={actionKey === `reject-${proposal.id}`}
741
+ className="text-destructive focus:text-destructive"
723
742
  onClick={() =>
724
- void handleStatusAction(proposal, 'submit')
743
+ void handleStatusAction(proposal, 'reject')
725
744
  }
726
745
  >
727
- <Send className="mr-2 h-4 w-4" />
728
- {proposalT('actions.submit')}
746
+ <XCircle className="mr-2 h-4 w-4" />
747
+ {proposalT('actions.reject')}
729
748
  </DropdownMenuItem>
730
- ) : null}
731
-
732
- {proposal.status === 'pending_approval' &&
733
- !proposal.current_user_has_approved ? (
734
- <>
735
- <DropdownMenuItem
736
- disabled={
737
- actionKey === `approve-${proposal.id}`
738
- }
739
- onClick={() =>
740
- void handleStatusAction(proposal, 'approve')
741
- }
742
- >
743
- <CheckCircle2 className="mr-2 h-4 w-4" />
744
- {proposalT('actions.approve')}
745
- </DropdownMenuItem>
746
- <DropdownMenuItem
747
- disabled={
748
- actionKey === `reject-${proposal.id}`
749
- }
750
- className="text-destructive focus:text-destructive"
751
- onClick={() =>
752
- void handleStatusAction(proposal, 'reject')
753
- }
754
- >
755
- <XCircle className="mr-2 h-4 w-4" />
756
- {proposalT('actions.reject')}
757
- </DropdownMenuItem>
758
- </>
759
- ) : null}
760
-
761
- <DropdownMenuSeparator />
749
+ </>
750
+ ) : null}
762
751
 
763
- <DropdownMenuItem
764
- disabled={
765
- actionKey === `generate-pdf-${proposal.id}`
766
- }
767
- onClick={() => void handleGeneratePdf(proposal)}
768
- >
769
- <FileText className="mr-2 h-4 w-4" />
770
- {proposalT('actions.generatePdf')}
771
- </DropdownMenuItem>
772
- </DropdownMenuContent>
773
- </DropdownMenu>
774
- </div>
752
+ <DropdownMenuSeparator />
753
+
754
+ <DropdownMenuItem
755
+ disabled={
756
+ actionKey === `generate-pdf-${proposal.id}`
757
+ }
758
+ onClick={() => void handleGeneratePdf(proposal)}
759
+ >
760
+ <FileText className="mr-2 h-4 w-4" />
761
+ {proposalT('actions.generatePdf')}
762
+ </DropdownMenuItem>
763
+ </DropdownMenuContent>
764
+ </DropdownMenu>
775
765
  </TableCell>
776
766
  </TableRow>
777
767
  );
@@ -867,18 +857,7 @@ export function ProposalsManagementPage({
867
857
  </div>
868
858
  </div>
869
859
 
870
- <div className="mt-auto flex justify-end gap-2">
871
- {canEditProposal(proposal.status) ? (
872
- <Button
873
- variant="outline"
874
- size="sm"
875
- className="gap-1.5"
876
- onClick={() => handleEdit(proposal)}
877
- >
878
- <Pencil className="h-3.5 w-3.5" />
879
- {proposalT('actions.edit')}
880
- </Button>
881
- ) : null}
860
+ <div className="mt-auto flex justify-end">
882
861
  <DropdownMenu>
883
862
  <DropdownMenuTrigger asChild>
884
863
  <Button variant="outline" size="sm" className="gap-1.5">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hed-hog/contact",
3
- "version": "0.0.333",
3
+ "version": "0.0.347",
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",
13
15
  "@hed-hog/api": "0.0.8",
14
- "@hed-hog/api-locale": "0.0.14",
15
16
  "@hed-hog/api-prisma": "0.0.6",
16
- "@hed-hog/api-mail": "0.0.9",
17
17
  "@hed-hog/api-pagination": "0.0.7",
18
- "@hed-hog/core": "0.0.333",
19
- "@hed-hog/address": "0.0.333"
18
+ "@hed-hog/api-mail": "0.0.9",
19
+ "@hed-hog/api-locale": "0.0.14"
20
20
  },
21
21
  "exports": {
22
22
  ".": {
@@ -2,28 +2,28 @@ import { DeleteDTO, Public, Role, User } from '@hed-hog/api';
2
2
  import { Locale } from '@hed-hog/api-locale';
3
3
  import { Pagination } from '@hed-hog/api-pagination';
4
4
  import {
5
- BadRequestException,
6
- Body,
7
- Controller,
8
- Delete,
9
- Get,
10
- Inject,
11
- Param,
12
- ParseIntPipe,
13
- Patch,
14
- Post,
15
- Query,
16
- Res,
17
- UploadedFile,
18
- UseInterceptors,
19
- forwardRef
5
+ BadRequestException,
6
+ Body,
7
+ Controller,
8
+ Delete,
9
+ Get,
10
+ Inject,
11
+ Param,
12
+ ParseIntPipe,
13
+ Patch,
14
+ Post,
15
+ Query,
16
+ Res,
17
+ UploadedFile,
18
+ UseInterceptors,
19
+ forwardRef
20
20
  } from '@nestjs/common';
21
21
  import { FileInterceptor } from '@nestjs/platform-express';
22
22
  import { Response } from 'express';
23
23
  import {
24
- AccountListQueryDTO,
25
- CreateAccountDTO,
26
- UpdateAccountDTO,
24
+ AccountListQueryDTO,
25
+ CreateAccountDTO,
26
+ UpdateAccountDTO,
27
27
  } from './dto/account.dto';
28
28
  import { ActivityListQueryDTO } from './dto/activity.dto';
29
29
  import { CreateFollowupDTO } from './dto/create-followup.dto';
@@ -32,8 +32,8 @@ import { CreateDTO } from './dto/create.dto';
32
32
  import { DashboardQueryDTO } from './dto/dashboard-query.dto';
33
33
  import { CheckPersonDuplicatesQueryDTO } from './dto/duplicates-query.dto';
34
34
  import {
35
- FollowupListQueryDTO,
36
- FollowupStatsQueryDTO,
35
+ FollowupListQueryDTO,
36
+ FollowupStatsQueryDTO,
37
37
  } from './dto/followup-query.dto';
38
38
  import { MergePersonDTO } from './dto/merge.dto';
39
39
  import { ReportsQueryDTO } from './dto/reports-query.dto';
@@ -264,8 +264,15 @@ export class PersonController {
264
264
  }
265
265
 
266
266
  @Post()
267
- async create(@Body() data: CreateDTO, @Locale() locale: string) {
268
- return this.personService.create(data, locale);
267
+ async create(
268
+ @Body() data: CreateDTO,
269
+ @Locale() locale: string,
270
+ @User() user,
271
+ ) {
272
+ return this.personService.create(data, locale, {
273
+ id: Number(user?.id || 0),
274
+ name: user?.name,
275
+ });
269
276
  }
270
277
 
271
278
  @Post('merge')