@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.
- package/dist/person/person.controller.d.ts +4 -5
- package/dist/person/person.controller.d.ts.map +1 -1
- package/dist/person/person.controller.js +7 -3
- package/dist/person/person.controller.js.map +1 -1
- package/dist/person/person.service.d.ts +6 -3
- package/dist/person/person.service.d.ts.map +1 -1
- package/dist/person/person.service.js +104 -23
- package/dist/person/person.service.js.map +1 -1
- package/hedhog/data/role.yaml +9 -1
- package/hedhog/endpoint/person.yaml +263 -0
- package/hedhog/frontend/app/_components/person-picker.tsx.ejs +7 -5
- package/hedhog/frontend/app/accounts/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/activities/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/contact-type/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/document-type/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/follow-ups/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/person/_components/person-form-sheet.tsx.ejs +24 -23
- package/hedhog/frontend/app/person/_components/person-import-sheet.tsx.ejs +102 -102
- package/hedhog/frontend/app/person/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/proposals/_components/proposal-form-sheet.tsx.ejs +1306 -1306
- package/hedhog/frontend/app/proposals/_components/proposal-types.ts.ejs +172 -172
- package/hedhog/frontend/app/proposals/_components/proposals-management-page.tsx.ejs +74 -95
- package/package.json +5 -5
- package/src/person/person.controller.ts +29 -22
- package/src/person/person.service.ts +128 -26
package/hedhog/data/role.yaml
CHANGED
|
@@ -20,4 +20,12 @@
|
|
|
20
20
|
pt: Aprovador de Propostas
|
|
21
21
|
description:
|
|
22
22
|
en: User authorized to approve proposals.
|
|
23
|
-
pt: Usuario autorizado a aprovar propostas.
|
|
23
|
+
pt: Usuario autorizado a aprovar propostas.
|
|
24
|
+
|
|
25
|
+
- slug: person-user-linker
|
|
26
|
+
name:
|
|
27
|
+
en: Person User Linker
|
|
28
|
+
pt: Vinculador de Pessoa a Usuario
|
|
29
|
+
description:
|
|
30
|
+
en: User allowed to relate a person to a system user.
|
|
31
|
+
pt: Usuario permitido para relacionar uma pessoa com um usuario do sistema.
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
resource: person
|
|
2
|
+
base_url: /person
|
|
3
|
+
roles: [admin, admin-contact]
|
|
4
|
+
|
|
5
|
+
endpoints:
|
|
6
|
+
- method: GET
|
|
7
|
+
path: /
|
|
8
|
+
description: List persons with pagination and filters
|
|
9
|
+
pagination: true
|
|
10
|
+
response:
|
|
11
|
+
schema: person
|
|
12
|
+
paginated: true
|
|
13
|
+
|
|
14
|
+
- method: GET
|
|
15
|
+
path: /stats
|
|
16
|
+
description: Get aggregated person statistics
|
|
17
|
+
|
|
18
|
+
- method: GET
|
|
19
|
+
path: /dashboard
|
|
20
|
+
description: Get dashboard data (charts, KPIs)
|
|
21
|
+
locale: true
|
|
22
|
+
query:
|
|
23
|
+
period: string?
|
|
24
|
+
|
|
25
|
+
- method: GET
|
|
26
|
+
path: /reports
|
|
27
|
+
description: Get report data
|
|
28
|
+
locale: true
|
|
29
|
+
query:
|
|
30
|
+
from: date?
|
|
31
|
+
to: date?
|
|
32
|
+
|
|
33
|
+
- method: GET
|
|
34
|
+
path: /owner-options
|
|
35
|
+
description: List users available as owner options (search-able)
|
|
36
|
+
query:
|
|
37
|
+
search: string?
|
|
38
|
+
|
|
39
|
+
- method: GET
|
|
40
|
+
path: /linked-user-options
|
|
41
|
+
description: List users available to be linked to a person
|
|
42
|
+
query:
|
|
43
|
+
search: string?
|
|
44
|
+
|
|
45
|
+
- method: GET
|
|
46
|
+
path: /duplicates
|
|
47
|
+
description: Check for duplicate persons by name or document
|
|
48
|
+
query:
|
|
49
|
+
name: string?
|
|
50
|
+
document: string?
|
|
51
|
+
|
|
52
|
+
- method: GET
|
|
53
|
+
path: /accounts
|
|
54
|
+
description: List company/person accounts with pagination
|
|
55
|
+
pagination: true
|
|
56
|
+
query:
|
|
57
|
+
type: enum[company,individual]?
|
|
58
|
+
|
|
59
|
+
- method: GET
|
|
60
|
+
path: /accounts/stats
|
|
61
|
+
description: Get account statistics
|
|
62
|
+
|
|
63
|
+
- method: GET
|
|
64
|
+
path: /followups
|
|
65
|
+
description: List followups with pagination and filters
|
|
66
|
+
pagination: true
|
|
67
|
+
query:
|
|
68
|
+
status: string?
|
|
69
|
+
assigned_to: int?
|
|
70
|
+
|
|
71
|
+
- method: GET
|
|
72
|
+
path: /followups/stats
|
|
73
|
+
description: Get followup statistics
|
|
74
|
+
query:
|
|
75
|
+
period: string?
|
|
76
|
+
|
|
77
|
+
- method: GET
|
|
78
|
+
path: /activities
|
|
79
|
+
description: List activities with pagination and filters
|
|
80
|
+
pagination: true
|
|
81
|
+
query:
|
|
82
|
+
type: string?
|
|
83
|
+
person_id: int?
|
|
84
|
+
|
|
85
|
+
- method: GET
|
|
86
|
+
path: /activities/stats
|
|
87
|
+
description: Get activity statistics
|
|
88
|
+
|
|
89
|
+
- method: GET
|
|
90
|
+
path: /activities/:id
|
|
91
|
+
description: Get a single activity detail
|
|
92
|
+
locale: true
|
|
93
|
+
params:
|
|
94
|
+
id: int
|
|
95
|
+
|
|
96
|
+
- method: GET
|
|
97
|
+
path: /:id
|
|
98
|
+
description: Get a single person with full details
|
|
99
|
+
locale: true
|
|
100
|
+
params:
|
|
101
|
+
id: int
|
|
102
|
+
|
|
103
|
+
- method: GET
|
|
104
|
+
path: /avatar/:id
|
|
105
|
+
description: Stream person avatar image (public)
|
|
106
|
+
auth: public
|
|
107
|
+
params:
|
|
108
|
+
id: int
|
|
109
|
+
locale: true
|
|
110
|
+
|
|
111
|
+
- method: POST
|
|
112
|
+
path: /
|
|
113
|
+
description: Create a new person (individual or company)
|
|
114
|
+
locale: true
|
|
115
|
+
body:
|
|
116
|
+
name: string
|
|
117
|
+
type: enum[individual,company]
|
|
118
|
+
status: enum[active,inactive]
|
|
119
|
+
avatar_id: int?
|
|
120
|
+
birth_date: date?
|
|
121
|
+
gender: enum[male,female,other]?
|
|
122
|
+
job_title: string?
|
|
123
|
+
trade_name: string?
|
|
124
|
+
foundation_date: date?
|
|
125
|
+
legal_nature: string?
|
|
126
|
+
notes: string?
|
|
127
|
+
employer_company_id: int?
|
|
128
|
+
owner_user_id: int?
|
|
129
|
+
user_id: int?
|
|
130
|
+
source: enum[referral,website,social,inbound,outbound,other]?
|
|
131
|
+
lifecycle_stage: enum[new,contacted,qualified,proposal,negotiation,customer,lost]?
|
|
132
|
+
next_action_at: datetime?
|
|
133
|
+
score: number?
|
|
134
|
+
deal_value: number?
|
|
135
|
+
tags: array<string>?
|
|
136
|
+
|
|
137
|
+
- method: POST
|
|
138
|
+
path: /accounts
|
|
139
|
+
description: Create a new account (company linked to persons)
|
|
140
|
+
locale: true
|
|
141
|
+
body:
|
|
142
|
+
name: string
|
|
143
|
+
document: string?
|
|
144
|
+
email: string?
|
|
145
|
+
phone: string?
|
|
146
|
+
|
|
147
|
+
- method: POST
|
|
148
|
+
path: /merge
|
|
149
|
+
description: Merge two person records into one
|
|
150
|
+
locale: true
|
|
151
|
+
body:
|
|
152
|
+
source_id: int
|
|
153
|
+
target_id: int
|
|
154
|
+
|
|
155
|
+
- method: POST
|
|
156
|
+
path: /import/preview
|
|
157
|
+
description: Preview CSV import before applying
|
|
158
|
+
multipart: true
|
|
159
|
+
|
|
160
|
+
- method: POST
|
|
161
|
+
path: /import
|
|
162
|
+
description: Import persons from CSV file
|
|
163
|
+
locale: true
|
|
164
|
+
multipart: true
|
|
165
|
+
|
|
166
|
+
- method: POST
|
|
167
|
+
path: /:id/interaction
|
|
168
|
+
description: Add an interaction record to a person
|
|
169
|
+
locale: true
|
|
170
|
+
params:
|
|
171
|
+
id: int
|
|
172
|
+
body:
|
|
173
|
+
type: string
|
|
174
|
+
content: string
|
|
175
|
+
occurred_at: datetime?
|
|
176
|
+
|
|
177
|
+
- method: POST
|
|
178
|
+
path: /:id/followup
|
|
179
|
+
description: Schedule a followup for a person
|
|
180
|
+
locale: true
|
|
181
|
+
params:
|
|
182
|
+
id: int
|
|
183
|
+
body:
|
|
184
|
+
title: string
|
|
185
|
+
due_at: datetime
|
|
186
|
+
notes: string?
|
|
187
|
+
assigned_to_user_id: int?
|
|
188
|
+
|
|
189
|
+
- method: POST
|
|
190
|
+
path: /:id/lifecycle-stage
|
|
191
|
+
description: Update lifecycle stage of a person
|
|
192
|
+
locale: true
|
|
193
|
+
params:
|
|
194
|
+
id: int
|
|
195
|
+
body:
|
|
196
|
+
stage: enum[new,contacted,qualified,proposal,negotiation,customer,lost]
|
|
197
|
+
|
|
198
|
+
- method: POST
|
|
199
|
+
path: /activities/:id/complete
|
|
200
|
+
description: Mark an activity as completed
|
|
201
|
+
locale: true
|
|
202
|
+
params:
|
|
203
|
+
id: int
|
|
204
|
+
|
|
205
|
+
- method: PATCH
|
|
206
|
+
path: /:id
|
|
207
|
+
description: Update a person record
|
|
208
|
+
locale: true
|
|
209
|
+
params:
|
|
210
|
+
id: int
|
|
211
|
+
body:
|
|
212
|
+
name: string?
|
|
213
|
+
type: enum[individual,company]?
|
|
214
|
+
status: enum[active,inactive]?
|
|
215
|
+
avatar_id: int?
|
|
216
|
+
birth_date: date?
|
|
217
|
+
gender: enum[male,female,other]?
|
|
218
|
+
job_title: string?
|
|
219
|
+
trade_name: string?
|
|
220
|
+
foundation_date: date?
|
|
221
|
+
legal_nature: string?
|
|
222
|
+
notes: string?
|
|
223
|
+
employer_company_id: int?
|
|
224
|
+
owner_user_id: int?
|
|
225
|
+
user_id: int?
|
|
226
|
+
source: enum[referral,website,social,inbound,outbound,other]?
|
|
227
|
+
lifecycle_stage: enum[new,contacted,qualified,proposal,negotiation,customer,lost]?
|
|
228
|
+
next_action_at: datetime?
|
|
229
|
+
score: number?
|
|
230
|
+
deal_value: number?
|
|
231
|
+
tags: array<string>?
|
|
232
|
+
|
|
233
|
+
- method: PATCH
|
|
234
|
+
path: /accounts/:id
|
|
235
|
+
description: Update an account
|
|
236
|
+
locale: true
|
|
237
|
+
params:
|
|
238
|
+
id: int
|
|
239
|
+
body:
|
|
240
|
+
name: string?
|
|
241
|
+
document: string?
|
|
242
|
+
email: string?
|
|
243
|
+
phone: string?
|
|
244
|
+
|
|
245
|
+
- method: DELETE
|
|
246
|
+
path: /
|
|
247
|
+
description: Delete multiple persons (bulk)
|
|
248
|
+
body:
|
|
249
|
+
ids: array<int>
|
|
250
|
+
|
|
251
|
+
- method: DELETE
|
|
252
|
+
path: /accounts
|
|
253
|
+
description: Delete multiple accounts (bulk)
|
|
254
|
+
locale: true
|
|
255
|
+
body:
|
|
256
|
+
ids: array<int>
|
|
257
|
+
|
|
258
|
+
- method: GET
|
|
259
|
+
path: /:id/interaction
|
|
260
|
+
description: List interactions for a person
|
|
261
|
+
locale: true
|
|
262
|
+
params:
|
|
263
|
+
id: int
|
|
@@ -574,6 +574,7 @@ export function PersonPicker({
|
|
|
574
574
|
createType = 'individual',
|
|
575
575
|
lockCreateType = false,
|
|
576
576
|
initialSelectedLabel = '',
|
|
577
|
+
initialSelectedAvatarId,
|
|
577
578
|
disabled = false,
|
|
578
579
|
clearable = true,
|
|
579
580
|
showEditButton = false,
|
|
@@ -590,6 +591,7 @@ export function PersonPicker({
|
|
|
590
591
|
createType?: Exclude<PersonTypeFilter, 'all'>;
|
|
591
592
|
lockCreateType?: boolean;
|
|
592
593
|
initialSelectedLabel?: string;
|
|
594
|
+
initialSelectedAvatarId?: number | null;
|
|
593
595
|
disabled?: boolean;
|
|
594
596
|
clearable?: boolean;
|
|
595
597
|
showEditButton?: boolean;
|
|
@@ -605,7 +607,7 @@ export function PersonPicker({
|
|
|
605
607
|
useState(initialSelectedLabel);
|
|
606
608
|
const [selectedPersonAvatarId, setSelectedPersonAvatarId] = useState<
|
|
607
609
|
number | null
|
|
608
|
-
>(null);
|
|
610
|
+
>(initialSelectedAvatarId ?? null);
|
|
609
611
|
const commandInputRef = useRef<HTMLInputElement | null>(null);
|
|
610
612
|
const parentScrollContainerRef = useRef<HTMLElement | null>(null);
|
|
611
613
|
const parentScrollTopRef = useRef(0);
|
|
@@ -921,7 +923,7 @@ export function PersonPicker({
|
|
|
921
923
|
</PopoverContent>
|
|
922
924
|
</Popover>
|
|
923
925
|
|
|
924
|
-
{
|
|
926
|
+
{canEditSelection && clearable ? (
|
|
925
927
|
<Button
|
|
926
928
|
type="button"
|
|
927
929
|
variant="outline"
|
|
@@ -941,15 +943,15 @@ export function PersonPicker({
|
|
|
941
943
|
</Button>
|
|
942
944
|
) : null}
|
|
943
945
|
|
|
944
|
-
{showEditButton ? (
|
|
946
|
+
{showEditButton && canEditSelection ? (
|
|
945
947
|
<Button
|
|
946
948
|
type="button"
|
|
947
949
|
variant="outline"
|
|
948
950
|
size="icon"
|
|
949
951
|
className="shrink-0"
|
|
950
|
-
disabled={disabled
|
|
952
|
+
disabled={disabled}
|
|
951
953
|
onClick={() => {
|
|
952
|
-
if (!
|
|
954
|
+
if (!onEditSelection) {
|
|
953
955
|
return;
|
|
954
956
|
}
|
|
955
957
|
onEditSelection(selectedPersonId);
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
} from '@/components/ui/table';
|
|
41
41
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
42
42
|
import { getFormDraftOwnerKey } from '@/hooks/use-form-draft';
|
|
43
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
43
44
|
import { formatDate } from '@/lib/format-date';
|
|
44
45
|
import { cn } from '@/lib/utils';
|
|
45
46
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
@@ -163,7 +164,11 @@ export default function AccountsPage() {
|
|
|
163
164
|
|
|
164
165
|
const [sorting, setSorting] = useState<SortingState>([]);
|
|
165
166
|
const [page, setPage] = useState(1);
|
|
166
|
-
const [pageSize, setPageSize] =
|
|
167
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
168
|
+
storageKey: 'pagination:global:pageSize',
|
|
169
|
+
defaultValue: 12,
|
|
170
|
+
allowedValues: [6, 12, 24, 48],
|
|
171
|
+
});
|
|
167
172
|
const [searchInput, setSearchInput] = useState('');
|
|
168
173
|
const [debouncedSearch, setDebouncedSearch] = useState('');
|
|
169
174
|
const [statusFilter, setStatusFilter] = useState('all');
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
TableRow,
|
|
23
23
|
} from '@/components/ui/table';
|
|
24
24
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
25
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
25
26
|
import { formatDateTime } from '@/lib/format-date';
|
|
26
27
|
import { cn } from '@/lib/utils';
|
|
27
28
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
@@ -95,7 +96,11 @@ export default function CrmActivitiesPage() {
|
|
|
95
96
|
const [typeFilter, setTypeFilter] = useState('all');
|
|
96
97
|
const [priorityFilter, setPriorityFilter] = useState('all');
|
|
97
98
|
const [page, setPage] = useState(1);
|
|
98
|
-
const [pageSize, setPageSize] =
|
|
99
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
100
|
+
storageKey: 'pagination:global:pageSize',
|
|
101
|
+
defaultValue: 12,
|
|
102
|
+
allowedValues: [6, 12, 24, 48],
|
|
103
|
+
});
|
|
99
104
|
const [viewMode, setViewMode] = useState<ActivityViewMode>('table');
|
|
100
105
|
const [selectedActivityId, setSelectedActivityId] = useState<number | null>(
|
|
101
106
|
null
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
TableRow,
|
|
53
53
|
} from '@/components/ui/table';
|
|
54
54
|
import { useFormDraft } from '@/hooks/use-form-draft';
|
|
55
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
55
56
|
import { formatDate, formatDateTime } from '@/lib/format-date';
|
|
56
57
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
57
58
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
@@ -126,7 +127,11 @@ export default function ContactTypePage() {
|
|
|
126
127
|
useState<ContactType | null>(null);
|
|
127
128
|
|
|
128
129
|
const [page, setPage] = useState(1);
|
|
129
|
-
const [pageSize, setPageSize] =
|
|
130
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
131
|
+
storageKey: 'pagination:global:pageSize',
|
|
132
|
+
defaultValue: 10,
|
|
133
|
+
allowedValues: [10, 20, 30, 40, 50],
|
|
134
|
+
});
|
|
130
135
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
131
136
|
const [deletingId, setDeletingId] = useState<number | null>(null);
|
|
132
137
|
|
|
@@ -61,6 +61,7 @@ import {
|
|
|
61
61
|
} from '@/components/ui/table';
|
|
62
62
|
import { COUNTRIES } from '@/constants/countries';
|
|
63
63
|
import { useFormDraft } from '@/hooks/use-form-draft';
|
|
64
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
64
65
|
import { formatDate, formatDateTime } from '@/lib/format-date';
|
|
65
66
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
66
67
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
@@ -143,7 +144,11 @@ export default function DocumentTypePage() {
|
|
|
143
144
|
useState<DocumentType | null>(null);
|
|
144
145
|
|
|
145
146
|
const [page, setPage] = useState(1);
|
|
146
|
-
const [pageSize, setPageSize] =
|
|
147
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
148
|
+
storageKey: 'pagination:global:pageSize',
|
|
149
|
+
defaultValue: 10,
|
|
150
|
+
allowedValues: [10, 20, 30, 40, 50],
|
|
151
|
+
});
|
|
147
152
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
148
153
|
const [deletingId, setDeletingId] = useState<number | null>(null);
|
|
149
154
|
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
import { Textarea } from '@/components/ui/textarea';
|
|
55
55
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
56
56
|
import { useFormDraft } from '@/hooks/use-form-draft';
|
|
57
|
+
import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
|
|
57
58
|
import { formatDateTime } from '@/lib/format-date';
|
|
58
59
|
import { cn } from '@/lib/utils';
|
|
59
60
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
@@ -206,7 +207,11 @@ export default function CrmFollowupsPage() {
|
|
|
206
207
|
const [dateFrom, setDateFrom] = useState('');
|
|
207
208
|
const [dateTo, setDateTo] = useState('');
|
|
208
209
|
const [page, setPage] = useState(1);
|
|
209
|
-
const [pageSize, setPageSize] =
|
|
210
|
+
const [pageSize, setPageSize] = usePersistedPageSize({
|
|
211
|
+
storageKey: 'pagination:global:pageSize',
|
|
212
|
+
defaultValue: 12,
|
|
213
|
+
allowedValues: [6, 12, 24, 48],
|
|
214
|
+
});
|
|
210
215
|
const [viewMode, setViewMode] = useState<FollowupViewMode>('table');
|
|
211
216
|
const [sheetOpen, setSheetOpen] = useState(false);
|
|
212
217
|
const [personPickerOpen, setPersonPickerOpen] = useState(false);
|
|
@@ -710,6 +710,19 @@ export function PersonFormSheet({
|
|
|
710
710
|
const t = useTranslations('contact.ContactPage');
|
|
711
711
|
const { request, currentLocaleCode, getSettingValue, user } = useApp();
|
|
712
712
|
const isEditing = Boolean(person);
|
|
713
|
+
const canEditLinkedUser = useMemo(
|
|
714
|
+
() =>
|
|
715
|
+
(user?.role_user ?? []).some((roleUser) => {
|
|
716
|
+
const roleSlug = roleUser.role?.slug;
|
|
717
|
+
|
|
718
|
+
return (
|
|
719
|
+
roleSlug === 'admin' ||
|
|
720
|
+
roleSlug === 'admin-contact' ||
|
|
721
|
+
roleSlug === 'person-user-linker'
|
|
722
|
+
);
|
|
723
|
+
}),
|
|
724
|
+
[user]
|
|
725
|
+
);
|
|
713
726
|
const allowCompanyRegistration =
|
|
714
727
|
getSettingValue('contact-allow-company-registration') !== false;
|
|
715
728
|
const effectiveAllowedTypes = useMemo<Array<Person['type']>>(() => {
|
|
@@ -1146,11 +1159,6 @@ export function PersonFormSheet({
|
|
|
1146
1159
|
? `${process.env.NEXT_PUBLIC_API_BASE_URL}/person/avatar/${fileId}`
|
|
1147
1160
|
: '/placeholder.png';
|
|
1148
1161
|
|
|
1149
|
-
const getUserPhotoUrl = (photoId?: number | null) =>
|
|
1150
|
-
typeof photoId === 'number' && photoId > 0
|
|
1151
|
-
? `${process.env.NEXT_PUBLIC_API_BASE_URL}/user/avatar/${photoId}`
|
|
1152
|
-
: undefined;
|
|
1153
|
-
|
|
1154
1162
|
const resolveApiUrl = (url: string) =>
|
|
1155
1163
|
/^https?:\/\//i.test(url)
|
|
1156
1164
|
? url
|
|
@@ -2500,10 +2508,17 @@ export function PersonFormSheet({
|
|
|
2500
2508
|
</div>
|
|
2501
2509
|
</CollapsibleTrigger>
|
|
2502
2510
|
|
|
2503
|
-
<CollapsibleContent
|
|
2511
|
+
<CollapsibleContent
|
|
2512
|
+
className={cn(
|
|
2513
|
+
'mt-2',
|
|
2514
|
+
!canEditLinkedUser && 'opacity-60 pointer-events-none'
|
|
2515
|
+
)}
|
|
2516
|
+
>
|
|
2504
2517
|
<EntityPicker
|
|
2518
|
+
disabled={!canEditLinkedUser}
|
|
2505
2519
|
value={linkedUserId}
|
|
2506
2520
|
onChange={(val, opt) => {
|
|
2521
|
+
if (!canEditLinkedUser) return;
|
|
2507
2522
|
setLinkedUserId(val as number | null);
|
|
2508
2523
|
setLinkedUserLabel(
|
|
2509
2524
|
opt
|
|
@@ -2513,31 +2528,17 @@ export function PersonFormSheet({
|
|
|
2513
2528
|
'')
|
|
2514
2529
|
: ''
|
|
2515
2530
|
);
|
|
2516
|
-
const userPhotoId = (
|
|
2517
|
-
opt as { photo_id?: number | null } | null
|
|
2518
|
-
)?.photo_id;
|
|
2519
|
-
if (val != null && userPhotoId && !avatarId) {
|
|
2520
|
-
setAvatarId(userPhotoId);
|
|
2521
|
-
setAvatarPreviewUrl(
|
|
2522
|
-
getUserPhotoUrl(userPhotoId) ?? '/placeholder.png'
|
|
2523
|
-
);
|
|
2524
|
-
}
|
|
2525
2531
|
}}
|
|
2526
2532
|
placeholder={t('selectLinkedUser')}
|
|
2527
2533
|
entityLabel={t('user')}
|
|
2528
|
-
clearable
|
|
2534
|
+
clearable={canEditLinkedUser}
|
|
2529
2535
|
valueType="number"
|
|
2530
2536
|
initialSelectedLabel={linkedUserLabel}
|
|
2531
2537
|
loadOptions={async ({ search }) => {
|
|
2532
2538
|
const params = new URLSearchParams();
|
|
2533
2539
|
if (search) params.set('search', search);
|
|
2534
2540
|
const response = await request<
|
|
2535
|
-
Array<{
|
|
2536
|
-
id: number;
|
|
2537
|
-
name: string;
|
|
2538
|
-
email?: string;
|
|
2539
|
-
photo_id?: number | null;
|
|
2540
|
-
}>
|
|
2541
|
+
Array<{ id: number; name: string; email?: string }>
|
|
2541
2542
|
>({
|
|
2542
2543
|
url: `/person/linked-user-options?${params.toString()}`,
|
|
2543
2544
|
method: 'GET',
|
|
@@ -2554,7 +2555,7 @@ export function PersonFormSheet({
|
|
|
2554
2555
|
getOptionDescription={(opt) =>
|
|
2555
2556
|
(opt as { email?: string }).email
|
|
2556
2557
|
}
|
|
2557
|
-
showCreateButton
|
|
2558
|
+
showCreateButton={canEditLinkedUser}
|
|
2558
2559
|
createTitle={t('createUserTitle')}
|
|
2559
2560
|
mapSearchToCreateValues={(search) => ({
|
|
2560
2561
|
name: search,
|