@accounter/client 0.0.7-alpha-20251021062721-ab43b5f013b9852ff5216bc141852da9dcfe0df7 → 0.0.8-alpha-20251021150028-9eb73ba6576d67c75a60786d0e4ab0876fd7a95c
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/CHANGELOG.md +102 -105
- package/dist/assets/index-0eCf1BcD.css +1 -0
- package/dist/assets/index-DHTbHvtz.js +1188 -0
- package/dist/assets/{index.es-NOVMdy_X.js → index.es-DHwHzag1.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -2
- package/src/app.tsx +4 -8
- package/src/components/business-transactions/business-extended-info.tsx +13 -9
- package/src/components/charges/charge-extended-info-menu.tsx +21 -27
- package/src/components/charges/charges-row.tsx +10 -12
- package/src/components/charges/charges-table.tsx +9 -15
- package/src/components/common/documents/issue-document/index.tsx +3 -3
- package/src/components/common/documents/issue-document/{recent-business-docs.tsx → recent-client-docs.tsx} +13 -19
- package/src/components/common/forms/business-card.tsx +0 -1
- package/src/components/common/forms/modify-business-fields.tsx +19 -2
- package/src/components/common/inputs/combo-box.tsx +1 -1
- package/src/components/reports/trial-balance-report/trial-balance-report-group.tsx +6 -4
- package/src/components/reports/trial-balance-report/trial-balance-report-sort-code.tsx +11 -8
- package/src/components/screens/documents/issue-documents/edit-issue-document-modal.tsx +4 -4
- package/src/gql/gql.ts +9 -93
- package/src/gql/graphql.ts +20 -285
- package/src/helpers/currency.ts +0 -5
- package/src/helpers/index.ts +0 -2
- package/src/hooks/use-get-all-contracts.ts +1 -0
- package/dist/assets/index-CJB5mMpd.js +0 -1224
- package/dist/assets/index-CzRRUK04.css +0 -1
- package/src/components/business/business-header.tsx +0 -65
- package/src/components/business/charges-section.tsx +0 -82
- package/src/components/business/charts-section.tsx +0 -115
- package/src/components/business/configurations-section.tsx +0 -835
- package/src/components/business/contact-info-section.tsx +0 -544
- package/src/components/business/contracts-section.tsx +0 -195
- package/src/components/business/documents-section.tsx +0 -26
- package/src/components/business/index.tsx +0 -171
- package/src/components/business/integrations-section.tsx +0 -479
- package/src/components/business/transactions-section.tsx +0 -26
- package/src/components/clients/contracts/modify-contract-dialog.tsx +0 -466
- package/src/components/clients/modify-client-dialog.tsx +0 -276
- package/src/components/screens/businesses/business.tsx +0 -50
- package/src/components/ui/progress.tsx +0 -25
- package/src/components/ui/skeleton.tsx +0 -12
- package/src/helpers/contracts.ts +0 -22
- package/src/helpers/pcn874.ts +0 -17
- package/src/hooks/use-create-contract.ts +0 -62
- package/src/hooks/use-delete-contract.ts +0 -64
- package/src/hooks/use-insert-client.ts +0 -80
- package/src/hooks/use-update-client.ts +0 -75
- package/src/hooks/use-update-contract.ts +0 -69
|
@@ -1,835 +0,0 @@
|
|
|
1
|
-
import { useCallback, useContext, useEffect, useState } from 'react';
|
|
2
|
-
import { Plus, Save, X } from 'lucide-react';
|
|
3
|
-
import { useForm } from 'react-hook-form';
|
|
4
|
-
import { Badge } from '@/components/ui/badge.js';
|
|
5
|
-
import { Button } from '@/components/ui/button.js';
|
|
6
|
-
import {
|
|
7
|
-
Card,
|
|
8
|
-
CardContent,
|
|
9
|
-
CardDescription,
|
|
10
|
-
CardFooter,
|
|
11
|
-
CardHeader,
|
|
12
|
-
CardTitle,
|
|
13
|
-
} from '@/components/ui/card.js';
|
|
14
|
-
import {
|
|
15
|
-
Form,
|
|
16
|
-
FormControl,
|
|
17
|
-
FormDescription,
|
|
18
|
-
FormField,
|
|
19
|
-
FormItem,
|
|
20
|
-
FormLabel,
|
|
21
|
-
FormMessage,
|
|
22
|
-
} from '@/components/ui/form.js';
|
|
23
|
-
import { Input } from '@/components/ui/input.js';
|
|
24
|
-
import {
|
|
25
|
-
Select,
|
|
26
|
-
SelectContent,
|
|
27
|
-
SelectItem,
|
|
28
|
-
SelectTrigger,
|
|
29
|
-
SelectValue,
|
|
30
|
-
} from '@/components/ui/select.js';
|
|
31
|
-
import { Separator } from '@/components/ui/separator.js';
|
|
32
|
-
import { Switch } from '@/components/ui/switch.js';
|
|
33
|
-
import {
|
|
34
|
-
BusinessConfigurationSectionFragmentDoc,
|
|
35
|
-
EmailAttachmentType,
|
|
36
|
-
Pcn874RecordType,
|
|
37
|
-
type BusinessConfigurationSectionFragment,
|
|
38
|
-
type UpdateBusinessInput,
|
|
39
|
-
} from '@/gql/graphql.js';
|
|
40
|
-
import { getFragmentData, type FragmentType } from '@/gql/index.js';
|
|
41
|
-
import {
|
|
42
|
-
dirtyFieldMarker,
|
|
43
|
-
pcn874RecordEnum,
|
|
44
|
-
relevantDataPicker,
|
|
45
|
-
type MakeBoolean,
|
|
46
|
-
} from '@/helpers/index.js';
|
|
47
|
-
import { useGetSortCodes } from '@/hooks/use-get-sort-codes.js';
|
|
48
|
-
import { useGetTags } from '@/hooks/use-get-tags.js';
|
|
49
|
-
import { useGetTaxCategories } from '@/hooks/use-get-tax-categories.js';
|
|
50
|
-
import { useUpdateBusiness } from '@/hooks/use-update-business.js';
|
|
51
|
-
import { UserContext } from '@/providers/user-provider.js';
|
|
52
|
-
import { ModifyClientDialog } from '../clients/modify-client-dialog.js';
|
|
53
|
-
import {
|
|
54
|
-
ComboBox,
|
|
55
|
-
MultiSelect,
|
|
56
|
-
NumberInput,
|
|
57
|
-
SimilarChargesByBusinessModal,
|
|
58
|
-
} from '../common/index.js';
|
|
59
|
-
|
|
60
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
|
|
61
|
-
/* GraphQL */ `
|
|
62
|
-
fragment BusinessConfigurationSection on Business {
|
|
63
|
-
__typename
|
|
64
|
-
id
|
|
65
|
-
pcn874RecordType
|
|
66
|
-
irsCode
|
|
67
|
-
... on LtdFinancialEntity {
|
|
68
|
-
optionalVAT
|
|
69
|
-
exemptDealer
|
|
70
|
-
sortCode {
|
|
71
|
-
id
|
|
72
|
-
key
|
|
73
|
-
defaultIrsCode
|
|
74
|
-
}
|
|
75
|
-
taxCategory {
|
|
76
|
-
id
|
|
77
|
-
}
|
|
78
|
-
suggestions {
|
|
79
|
-
phrases
|
|
80
|
-
emails
|
|
81
|
-
tags {
|
|
82
|
-
id
|
|
83
|
-
}
|
|
84
|
-
description
|
|
85
|
-
emailListener {
|
|
86
|
-
internalEmailLinks
|
|
87
|
-
emailBody
|
|
88
|
-
attachments
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
clientInfo {
|
|
92
|
-
id
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
`;
|
|
97
|
-
|
|
98
|
-
interface ConfigurationFormValues {
|
|
99
|
-
isClient: boolean;
|
|
100
|
-
// TODO: activate these fields later. requires additional backend support
|
|
101
|
-
// isActive: boolean;
|
|
102
|
-
// isReceiptEnough: boolean;
|
|
103
|
-
// noDocsRequired: boolean;
|
|
104
|
-
isVatOptional: boolean;
|
|
105
|
-
isExemptDealer: boolean;
|
|
106
|
-
sortCode: string;
|
|
107
|
-
taxCategory: string;
|
|
108
|
-
pcn874RecordType: Pcn874RecordType;
|
|
109
|
-
irsCode: number | null;
|
|
110
|
-
description: string;
|
|
111
|
-
tags: string[];
|
|
112
|
-
phrases: string[];
|
|
113
|
-
emails: string[];
|
|
114
|
-
internalLinks: string[];
|
|
115
|
-
attachmentTypes: EmailAttachmentType[];
|
|
116
|
-
useMessageBody: boolean;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function ConfigurationsSectionFragmentToFormValues(
|
|
120
|
-
business?: BusinessConfigurationSectionFragment,
|
|
121
|
-
): Partial<ConfigurationFormValues> {
|
|
122
|
-
if (!business || business.__typename !== 'LtdFinancialEntity') {
|
|
123
|
-
return {} as ConfigurationFormValues;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
isClient: !!business.clientInfo?.id,
|
|
128
|
-
// TODO: activate these fields later. requires additional backend support
|
|
129
|
-
// isActive: true,
|
|
130
|
-
// isReceiptEnough: false,
|
|
131
|
-
// noDocsRequired: false,
|
|
132
|
-
isVatOptional: business.optionalVAT ?? undefined,
|
|
133
|
-
isExemptDealer: business.exemptDealer ?? undefined,
|
|
134
|
-
sortCode: business.sortCode?.key?.toString() ?? undefined,
|
|
135
|
-
taxCategory: business.taxCategory?.id ?? undefined,
|
|
136
|
-
pcn874RecordType: business?.pcn874RecordType ?? undefined,
|
|
137
|
-
irsCode: business?.irsCode ?? undefined,
|
|
138
|
-
description: business.suggestions?.description ?? undefined,
|
|
139
|
-
tags: business.suggestions?.tags?.map(tag => tag.id) ?? undefined,
|
|
140
|
-
phrases: business.suggestions?.phrases,
|
|
141
|
-
emails: business.suggestions?.emails ?? undefined,
|
|
142
|
-
internalLinks: business.suggestions?.emailListener?.internalEmailLinks ?? undefined,
|
|
143
|
-
attachmentTypes: business.suggestions?.emailListener?.attachments ?? undefined,
|
|
144
|
-
useMessageBody: business.suggestions?.emailListener?.emailBody ?? undefined,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function convertFormDataToUpdateBusinessInput(
|
|
149
|
-
formData: Partial<ConfigurationFormValues>,
|
|
150
|
-
): UpdateBusinessInput {
|
|
151
|
-
const emailListenerDataExists =
|
|
152
|
-
formData.internalLinks || formData.useMessageBody || formData.attachmentTypes;
|
|
153
|
-
const emailListener:
|
|
154
|
-
| NonNullable<UpdateBusinessInput['suggestions']>['emailListener']
|
|
155
|
-
| undefined = emailListenerDataExists
|
|
156
|
-
? {
|
|
157
|
-
internalEmailLinks: formData.internalLinks,
|
|
158
|
-
emailBody: formData.useMessageBody,
|
|
159
|
-
attachments: formData.attachmentTypes,
|
|
160
|
-
}
|
|
161
|
-
: undefined;
|
|
162
|
-
|
|
163
|
-
const suggestionsDataExists =
|
|
164
|
-
formData.description || formData.tags || formData.phrases || formData.emails || emailListener;
|
|
165
|
-
const suggestions: UpdateBusinessInput['suggestions'] | undefined = suggestionsDataExists
|
|
166
|
-
? {
|
|
167
|
-
description: formData.description,
|
|
168
|
-
tags: formData.tags?.map(id => ({ id })),
|
|
169
|
-
phrases: formData.phrases,
|
|
170
|
-
emails: formData.emails,
|
|
171
|
-
emailListener,
|
|
172
|
-
}
|
|
173
|
-
: undefined;
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
optionalVAT: formData.isVatOptional,
|
|
177
|
-
exemptDealer: formData.isExemptDealer,
|
|
178
|
-
sortCode: formData.sortCode ? parseInt(formData.sortCode) : undefined,
|
|
179
|
-
taxCategory: formData.taxCategory,
|
|
180
|
-
pcn874RecordType: formData.pcn874RecordType,
|
|
181
|
-
irsCode: formData.irsCode,
|
|
182
|
-
suggestions,
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
interface Props {
|
|
187
|
-
data?: FragmentType<typeof BusinessConfigurationSectionFragmentDoc>;
|
|
188
|
-
refetchBusiness?: () => Promise<void>;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export function ConfigurationsSection({ data, refetchBusiness }: Props) {
|
|
192
|
-
const business = getFragmentData(BusinessConfigurationSectionFragmentDoc, data);
|
|
193
|
-
const [defaultFormValues, setDefaultFormValues] = useState(
|
|
194
|
-
ConfigurationsSectionFragmentToFormValues(business),
|
|
195
|
-
);
|
|
196
|
-
const { userContext } = useContext(UserContext);
|
|
197
|
-
|
|
198
|
-
const { updateBusiness: updateDbBusiness, fetching: isBusinessUpdating } = useUpdateBusiness();
|
|
199
|
-
|
|
200
|
-
const [similarChargesOpen, setSimilarChargesOpen] = useState(false);
|
|
201
|
-
const [similarChargesData, setSimilarChargesData] = useState<
|
|
202
|
-
| {
|
|
203
|
-
tagIds?: { id: string }[];
|
|
204
|
-
description?: string;
|
|
205
|
-
}
|
|
206
|
-
| undefined
|
|
207
|
-
>(undefined);
|
|
208
|
-
const [newPhrase, setNewPhrase] = useState('');
|
|
209
|
-
const [newEmail, setNewEmail] = useState('');
|
|
210
|
-
const [newLink, setNewLink] = useState('');
|
|
211
|
-
|
|
212
|
-
const form = useForm<ConfigurationFormValues>({
|
|
213
|
-
defaultValues: defaultFormValues,
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
// Sort codes array handle
|
|
217
|
-
const { selectableSortCodes, fetching: fetchingSortCodes, sortCodes } = useGetSortCodes();
|
|
218
|
-
|
|
219
|
-
// Tax categories array handle
|
|
220
|
-
const { selectableTaxCategories, fetching: fetchingTaxCategories } = useGetTaxCategories();
|
|
221
|
-
|
|
222
|
-
// Tags handle
|
|
223
|
-
const { selectableTags, fetching: fetchingTags } = useGetTags();
|
|
224
|
-
|
|
225
|
-
const availableAttachmentTypes = Object.values(EmailAttachmentType);
|
|
226
|
-
|
|
227
|
-
const addPhrase = () => {
|
|
228
|
-
if (newPhrase.trim()) {
|
|
229
|
-
const currentPhrases = form.getValues('phrases');
|
|
230
|
-
form.setValue('phrases', [...currentPhrases, newPhrase.trim()], { shouldDirty: true });
|
|
231
|
-
setNewPhrase('');
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
const addEmail = () => {
|
|
236
|
-
if (newEmail.trim()) {
|
|
237
|
-
const currentEmails = form.getValues('emails');
|
|
238
|
-
form.setValue('emails', [...currentEmails, newEmail.trim()], { shouldDirty: true });
|
|
239
|
-
setNewEmail('');
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
const addLink = () => {
|
|
244
|
-
if (newLink.trim()) {
|
|
245
|
-
const currentLinks = form.getValues('internalLinks');
|
|
246
|
-
form.setValue('internalLinks', [...currentLinks, newLink.trim()], { shouldDirty: true });
|
|
247
|
-
setNewLink('');
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const removePhrase = (index: number) => {
|
|
252
|
-
const currentPhrases = form.getValues('phrases');
|
|
253
|
-
form.setValue(
|
|
254
|
-
'phrases',
|
|
255
|
-
currentPhrases.filter((_, i) => i !== index),
|
|
256
|
-
{ shouldDirty: true },
|
|
257
|
-
);
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
const removeEmail = (index: number) => {
|
|
261
|
-
const currentEmails = form.getValues('emails');
|
|
262
|
-
form.setValue(
|
|
263
|
-
'emails',
|
|
264
|
-
currentEmails.filter((_, i) => i !== index),
|
|
265
|
-
{ shouldDirty: true },
|
|
266
|
-
);
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
const removeLink = (index: number) => {
|
|
270
|
-
const currentLinks = form.getValues('internalLinks');
|
|
271
|
-
form.setValue(
|
|
272
|
-
'internalLinks',
|
|
273
|
-
currentLinks.filter((_, i) => i !== index),
|
|
274
|
-
{ shouldDirty: true },
|
|
275
|
-
);
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
const toggleAttachmentType = (type: EmailAttachmentType) => {
|
|
279
|
-
const currentTypes = form.getValues('attachmentTypes');
|
|
280
|
-
form.setValue(
|
|
281
|
-
'attachmentTypes',
|
|
282
|
-
currentTypes?.includes(type) ? currentTypes.filter(t => t !== type) : [...currentTypes, type],
|
|
283
|
-
{ shouldDirty: true },
|
|
284
|
-
);
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
const onSubmit = async (values: Partial<ConfigurationFormValues>) => {
|
|
288
|
-
if (!business || !userContext?.context.adminBusinessId) {
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const dataToUpdate = relevantDataPicker(
|
|
293
|
-
values,
|
|
294
|
-
form.formState.dirtyFields as MakeBoolean<typeof values>,
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
if (!dataToUpdate) return;
|
|
298
|
-
|
|
299
|
-
const updateBusinessInput = convertFormDataToUpdateBusinessInput(dataToUpdate);
|
|
300
|
-
|
|
301
|
-
await updateDbBusiness({
|
|
302
|
-
businessId: business.id,
|
|
303
|
-
ownerId: userContext.context.adminBusinessId,
|
|
304
|
-
fields: updateBusinessInput,
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
if (dataToUpdate.tags?.length || dataToUpdate.description) {
|
|
308
|
-
// Show similar charges modal if tags or description were updated
|
|
309
|
-
setSimilarChargesData({
|
|
310
|
-
tagIds: dataToUpdate.tags?.map(id => ({ id })),
|
|
311
|
-
description: dataToUpdate.description,
|
|
312
|
-
});
|
|
313
|
-
setSimilarChargesOpen(true);
|
|
314
|
-
} else {
|
|
315
|
-
// Otherwise, just refetch business data
|
|
316
|
-
refetchBusiness?.();
|
|
317
|
-
}
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
// When sort code changes, update IRS code if sort code has a default IRS code
|
|
321
|
-
const onSortCodeChangeUpdateIrsCode = useCallback(
|
|
322
|
-
(sortCode: string | null) => {
|
|
323
|
-
if (sortCode) {
|
|
324
|
-
const sortCodeObj = sortCodes.find(sc => Number(sc.key) === Number(sortCode));
|
|
325
|
-
|
|
326
|
-
if (sortCodeObj) {
|
|
327
|
-
if (sortCodeObj.defaultIrsCode) {
|
|
328
|
-
form.setValue('irsCode', sortCodeObj.defaultIrsCode, { shouldDirty: true });
|
|
329
|
-
} else {
|
|
330
|
-
form.setValue('irsCode', null, { shouldDirty: true });
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
[form, sortCodes],
|
|
336
|
-
);
|
|
337
|
-
|
|
338
|
-
useEffect(() => {
|
|
339
|
-
if (business) {
|
|
340
|
-
const formValues = ConfigurationsSectionFragmentToFormValues(business);
|
|
341
|
-
setDefaultFormValues(formValues);
|
|
342
|
-
form.reset(formValues);
|
|
343
|
-
}
|
|
344
|
-
}, [business, form]);
|
|
345
|
-
|
|
346
|
-
if (!business) {
|
|
347
|
-
return <div />;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
const isClient = form.watch('isClient');
|
|
351
|
-
|
|
352
|
-
return (
|
|
353
|
-
<Card>
|
|
354
|
-
<CardHeader>
|
|
355
|
-
<div className="flex items-center justify-between">
|
|
356
|
-
<div>
|
|
357
|
-
<CardTitle>Configurations</CardTitle>
|
|
358
|
-
<CardDescription>
|
|
359
|
-
Business status, tax settings, automation rules, and integration preferences
|
|
360
|
-
</CardDescription>
|
|
361
|
-
</div>
|
|
362
|
-
{!isClient && <ModifyClientDialog businessId={business.id} onDone={refetchBusiness} />}
|
|
363
|
-
</div>
|
|
364
|
-
</CardHeader>
|
|
365
|
-
<Form {...form}>
|
|
366
|
-
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
367
|
-
<CardContent className="space-y-6">
|
|
368
|
-
<div className="space-y-4">
|
|
369
|
-
<h3 className="text-sm font-semibold text-foreground">Business Status & Behavior</h3>
|
|
370
|
-
<div className="space-y-4">
|
|
371
|
-
<FormField
|
|
372
|
-
control={form.control}
|
|
373
|
-
name="isClient"
|
|
374
|
-
render={({ field }) => (
|
|
375
|
-
<FormItem className="flex items-center justify-between">
|
|
376
|
-
<div className="space-y-0.5">
|
|
377
|
-
<FormLabel>Is Client</FormLabel>
|
|
378
|
-
<FormDescription>Mark this business as a client</FormDescription>
|
|
379
|
-
</div>
|
|
380
|
-
<FormControl>
|
|
381
|
-
<Switch disabled checked={field.value} onCheckedChange={field.onChange} />
|
|
382
|
-
</FormControl>
|
|
383
|
-
</FormItem>
|
|
384
|
-
)}
|
|
385
|
-
/>
|
|
386
|
-
|
|
387
|
-
{/* TODO: activate these fields later. requires additional backend support */}
|
|
388
|
-
{/* <FormField
|
|
389
|
-
control={form.control}
|
|
390
|
-
name="isActive"
|
|
391
|
-
render={({ field }) => (
|
|
392
|
-
<FormItem className="flex items-center justify-between">
|
|
393
|
-
<div className="space-y-0.5">
|
|
394
|
-
<FormLabel>Is Active</FormLabel>
|
|
395
|
-
<FormDescription>Business is currently active</FormDescription>
|
|
396
|
-
</div>
|
|
397
|
-
<FormControl>
|
|
398
|
-
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
|
399
|
-
</FormControl>
|
|
400
|
-
</FormItem>
|
|
401
|
-
)}
|
|
402
|
-
/> */}
|
|
403
|
-
|
|
404
|
-
{/* <FormField
|
|
405
|
-
control={form.control}
|
|
406
|
-
name="isReceiptEnough"
|
|
407
|
-
render={({ field }) => (
|
|
408
|
-
<FormItem className="flex items-center justify-between">
|
|
409
|
-
<div className="space-y-0.5">
|
|
410
|
-
<FormLabel>Is Receipt Enough</FormLabel>
|
|
411
|
-
<FormDescription>
|
|
412
|
-
Generate ledger for receipt documents if no invoice available
|
|
413
|
-
</FormDescription>
|
|
414
|
-
</div>
|
|
415
|
-
<FormControl>
|
|
416
|
-
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
|
417
|
-
</FormControl>
|
|
418
|
-
</FormItem>
|
|
419
|
-
)}
|
|
420
|
-
/> */}
|
|
421
|
-
|
|
422
|
-
{/* <FormField
|
|
423
|
-
control={form.control}
|
|
424
|
-
name="noDocsRequired"
|
|
425
|
-
render={({ field }) => (
|
|
426
|
-
<FormItem className="flex items-center justify-between">
|
|
427
|
-
<div className="space-y-0.5">
|
|
428
|
-
<FormLabel>No Docs Required</FormLabel>
|
|
429
|
-
<FormDescription>
|
|
430
|
-
Skip document validation for common charges
|
|
431
|
-
</FormDescription>
|
|
432
|
-
</div>
|
|
433
|
-
<FormControl>
|
|
434
|
-
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
|
435
|
-
</FormControl>
|
|
436
|
-
</FormItem>
|
|
437
|
-
)}
|
|
438
|
-
/> */}
|
|
439
|
-
|
|
440
|
-
<FormField
|
|
441
|
-
control={form.control}
|
|
442
|
-
name="isVatOptional"
|
|
443
|
-
render={({ field }) => (
|
|
444
|
-
<FormItem className="flex items-center justify-between">
|
|
445
|
-
<div className="space-y-0.5">
|
|
446
|
-
<FormLabel>Is VAT Optional</FormLabel>
|
|
447
|
-
<FormDescription>Mute missing VAT indicator</FormDescription>
|
|
448
|
-
</div>
|
|
449
|
-
<FormControl>
|
|
450
|
-
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
|
451
|
-
</FormControl>
|
|
452
|
-
</FormItem>
|
|
453
|
-
)}
|
|
454
|
-
/>
|
|
455
|
-
|
|
456
|
-
<FormField
|
|
457
|
-
control={form.control}
|
|
458
|
-
name="isExemptDealer"
|
|
459
|
-
render={({ field }) => (
|
|
460
|
-
<FormItem className="flex items-center justify-between">
|
|
461
|
-
<div className="space-y-0.5">
|
|
462
|
-
<FormLabel>Is Exempt Dealer</FormLabel>
|
|
463
|
-
<FormDescription>Business is exempt from VAT requirements</FormDescription>
|
|
464
|
-
</div>
|
|
465
|
-
<FormControl>
|
|
466
|
-
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
|
467
|
-
</FormControl>
|
|
468
|
-
</FormItem>
|
|
469
|
-
)}
|
|
470
|
-
/>
|
|
471
|
-
</div>
|
|
472
|
-
</div>
|
|
473
|
-
|
|
474
|
-
<Separator />
|
|
475
|
-
|
|
476
|
-
<div className="space-y-4">
|
|
477
|
-
<h3 className="text-sm font-semibold text-foreground">Default Settings</h3>
|
|
478
|
-
<div className="grid gap-4 md:grid-cols-2">
|
|
479
|
-
<FormField
|
|
480
|
-
control={form.control}
|
|
481
|
-
name="sortCode"
|
|
482
|
-
render={({ field, fieldState }) => (
|
|
483
|
-
<FormItem>
|
|
484
|
-
<FormLabel>Sort Code</FormLabel>
|
|
485
|
-
<FormControl>
|
|
486
|
-
<ComboBox
|
|
487
|
-
onChange={sortCode => {
|
|
488
|
-
onSortCodeChangeUpdateIrsCode(sortCode);
|
|
489
|
-
field.onChange(sortCode);
|
|
490
|
-
}}
|
|
491
|
-
data={selectableSortCodes}
|
|
492
|
-
value={field.value}
|
|
493
|
-
disabled={fetchingSortCodes}
|
|
494
|
-
placeholder="Scroll to see all options"
|
|
495
|
-
formPart
|
|
496
|
-
triggerProps={{
|
|
497
|
-
className: dirtyFieldMarker(fieldState),
|
|
498
|
-
}}
|
|
499
|
-
/>
|
|
500
|
-
</FormControl>
|
|
501
|
-
<FormMessage />
|
|
502
|
-
</FormItem>
|
|
503
|
-
)}
|
|
504
|
-
/>
|
|
505
|
-
|
|
506
|
-
<FormField
|
|
507
|
-
control={form.control}
|
|
508
|
-
name="taxCategory"
|
|
509
|
-
render={({ field, fieldState }) => (
|
|
510
|
-
<FormItem>
|
|
511
|
-
<FormLabel>Tax Category</FormLabel>
|
|
512
|
-
<FormControl>
|
|
513
|
-
<ComboBox
|
|
514
|
-
data={selectableTaxCategories}
|
|
515
|
-
disabled={fetchingTaxCategories}
|
|
516
|
-
value={field.value}
|
|
517
|
-
onChange={field.onChange}
|
|
518
|
-
placeholder="Scroll to see all options"
|
|
519
|
-
formPart
|
|
520
|
-
triggerProps={{
|
|
521
|
-
className: dirtyFieldMarker(fieldState),
|
|
522
|
-
}}
|
|
523
|
-
/>
|
|
524
|
-
</FormControl>
|
|
525
|
-
<FormMessage />
|
|
526
|
-
</FormItem>
|
|
527
|
-
)}
|
|
528
|
-
/>
|
|
529
|
-
|
|
530
|
-
<FormField
|
|
531
|
-
control={form.control}
|
|
532
|
-
name="pcn874RecordType"
|
|
533
|
-
render={({ field, fieldState }) => (
|
|
534
|
-
<FormItem>
|
|
535
|
-
<FormLabel>PCN874 Record Type</FormLabel>
|
|
536
|
-
<Select onValueChange={field.onChange} value={field.value}>
|
|
537
|
-
<FormControl>
|
|
538
|
-
<SelectTrigger className={dirtyFieldMarker(fieldState)}>
|
|
539
|
-
<SelectValue />
|
|
540
|
-
</SelectTrigger>
|
|
541
|
-
</FormControl>
|
|
542
|
-
<SelectContent>
|
|
543
|
-
{Object.entries(pcn874RecordEnum).map(([value, label]) => (
|
|
544
|
-
<SelectItem key={value} value={value}>
|
|
545
|
-
{`${label} (${value})`}
|
|
546
|
-
</SelectItem>
|
|
547
|
-
))}
|
|
548
|
-
</SelectContent>
|
|
549
|
-
</Select>
|
|
550
|
-
<FormMessage />
|
|
551
|
-
</FormItem>
|
|
552
|
-
)}
|
|
553
|
-
/>
|
|
554
|
-
|
|
555
|
-
<FormField
|
|
556
|
-
control={form.control}
|
|
557
|
-
name="irsCode"
|
|
558
|
-
render={({ field, fieldState }) => (
|
|
559
|
-
<FormItem>
|
|
560
|
-
<FormLabel>IRS Code</FormLabel>
|
|
561
|
-
<FormControl>
|
|
562
|
-
<NumberInput
|
|
563
|
-
value={field.value ?? undefined}
|
|
564
|
-
onValueChange={value => field.onChange(value ?? null)}
|
|
565
|
-
hideControls
|
|
566
|
-
decimalScale={0}
|
|
567
|
-
className={dirtyFieldMarker(fieldState)}
|
|
568
|
-
/>
|
|
569
|
-
</FormControl>
|
|
570
|
-
<FormMessage />
|
|
571
|
-
</FormItem>
|
|
572
|
-
)}
|
|
573
|
-
/>
|
|
574
|
-
|
|
575
|
-
<FormField
|
|
576
|
-
control={form.control}
|
|
577
|
-
name="description"
|
|
578
|
-
render={({ field, fieldState }) => (
|
|
579
|
-
<FormItem>
|
|
580
|
-
<FormLabel>Charge Description</FormLabel>
|
|
581
|
-
<FormControl>
|
|
582
|
-
<Input
|
|
583
|
-
placeholder="Enter default charge description"
|
|
584
|
-
{...field}
|
|
585
|
-
className={dirtyFieldMarker(fieldState)}
|
|
586
|
-
/>
|
|
587
|
-
</FormControl>
|
|
588
|
-
<FormMessage />
|
|
589
|
-
</FormItem>
|
|
590
|
-
)}
|
|
591
|
-
/>
|
|
592
|
-
|
|
593
|
-
<FormField
|
|
594
|
-
control={form.control}
|
|
595
|
-
name="tags"
|
|
596
|
-
render={({ field, fieldState }) => (
|
|
597
|
-
<FormItem>
|
|
598
|
-
<FormLabel>Tags</FormLabel>
|
|
599
|
-
<FormControl>
|
|
600
|
-
<MultiSelect
|
|
601
|
-
options={Object.values(selectableTags).map(({ value, label }) => ({
|
|
602
|
-
label,
|
|
603
|
-
value,
|
|
604
|
-
}))}
|
|
605
|
-
onValueChange={field.onChange}
|
|
606
|
-
defaultValue={field.value}
|
|
607
|
-
value={field.value}
|
|
608
|
-
placeholder="Select Default Tags"
|
|
609
|
-
variant="default"
|
|
610
|
-
disabled={fetchingTags}
|
|
611
|
-
className={dirtyFieldMarker(fieldState)}
|
|
612
|
-
/>
|
|
613
|
-
</FormControl>
|
|
614
|
-
<FormMessage />
|
|
615
|
-
</FormItem>
|
|
616
|
-
)}
|
|
617
|
-
/>
|
|
618
|
-
</div>
|
|
619
|
-
</div>
|
|
620
|
-
|
|
621
|
-
<Separator />
|
|
622
|
-
|
|
623
|
-
<div className="space-y-4">
|
|
624
|
-
<h3 className="text-sm font-semibold text-foreground">Auto-matching Configuration</h3>
|
|
625
|
-
<p className="text-sm text-muted-foreground">
|
|
626
|
-
Configure patterns for automatic matching of bank transactions and documents
|
|
627
|
-
</p>
|
|
628
|
-
|
|
629
|
-
<FormField
|
|
630
|
-
control={form.control}
|
|
631
|
-
name="phrases"
|
|
632
|
-
render={({ field, fieldState }) => (
|
|
633
|
-
<FormItem>
|
|
634
|
-
<FormLabel>Phrases</FormLabel>
|
|
635
|
-
<div className="flex gap-2">
|
|
636
|
-
<Input
|
|
637
|
-
placeholder="Add phrase..."
|
|
638
|
-
value={newPhrase}
|
|
639
|
-
onChange={e => setNewPhrase(e.target.value)}
|
|
640
|
-
onKeyDown={e => {
|
|
641
|
-
if (e.key === 'Enter') {
|
|
642
|
-
e.preventDefault();
|
|
643
|
-
addPhrase();
|
|
644
|
-
}
|
|
645
|
-
}}
|
|
646
|
-
className={dirtyFieldMarker(fieldState)}
|
|
647
|
-
/>
|
|
648
|
-
<Button type="button" size="sm" onClick={addPhrase}>
|
|
649
|
-
<Plus className="h-4 w-4" />
|
|
650
|
-
</Button>
|
|
651
|
-
</div>
|
|
652
|
-
<div className="flex flex-wrap gap-2">
|
|
653
|
-
{field.value?.map((phrase, index) => (
|
|
654
|
-
<Badge key={index} variant="secondary" className="gap-1">
|
|
655
|
-
{phrase}
|
|
656
|
-
<Button
|
|
657
|
-
variant="ghost"
|
|
658
|
-
size="icon"
|
|
659
|
-
className="p-0 size-3"
|
|
660
|
-
onClick={() => removePhrase(index)}
|
|
661
|
-
>
|
|
662
|
-
<X
|
|
663
|
-
className="size-3 cursor-pointer"
|
|
664
|
-
onClick={() => removePhrase(index)}
|
|
665
|
-
/>
|
|
666
|
-
</Button>
|
|
667
|
-
</Badge>
|
|
668
|
-
))}
|
|
669
|
-
</div>
|
|
670
|
-
<FormMessage />
|
|
671
|
-
</FormItem>
|
|
672
|
-
)}
|
|
673
|
-
/>
|
|
674
|
-
|
|
675
|
-
<FormField
|
|
676
|
-
control={form.control}
|
|
677
|
-
name="emails"
|
|
678
|
-
render={({ field, fieldState }) => (
|
|
679
|
-
<FormItem>
|
|
680
|
-
<FormLabel>Email Addresses</FormLabel>
|
|
681
|
-
<div className="flex gap-2">
|
|
682
|
-
<Input
|
|
683
|
-
type="email"
|
|
684
|
-
placeholder="Add email..."
|
|
685
|
-
value={newEmail}
|
|
686
|
-
onChange={e => setNewEmail(e.target.value)}
|
|
687
|
-
onKeyDown={e => {
|
|
688
|
-
if (e.key === 'Enter') {
|
|
689
|
-
e.preventDefault();
|
|
690
|
-
addEmail();
|
|
691
|
-
}
|
|
692
|
-
}}
|
|
693
|
-
className={dirtyFieldMarker(fieldState)}
|
|
694
|
-
/>
|
|
695
|
-
<Button type="button" size="sm" onClick={addEmail}>
|
|
696
|
-
<Plus className="h-4 w-4" />
|
|
697
|
-
</Button>
|
|
698
|
-
</div>
|
|
699
|
-
<div className="flex flex-wrap gap-2">
|
|
700
|
-
{field.value?.map((email, index) => (
|
|
701
|
-
<Badge key={index} variant="secondary" className="gap-1">
|
|
702
|
-
{email}
|
|
703
|
-
<Button
|
|
704
|
-
variant="ghost"
|
|
705
|
-
size="icon"
|
|
706
|
-
className="p-0 size-3"
|
|
707
|
-
onClick={() => removeEmail(index)}
|
|
708
|
-
>
|
|
709
|
-
<X className="size-3 cursor-pointer" />
|
|
710
|
-
</Button>
|
|
711
|
-
</Badge>
|
|
712
|
-
))}
|
|
713
|
-
</div>
|
|
714
|
-
<FormMessage />
|
|
715
|
-
</FormItem>
|
|
716
|
-
)}
|
|
717
|
-
/>
|
|
718
|
-
</div>
|
|
719
|
-
|
|
720
|
-
<Separator />
|
|
721
|
-
|
|
722
|
-
<div className="space-y-4">
|
|
723
|
-
<h3 className="text-sm font-semibold text-foreground">Gmail Feature Configuration</h3>
|
|
724
|
-
<p className="text-sm text-muted-foreground">
|
|
725
|
-
Configure Gmail integration settings for document processing
|
|
726
|
-
</p>
|
|
727
|
-
|
|
728
|
-
<FormField
|
|
729
|
-
control={form.control}
|
|
730
|
-
name="attachmentTypes"
|
|
731
|
-
render={({ field, fieldState }) => (
|
|
732
|
-
<FormItem>
|
|
733
|
-
<FormLabel>Attachment Types</FormLabel>
|
|
734
|
-
<div
|
|
735
|
-
className={dirtyFieldMarker(fieldState) + ' rounded-md flex flex-wrap gap-2'}
|
|
736
|
-
>
|
|
737
|
-
{availableAttachmentTypes.map(type => (
|
|
738
|
-
<Badge
|
|
739
|
-
key={type}
|
|
740
|
-
variant={field.value?.includes(type) ? 'default' : 'outline'}
|
|
741
|
-
className="cursor-pointer"
|
|
742
|
-
onClick={() => toggleAttachmentType(type)}
|
|
743
|
-
>
|
|
744
|
-
{type}
|
|
745
|
-
</Badge>
|
|
746
|
-
))}
|
|
747
|
-
</div>
|
|
748
|
-
<FormMessage />
|
|
749
|
-
</FormItem>
|
|
750
|
-
)}
|
|
751
|
-
/>
|
|
752
|
-
|
|
753
|
-
<FormField
|
|
754
|
-
control={form.control}
|
|
755
|
-
name="internalLinks"
|
|
756
|
-
render={({ field }) => (
|
|
757
|
-
<FormItem>
|
|
758
|
-
<FormLabel>Internal Links</FormLabel>
|
|
759
|
-
<div className="flex gap-2">
|
|
760
|
-
<Input
|
|
761
|
-
type="url"
|
|
762
|
-
placeholder="Add internal link..."
|
|
763
|
-
value={newLink}
|
|
764
|
-
onChange={e => setNewLink(e.target.value)}
|
|
765
|
-
onKeyDown={e => {
|
|
766
|
-
if (e.key === 'Enter') {
|
|
767
|
-
e.preventDefault();
|
|
768
|
-
addLink();
|
|
769
|
-
}
|
|
770
|
-
}}
|
|
771
|
-
/>
|
|
772
|
-
<Button type="button" size="sm" onClick={addLink}>
|
|
773
|
-
<Plus className="h-4 w-4" />
|
|
774
|
-
</Button>
|
|
775
|
-
</div>
|
|
776
|
-
<div className="flex flex-wrap gap-2">
|
|
777
|
-
{field.value?.map((link, index) => (
|
|
778
|
-
<Badge key={index} variant="secondary" className="gap-1 max-w-xs truncate">
|
|
779
|
-
{link}
|
|
780
|
-
<Button
|
|
781
|
-
variant="ghost"
|
|
782
|
-
size="icon"
|
|
783
|
-
className="p-0 size-3"
|
|
784
|
-
onClick={() => removeLink(index)}
|
|
785
|
-
>
|
|
786
|
-
<X className="size-3 cursor-pointer flex-shrink-0" />
|
|
787
|
-
</Button>
|
|
788
|
-
</Badge>
|
|
789
|
-
))}
|
|
790
|
-
</div>
|
|
791
|
-
<FormMessage />
|
|
792
|
-
</FormItem>
|
|
793
|
-
)}
|
|
794
|
-
/>
|
|
795
|
-
|
|
796
|
-
<FormField
|
|
797
|
-
control={form.control}
|
|
798
|
-
name="useMessageBody"
|
|
799
|
-
render={({ field }) => (
|
|
800
|
-
<FormItem className="flex items-center justify-between">
|
|
801
|
-
<div className="space-y-0.5">
|
|
802
|
-
<FormLabel>Should Use Message Body</FormLabel>
|
|
803
|
-
<FormDescription>Extract information from email message body</FormDescription>
|
|
804
|
-
</div>
|
|
805
|
-
<FormControl>
|
|
806
|
-
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
|
807
|
-
</FormControl>
|
|
808
|
-
</FormItem>
|
|
809
|
-
)}
|
|
810
|
-
/>
|
|
811
|
-
</div>
|
|
812
|
-
</CardContent>
|
|
813
|
-
<CardFooter className="flex justify-end border-t pt-6">
|
|
814
|
-
<Button
|
|
815
|
-
type="submit"
|
|
816
|
-
disabled={isBusinessUpdating || Object.keys(form.formState.dirtyFields).length === 0}
|
|
817
|
-
>
|
|
818
|
-
<Save className="h-4 w-4 mr-2" />
|
|
819
|
-
Save Changes
|
|
820
|
-
</Button>
|
|
821
|
-
</CardFooter>
|
|
822
|
-
</form>
|
|
823
|
-
</Form>
|
|
824
|
-
|
|
825
|
-
<SimilarChargesByBusinessModal
|
|
826
|
-
businessId={business.id}
|
|
827
|
-
tagIds={similarChargesData?.tagIds}
|
|
828
|
-
description={similarChargesData?.description}
|
|
829
|
-
open={similarChargesOpen}
|
|
830
|
-
onOpenChange={setSimilarChargesOpen}
|
|
831
|
-
onClose={refetchBusiness}
|
|
832
|
-
/>
|
|
833
|
-
</Card>
|
|
834
|
-
);
|
|
835
|
-
}
|