@accounter/client 0.0.7-alpha-20251020201456-275b6a9424e58d4d2c7e9099986230d087ccd6cd → 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.
Files changed (42) hide show
  1. package/CHANGELOG.md +102 -105
  2. package/dist/assets/index-0eCf1BcD.css +1 -0
  3. package/dist/assets/index-DHTbHvtz.js +1188 -0
  4. package/dist/assets/{index.es-DldZjCMz.js → index.es-DHwHzag1.js} +1 -1
  5. package/dist/index.html +2 -2
  6. package/package.json +1 -2
  7. package/src/app.tsx +4 -8
  8. package/src/components/business-transactions/business-extended-info.tsx +13 -9
  9. package/src/components/charges/charge-extended-info-menu.tsx +21 -27
  10. package/src/components/charges/charges-row.tsx +10 -12
  11. package/src/components/charges/charges-table.tsx +9 -15
  12. package/src/components/common/documents/issue-document/recent-client-docs.tsx +3 -5
  13. package/src/components/common/forms/business-card.tsx +0 -1
  14. package/src/components/common/forms/modify-business-fields.tsx +19 -2
  15. package/src/components/common/inputs/combo-box.tsx +1 -1
  16. package/src/components/reports/trial-balance-report/trial-balance-report-group.tsx +6 -4
  17. package/src/components/reports/trial-balance-report/trial-balance-report-sort-code.tsx +11 -8
  18. package/src/gql/gql.ts +6 -72
  19. package/src/gql/graphql.ts +14 -193
  20. package/src/helpers/currency.ts +0 -5
  21. package/src/helpers/index.ts +0 -2
  22. package/dist/assets/index-BSNI6g4T.js +0 -1224
  23. package/dist/assets/index-CzRRUK04.css +0 -1
  24. package/src/components/business/business-header.tsx +0 -65
  25. package/src/components/business/charges-section.tsx +0 -82
  26. package/src/components/business/charts-section.tsx +0 -115
  27. package/src/components/business/configurations-section.tsx +0 -835
  28. package/src/components/business/contact-info-section.tsx +0 -544
  29. package/src/components/business/contracts-section.tsx +0 -195
  30. package/src/components/business/documents-section.tsx +0 -26
  31. package/src/components/business/index.tsx +0 -171
  32. package/src/components/business/integrations-section.tsx +0 -479
  33. package/src/components/business/transactions-section.tsx +0 -26
  34. package/src/components/clients/contracts/modify-contract-dialog.tsx +0 -426
  35. package/src/components/clients/modify-client-dialog.tsx +0 -276
  36. package/src/components/screens/businesses/business.tsx +0 -50
  37. package/src/components/ui/progress.tsx +0 -25
  38. package/src/components/ui/skeleton.tsx +0 -12
  39. package/src/helpers/contracts.ts +0 -22
  40. package/src/helpers/pcn874.ts +0 -17
  41. package/src/hooks/use-insert-client.ts +0 -80
  42. package/src/hooks/use-update-client.ts +0 -75
@@ -1,544 +0,0 @@
1
- import { useContext, useEffect, useState } from 'react';
2
- import { Globe, Mail, MapPin, Phone, Plus, Save, X } from 'lucide-react';
3
- import { useForm } from 'react-hook-form';
4
- import { z } from 'zod';
5
- import { Badge } from '@/components/ui/badge.js';
6
- import { Button } from '@/components/ui/button.js';
7
- import {
8
- Card,
9
- CardContent,
10
- CardDescription,
11
- CardFooter,
12
- CardHeader,
13
- CardTitle,
14
- } from '@/components/ui/card.js';
15
- import {
16
- Form,
17
- FormControl,
18
- FormField,
19
- FormItem,
20
- FormLabel,
21
- FormMessage,
22
- } from '@/components/ui/form.js';
23
- import { Input } from '@/components/ui/input.js';
24
- import { Textarea } from '@/components/ui/textarea.js';
25
- import {
26
- BusinessContactSectionFragmentDoc,
27
- type BusinessContactSectionFragment,
28
- type UpdateBusinessInput,
29
- } from '@/gql/graphql.js';
30
- import { getFragmentData, type FragmentType } from '@/gql/index.js';
31
- import { dirtyFieldMarker, relevantDataPicker, type MakeBoolean } from '@/helpers/index.js';
32
- import { useAllCountries } from '@/hooks/use-get-countries.js';
33
- import { useUpdateBusiness } from '@/hooks/use-update-business.js';
34
- import { UserContext } from '@/providers/user-provider.js';
35
- import { zodResolver } from '@hookform/resolvers/zod';
36
- import { ComboBox } from '../common';
37
-
38
- // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
39
- /* GraphQL */ `
40
- fragment BusinessContactSection on Business {
41
- __typename
42
- id
43
- ... on LtdFinancialEntity {
44
- name
45
- hebrewName
46
- country
47
- governmentId
48
- address
49
- email
50
- # localAddress
51
- phoneNumber
52
- website
53
- clientInfo {
54
- id
55
- emails
56
- }
57
- }
58
- }
59
- `;
60
-
61
- const contactInfoSchema = z.object({
62
- businessName: z.string().min(1, 'Business name is required'),
63
- locality: z.string().min(1, 'Locality is required'),
64
- localName: z.string().optional(),
65
- govId: z.string().optional(),
66
- address: z.string().optional(),
67
- localAddress: z.string().optional(),
68
- phone: z.string().optional(),
69
- website: z.url('Invalid URL').optional().or(z.literal('')),
70
- generalContacts: z.array(z.email()).optional(),
71
- billingEmails: z.array(z.email()).optional(),
72
- });
73
-
74
- type ContactInfoFormValues = z.infer<typeof contactInfoSchema>;
75
-
76
- function ContactsSectionFragmentToFormValues(
77
- business?: BusinessContactSectionFragment,
78
- ): ContactInfoFormValues {
79
- if (!business || business.__typename !== 'LtdFinancialEntity') {
80
- return {} as ContactInfoFormValues;
81
- }
82
-
83
- return {
84
- businessName: business.name,
85
- locality: business.country,
86
- localName: business.hebrewName ?? undefined,
87
- govId: business.governmentId ?? undefined,
88
- address: business.address ?? undefined,
89
- // TODO: activate this field later. requires additional backend support
90
- // localAddress: ,
91
- phone: business.phoneNumber ?? undefined,
92
- website: business.website ?? undefined,
93
- generalContacts: business.email?.split(',').map(email => email.trim()),
94
- billingEmails: business.clientInfo?.emails,
95
- };
96
- }
97
-
98
- function convertFormDataToUpdateBusinessInput(
99
- formData: Partial<ContactInfoFormValues>,
100
- ): UpdateBusinessInput {
101
- return {
102
- name: formData.businessName,
103
- country: formData.locality,
104
- hebrewName: formData.localName,
105
- governmentId: formData.govId,
106
- address: formData.address,
107
- // localAddress: formData.localAddress,
108
- phoneNumber: formData.phone,
109
- website: formData.website,
110
- email: formData.generalContacts?.join(', '),
111
- };
112
- }
113
-
114
- interface Props {
115
- data?: FragmentType<typeof BusinessContactSectionFragmentDoc>;
116
- refetchBusiness?: () => Promise<void>;
117
- }
118
-
119
- export function ContactInfoSection({ data, refetchBusiness }: Props) {
120
- const business = getFragmentData(BusinessContactSectionFragmentDoc, data);
121
- const [defaultFormValues, setDefaultFormValues] = useState(
122
- ContactsSectionFragmentToFormValues(business),
123
- );
124
- const { userContext } = useContext(UserContext);
125
-
126
- const { updateBusiness: updateDbBusiness, fetching: isBusinessUpdating } = useUpdateBusiness();
127
-
128
- const form = useForm<ContactInfoFormValues>({
129
- resolver: zodResolver(contactInfoSchema),
130
- defaultValues: defaultFormValues,
131
- });
132
-
133
- // handle countries
134
- const { countries, fetching: fetchingCountries } = useAllCountries();
135
-
136
- const [newContact, setNewContact] = useState('');
137
- const [newBillingEmail, setNewBillingEmail] = useState('');
138
-
139
- const locality = form.watch('locality');
140
- const address = form.watch('address');
141
- const localAddress = form.watch('localAddress');
142
-
143
- useEffect(() => {
144
- if (address !== defaultFormValues.address) {
145
- form.setValue('address', address, { shouldDirty: true });
146
- }
147
- }, [address, defaultFormValues.address, form]);
148
-
149
- useEffect(() => {
150
- if (localAddress !== defaultFormValues.localAddress) {
151
- form.setValue('localAddress', localAddress, { shouldDirty: true });
152
- }
153
- }, [localAddress, defaultFormValues.localAddress, form]);
154
-
155
- const isClient = business && 'clientInfo' in business && !!business.clientInfo;
156
- const isLocalEntity = locality === 'ISR'; // TODO: Replace with user context based check. requires additional backend support
157
-
158
- const addGeneralContact = (currentContacts: string[]) => {
159
- if (newContact.trim()) {
160
- form.setValue('generalContacts', [...currentContacts, newContact.trim()], {
161
- shouldDirty: true,
162
- });
163
- setNewContact('');
164
- }
165
- };
166
-
167
- const removeGeneralContact = (currentContacts: string[], index: number) => {
168
- form.setValue(
169
- 'generalContacts',
170
- currentContacts.filter((_, i) => i !== index),
171
- { shouldDirty: true },
172
- );
173
- };
174
-
175
- const addBillingEmail = (currentEmails: string[]) => {
176
- if (newBillingEmail.trim()) {
177
- form.setValue('billingEmails', [...currentEmails, newBillingEmail.trim()], {
178
- shouldDirty: true,
179
- });
180
- setNewBillingEmail('');
181
- }
182
- };
183
-
184
- const removeBillingEmail = (currentEmails: string[], index: number) => {
185
- form.setValue(
186
- 'billingEmails',
187
- currentEmails.filter((_, i) => i !== index),
188
- { shouldDirty: true },
189
- );
190
- };
191
-
192
- const onSubmit = async (data: ContactInfoFormValues) => {
193
- if (!business || !userContext?.context.adminBusinessId) {
194
- return;
195
- }
196
-
197
- const dataToUpdate = relevantDataPicker(
198
- data,
199
- form.formState.dirtyFields as MakeBoolean<typeof data>,
200
- );
201
-
202
- if (!dataToUpdate) return;
203
-
204
- const updateBusinessInput = convertFormDataToUpdateBusinessInput(dataToUpdate);
205
-
206
- await updateDbBusiness({
207
- businessId: business.id,
208
- ownerId: userContext.context.adminBusinessId,
209
- fields: updateBusinessInput,
210
- });
211
-
212
- refetchBusiness?.();
213
- };
214
-
215
- useEffect(() => {
216
- if (business) {
217
- const formValues = ContactsSectionFragmentToFormValues(business);
218
- setDefaultFormValues(formValues);
219
- form.reset(formValues);
220
- }
221
- }, [business, form]);
222
-
223
- return (
224
- <Card>
225
- <CardHeader>
226
- <CardTitle>Contact Information</CardTitle>
227
- <CardDescription>Business contact details and address information</CardDescription>
228
- </CardHeader>
229
- <Form {...form}>
230
- <form onSubmit={form.handleSubmit(onSubmit)}>
231
- <CardContent>
232
- <div className="grid gap-6 md:grid-cols-2">
233
- <FormField
234
- control={form.control}
235
- name="businessName"
236
- render={({ field, fieldState }) => (
237
- <FormItem>
238
- <FormLabel>Business Name</FormLabel>
239
- <FormControl>
240
- <Input {...field} className={dirtyFieldMarker(fieldState)} />
241
- </FormControl>
242
- <FormMessage />
243
- </FormItem>
244
- )}
245
- />
246
-
247
- <FormField
248
- control={form.control}
249
- name="locality"
250
- render={({ field, fieldState }) => (
251
- <FormItem>
252
- <FormLabel>Locality / Country</FormLabel>
253
- <ComboBox
254
- onChange={field.onChange}
255
- data={countries.map(country => ({
256
- value: country.code,
257
- label: country.name,
258
- }))}
259
- value={field.value}
260
- disabled={fetchingCountries}
261
- placeholder="Scroll to see all options"
262
- formPart
263
- triggerProps={{
264
- className: dirtyFieldMarker(fieldState),
265
- }}
266
- />
267
- <FormMessage />
268
- </FormItem>
269
- )}
270
- />
271
-
272
- {isLocalEntity && (
273
- <FormField
274
- control={form.control}
275
- name="localName"
276
- render={({ field, fieldState }) => (
277
- <FormItem>
278
- <FormLabel>Local Name</FormLabel>
279
- <FormControl>
280
- <Input
281
- {...field}
282
- placeholder="Business name in local language"
283
- className={dirtyFieldMarker(fieldState)}
284
- />
285
- </FormControl>
286
- <FormMessage />
287
- </FormItem>
288
- )}
289
- />
290
- )}
291
-
292
- {isLocalEntity && (
293
- <FormField
294
- control={form.control}
295
- name="govId"
296
- render={({ field, fieldState }) => (
297
- <FormItem>
298
- <FormLabel>Government ID</FormLabel>
299
- <FormControl>
300
- <Input
301
- {...field}
302
- placeholder="Enter Government ID"
303
- className={dirtyFieldMarker(fieldState)}
304
- />
305
- </FormControl>
306
- <FormMessage />
307
- </FormItem>
308
- )}
309
- />
310
- )}
311
-
312
- <FormField
313
- control={form.control}
314
- name="address"
315
- render={({ field, fieldState }) => (
316
- <FormItem className="md:col-span-2">
317
- <FormLabel className="flex items-center gap-2">
318
- <MapPin className="h-4 w-4" />
319
- Address
320
- </FormLabel>
321
- <FormControl>
322
- <Textarea
323
- {...field}
324
- rows={3}
325
- placeholder="Enter business address"
326
- className={dirtyFieldMarker(fieldState)}
327
- />
328
- </FormControl>
329
- <FormMessage />
330
- </FormItem>
331
- )}
332
- />
333
-
334
- {isLocalEntity && (
335
- <FormField
336
- control={form.control}
337
- name="localAddress"
338
- render={({ field, fieldState }) => (
339
- <FormItem className="md:col-span-2">
340
- <FormLabel className="flex items-center gap-2">
341
- <MapPin className="h-4 w-4" />
342
- Local Address
343
- </FormLabel>
344
- <FormControl>
345
- <Textarea
346
- {...field}
347
- onChange={val => {
348
- console.log(val);
349
- field.onChange(val);
350
- }}
351
- onBlur={val => {
352
- console.log(val);
353
- field.onBlur();
354
- }}
355
- rows={3}
356
- placeholder="Enter address in local language"
357
- className={dirtyFieldMarker(fieldState)}
358
- />
359
- </FormControl>
360
- <FormMessage />
361
- </FormItem>
362
- )}
363
- />
364
- )}
365
-
366
- <FormField
367
- control={form.control}
368
- name="phone"
369
- render={({ field, fieldState }) => (
370
- <FormItem>
371
- <FormLabel className="flex items-center gap-2">
372
- <Phone className="h-4 w-4" />
373
- Phone
374
- </FormLabel>
375
- <FormControl>
376
- <Input
377
- {...field}
378
- type="tel"
379
- placeholder="+1 (555) 123-4567"
380
- className={dirtyFieldMarker(fieldState)}
381
- />
382
- </FormControl>
383
- <FormMessage />
384
- </FormItem>
385
- )}
386
- />
387
-
388
- <FormField
389
- control={form.control}
390
- name="website"
391
- render={({ field, fieldState }) => (
392
- <FormItem>
393
- <FormLabel className="flex items-center gap-2">
394
- <Globe className="h-4 w-4" />
395
- Website
396
- </FormLabel>
397
- <FormControl>
398
- <Input
399
- {...field}
400
- type="url"
401
- placeholder="https://example.com"
402
- className={dirtyFieldMarker(fieldState)}
403
- />
404
- </FormControl>
405
- <FormMessage />
406
- </FormItem>
407
- )}
408
- />
409
-
410
- <FormField
411
- control={form.control}
412
- name="generalContacts"
413
- render={({ field, fieldState }) => (
414
- <FormItem className="md:col-span-2">
415
- <FormLabel className="flex items-center gap-2">
416
- <Mail className="h-4 w-4" />
417
- General Contacts
418
- </FormLabel>
419
- <FormControl>
420
- <div className="space-y-2">
421
- <div
422
- className={
423
- dirtyFieldMarker(fieldState) + ' flex flex-wrap gap-2 mb-2 rounded-md'
424
- }
425
- >
426
- {field.value?.map((contact, index) => (
427
- <Badge key={index} variant="secondary" className="gap-1 pr-1">
428
- {contact}
429
- <button
430
- type="button"
431
- onClick={() => removeGeneralContact(field.value ?? [], index)}
432
- className="ml-1 hover:bg-muted rounded-sm p-0.5"
433
- >
434
- <X className="h-3 w-3" />
435
- </button>
436
- </Badge>
437
- ))}
438
- </div>
439
- <div className="flex gap-2">
440
- <Input
441
- value={newContact}
442
- onChange={e => setNewContact(e.target.value)}
443
- onKeyDown={e => {
444
- if (e.key === 'Enter') {
445
- e.preventDefault();
446
- addGeneralContact(field.value ?? []);
447
- }
448
- }}
449
- placeholder="Add contact email"
450
- type="email"
451
- />
452
- <Button
453
- type="button"
454
- variant="outline"
455
- size="icon"
456
- disabled={!newContact.trim()}
457
- onClick={() => addGeneralContact(field.value ?? [])}
458
- >
459
- <Plus className="h-4 w-4" />
460
- </Button>
461
- </div>
462
- </div>
463
- </FormControl>
464
- <FormMessage />
465
- </FormItem>
466
- )}
467
- />
468
-
469
- {isClient && (
470
- <FormField
471
- control={form.control}
472
- name="billingEmails"
473
- render={({ field, fieldState }) => (
474
- <FormItem className="md:col-span-2">
475
- <FormLabel className="flex items-center gap-2">
476
- <Mail className="h-4 w-4" />
477
- Billing Emails
478
- </FormLabel>
479
- <FormControl>
480
- <div className="space-y-2">
481
- <div
482
- className={
483
- dirtyFieldMarker(fieldState) + ' flex flex-wrap gap-2 mb-2 rounded-md'
484
- }
485
- >
486
- {field.value?.map((email, index) => (
487
- <Badge key={index} variant="secondary" className="gap-1 pr-1">
488
- {email}
489
- <button
490
- type="button"
491
- onClick={() => removeBillingEmail(field.value ?? [], index)}
492
- className="ml-1 hover:bg-muted rounded-sm p-0.5"
493
- >
494
- <X className="h-3 w-3" />
495
- </button>
496
- </Badge>
497
- ))}
498
- </div>
499
- <div className="flex gap-2">
500
- <Input
501
- value={newBillingEmail}
502
- onChange={e => setNewBillingEmail(e.target.value)}
503
- onKeyDown={e => {
504
- if (e.key === 'Enter') {
505
- e.preventDefault();
506
- addBillingEmail(field.value ?? []);
507
- }
508
- }}
509
- placeholder="Add billing email"
510
- type="email"
511
- />
512
- <Button
513
- type="button"
514
- variant="outline"
515
- size="icon"
516
- disabled={!newBillingEmail.trim()}
517
- onClick={() => addBillingEmail(field.value ?? [])}
518
- >
519
- <Plus className="h-4 w-4" />
520
- </Button>
521
- </div>
522
- </div>
523
- </FormControl>
524
- <FormMessage />
525
- </FormItem>
526
- )}
527
- />
528
- )}
529
- </div>
530
- </CardContent>
531
- <CardFooter className="flex justify-end border-t mt-4 pt-6">
532
- <Button
533
- type="submit"
534
- disabled={isBusinessUpdating || Object.keys(form.formState.dirtyFields).length === 0}
535
- >
536
- <Save className="h-4 w-4 mr-2" />
537
- Save Changes
538
- </Button>
539
- </CardFooter>
540
- </form>
541
- </Form>
542
- </Card>
543
- );
544
- }