@accounter/client 0.0.7-alpha-20251020201144-e623a2bb538d1219a5f541df2364646012ebe9ea → 0.0.7-alpha-20251021062721-ab43b5f013b9852ff5216bc141852da9dcfe0df7

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 (50) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/dist/assets/index-CJB5mMpd.js +1224 -0
  3. package/dist/assets/index-CzRRUK04.css +1 -0
  4. package/dist/assets/{index.es-CNXdeXse.js → index.es-NOVMdy_X.js} +1 -1
  5. package/dist/index.html +2 -2
  6. package/package.json +3 -2
  7. package/src/app.tsx +8 -4
  8. package/src/components/business/business-header.tsx +65 -0
  9. package/src/components/business/charges-section.tsx +82 -0
  10. package/src/components/business/charts-section.tsx +115 -0
  11. package/src/components/business/configurations-section.tsx +835 -0
  12. package/src/components/business/contact-info-section.tsx +544 -0
  13. package/src/components/business/contracts-section.tsx +195 -0
  14. package/src/components/business/documents-section.tsx +26 -0
  15. package/src/components/business/index.tsx +171 -0
  16. package/src/components/business/integrations-section.tsx +479 -0
  17. package/src/components/business/transactions-section.tsx +26 -0
  18. package/src/components/business-transactions/business-extended-info.tsx +9 -13
  19. package/src/components/charges/charge-extended-info-menu.tsx +27 -21
  20. package/src/components/charges/charges-row.tsx +12 -10
  21. package/src/components/charges/charges-table.tsx +15 -9
  22. package/src/components/clients/contracts/modify-contract-dialog.tsx +466 -0
  23. package/src/components/clients/modify-client-dialog.tsx +276 -0
  24. package/src/components/common/documents/issue-document/index.tsx +3 -3
  25. package/src/components/common/documents/issue-document/{recent-client-docs.tsx → recent-business-docs.tsx} +19 -13
  26. package/src/components/common/forms/business-card.tsx +1 -0
  27. package/src/components/common/forms/modify-business-fields.tsx +15 -33
  28. package/src/components/common/inputs/combo-box.tsx +1 -1
  29. package/src/components/common/modals/insert-business.tsx +4 -2
  30. package/src/components/reports/trial-balance-report/trial-balance-report-group.tsx +4 -6
  31. package/src/components/reports/trial-balance-report/trial-balance-report-sort-code.tsx +8 -11
  32. package/src/components/screens/businesses/business.tsx +50 -0
  33. package/src/components/screens/documents/issue-documents/edit-issue-document-modal.tsx +4 -4
  34. package/src/components/ui/progress.tsx +25 -0
  35. package/src/components/ui/skeleton.tsx +12 -0
  36. package/src/gql/gql.ts +96 -12
  37. package/src/gql/graphql.ts +290 -11
  38. package/src/helpers/contracts.ts +22 -0
  39. package/src/helpers/currency.ts +5 -0
  40. package/src/helpers/index.ts +2 -0
  41. package/src/helpers/pcn874.ts +17 -0
  42. package/src/hooks/use-create-contract.ts +62 -0
  43. package/src/hooks/use-delete-contract.ts +64 -0
  44. package/src/hooks/use-get-all-contracts.ts +0 -1
  45. package/src/hooks/use-insert-client.ts +80 -0
  46. package/src/hooks/use-update-client.ts +75 -0
  47. package/src/hooks/use-update-contract.ts +69 -0
  48. package/src/providers/user-provider.tsx +2 -0
  49. package/dist/assets/index-0eCf1BcD.css +0 -1
  50. package/dist/assets/index-Dh8zU8Ik.js +0 -1188
@@ -0,0 +1,276 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { Plus, X } from 'lucide-react';
3
+ import { useForm } from 'react-hook-form';
4
+ import { z } from 'zod';
5
+ import { Button } from '@/components/ui/button.js';
6
+ import {
7
+ Dialog,
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogFooter,
11
+ DialogHeader,
12
+ DialogTitle,
13
+ DialogTrigger,
14
+ } from '@/components/ui/dialog.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 {
25
+ Select,
26
+ SelectContent,
27
+ SelectItem,
28
+ SelectTrigger,
29
+ SelectValue,
30
+ } from '@/components/ui/select.js';
31
+ import { DocumentType } from '@/gql/graphql.js';
32
+ import { getDocumentNameFromType } from '@/helpers/index.js';
33
+ import { useInsertClient } from '@/hooks/use-insert-client.js';
34
+ import { useUpdateClient } from '@/hooks/use-update-client.js';
35
+ import { zodResolver } from '@hookform/resolvers/zod';
36
+ import { Badge } from '../ui/badge';
37
+
38
+ const clientFormSchema = z.object({
39
+ id: z.uuid().optional(),
40
+ emails: z.array(z.email()).optional(),
41
+ generatedDocumentType: z.enum(Object.values(DocumentType)),
42
+ greenInvoiceId: z.uuid().optional(),
43
+ hiveId: z.string().optional(),
44
+ });
45
+
46
+ export type ClientFormValues = z.infer<typeof clientFormSchema>;
47
+
48
+ interface Props {
49
+ businessId: string;
50
+ client?: ClientFormValues | null;
51
+ onDone?: () => void;
52
+ }
53
+
54
+ export function ModifyClientDialog({ client, businessId, onDone }: Props) {
55
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
56
+ const [editingClient, setEditingClient] = useState<ClientFormValues | null>(null);
57
+
58
+ const { updateClient } = useUpdateClient();
59
+ const { insertClient } = useInsertClient();
60
+
61
+ const [newEmail, setNewEmail] = useState('');
62
+
63
+ const newClientDefaultValues: ClientFormValues = {
64
+ id: businessId,
65
+ generatedDocumentType: DocumentType.Proforma,
66
+ emails: [],
67
+ };
68
+
69
+ const form = useForm<ClientFormValues>({
70
+ resolver: zodResolver(clientFormSchema),
71
+ defaultValues: client || newClientDefaultValues,
72
+ });
73
+
74
+ useEffect(() => {
75
+ if (client) {
76
+ setEditingClient(client);
77
+ form.reset({
78
+ emails: client.emails,
79
+ greenInvoiceId: client.greenInvoiceId,
80
+ hiveId: client.hiveId,
81
+ generatedDocumentType: client.generatedDocumentType,
82
+ id: client.id,
83
+ });
84
+ setIsDialogOpen(true);
85
+ }
86
+ }, [client, form]);
87
+
88
+ const handleNew = () => {
89
+ setEditingClient(null);
90
+ form.reset(newClientDefaultValues);
91
+ setIsDialogOpen(true);
92
+ };
93
+
94
+ const addEmail = () => {
95
+ if (newEmail.trim()) {
96
+ const currentEmails = form.getValues('emails');
97
+ form.setValue('emails', [...(currentEmails ?? []), newEmail.trim()], { shouldDirty: true });
98
+ setNewEmail('');
99
+ }
100
+ };
101
+
102
+ const removeEmail = (index: number) => {
103
+ const currentEmails = form.getValues('emails');
104
+ form.setValue(
105
+ 'emails',
106
+ (currentEmails ?? []).filter((_, i) => i !== index),
107
+ { shouldDirty: true },
108
+ );
109
+ };
110
+
111
+ const onSubmit = useCallback(
112
+ async (values: ClientFormValues) => {
113
+ if (editingClient) {
114
+ console.log('[v0] Updating client:', editingClient.id, values);
115
+ updateClient({
116
+ businessId,
117
+ fields: values,
118
+ });
119
+ } else {
120
+ if (!values.greenInvoiceId) {
121
+ form.setError('greenInvoiceId', { message: 'Green Invoice ID is required' });
122
+ return;
123
+ }
124
+ console.log('[v0] Creating new client:', values);
125
+ insertClient({
126
+ fields: {
127
+ ...values,
128
+ greenInvoiceId: values.greenInvoiceId!,
129
+ businessId,
130
+ },
131
+ });
132
+ }
133
+ setIsDialogOpen(false);
134
+ setEditingClient(null);
135
+ onDone?.();
136
+ },
137
+ [editingClient, onDone, updateClient, businessId, insertClient, form],
138
+ );
139
+
140
+ return (
141
+ <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
142
+ <DialogTrigger asChild>
143
+ <Button size="sm" onClick={handleNew}>
144
+ <Plus className="h-4 w-4 mr-2" />
145
+ New Client
146
+ </Button>
147
+ </DialogTrigger>
148
+ <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
149
+ <DialogHeader>
150
+ <DialogTitle>{editingClient ? 'Edit Client' : 'Create New Client'}</DialogTitle>
151
+ <DialogDescription>
152
+ {editingClient ? 'Update client details' : 'Add a new client with all required details'}
153
+ </DialogDescription>
154
+ </DialogHeader>
155
+ <Form {...form}>
156
+ <form onSubmit={form.handleSubmit(onSubmit)}>
157
+ <div className="grid gap-4 py-4">
158
+ <div className="grid gap-4 md:grid-cols-2">
159
+ <FormField
160
+ control={form.control}
161
+ name="emails"
162
+ render={({ field }) => (
163
+ <FormItem>
164
+ <FormLabel>Emails</FormLabel>
165
+ <div className="flex gap-2">
166
+ <Input
167
+ placeholder="Add email..."
168
+ value={newEmail}
169
+ onChange={e => setNewEmail(e.target.value)}
170
+ onKeyDown={e => {
171
+ if (e.key === 'Enter') {
172
+ e.preventDefault();
173
+ addEmail();
174
+ }
175
+ }}
176
+ />
177
+ <Button type="button" size="sm" onClick={addEmail}>
178
+ <Plus className="h-4 w-4" />
179
+ </Button>
180
+ </div>
181
+ <div className="flex flex-wrap gap-2">
182
+ {field.value?.map((email, index) => (
183
+ <Badge key={index} variant="secondary" className="gap-1">
184
+ {email}
185
+ <Button
186
+ variant="ghost"
187
+ size="icon"
188
+ className="p-0 size-3"
189
+ onClick={() => removeEmail(index)}
190
+ >
191
+ <X className="size-3 cursor-pointer" />
192
+ </Button>
193
+ </Badge>
194
+ ))}
195
+ </div>
196
+ <FormMessage />
197
+ </FormItem>
198
+ )}
199
+ />
200
+
201
+ <FormField
202
+ control={form.control}
203
+ name="generatedDocumentType"
204
+ render={({ field }) => (
205
+ <FormItem>
206
+ <FormLabel>Default Document Type</FormLabel>
207
+ <Select onValueChange={field.onChange} defaultValue={field.value}>
208
+ <FormControl>
209
+ <SelectTrigger>
210
+ <SelectValue />
211
+ </SelectTrigger>
212
+ </FormControl>
213
+ <SelectContent>
214
+ {Object.values(DocumentType).map(type => (
215
+ <SelectItem key={type} value={type}>
216
+ {getDocumentNameFromType(type)}
217
+ </SelectItem>
218
+ ))}
219
+ </SelectContent>
220
+ </Select>
221
+ <FormMessage />
222
+ </FormItem>
223
+ )}
224
+ />
225
+
226
+ <FormField
227
+ control={form.control}
228
+ name="greenInvoiceId"
229
+ render={({ field }) => (
230
+ <FormItem>
231
+ <FormLabel>Green Invoice ID</FormLabel>
232
+ <FormControl>
233
+ <Input
234
+ type="text"
235
+ placeholder="Enter Green Invoice ID"
236
+ {...field}
237
+ required={!editingClient}
238
+ />
239
+ </FormControl>
240
+ <FormMessage />
241
+ </FormItem>
242
+ )}
243
+ />
244
+
245
+ <FormField
246
+ control={form.control}
247
+ name="hiveId"
248
+ render={({ field }) => (
249
+ <FormItem>
250
+ <FormLabel>Hive ID</FormLabel>
251
+ <FormControl>
252
+ <Input
253
+ type="text"
254
+ placeholder="Enter Hive ID"
255
+ {...field}
256
+ required={!editingClient}
257
+ />
258
+ </FormControl>
259
+ <FormMessage />
260
+ </FormItem>
261
+ )}
262
+ />
263
+ </div>
264
+ </div>
265
+ <DialogFooter>
266
+ <Button type="button" variant="outline" onClick={() => setIsDialogOpen(false)}>
267
+ Cancel
268
+ </Button>
269
+ <Button type="submit">{editingClient ? 'Update Client' : 'Create Client'}</Button>
270
+ </DialogFooter>
271
+ </form>
272
+ </Form>
273
+ </DialogContent>
274
+ </Dialog>
275
+ );
276
+ }
@@ -20,7 +20,7 @@ import {
20
20
  } from '../../forms/index.js';
21
21
  import { IssueDocumentModal, type IssueDocumentData } from './issue-document-modal.js';
22
22
  import { PdfViewer } from './pdf-viewer.js';
23
- import { RecentClientDocs } from './recent-client-docs.js';
23
+ import { RecentBusinessDocs } from './recent-business-docs.js';
24
24
  import { RecentDocsOfSameType } from './recent-docs-of-same-type.js';
25
25
 
26
26
  interface GenerateDocumentProps {
@@ -235,8 +235,8 @@ export function GenerateDocument({
235
235
 
236
236
  {/* Previous client documents */}
237
237
  {formData.client?.id && (
238
- <RecentClientDocs
239
- clientId={formData.client?.id}
238
+ <RecentBusinessDocs
239
+ businessId={formData.client?.id}
240
240
  linkedDocumentIds={formData.linkedDocumentIds ?? []}
241
241
  />
242
242
  )}
@@ -4,12 +4,12 @@ import { useMemo } from 'react';
4
4
  import { useQuery } from 'urql';
5
5
  import { flexRender, getCoreRowModel, useReactTable, type ColumnDef } from '@tanstack/react-table';
6
6
  import {
7
- RecentClientIssuedDocumentsDocument,
7
+ RecentBusinessIssuedDocumentsDocument,
8
8
  TableDocumentsRowFieldsFragmentDoc,
9
9
  } from '../../../../gql/graphql.js';
10
10
  import { getFragmentData } from '../../../../gql/index.js';
11
- import { columns, type DocumentsTableRowType } from '../../../documents-table/columns.js';
12
- import { Card, CardContent, CardHeader, CardTitle } from '../../../ui/card.js';
11
+ import { columns, type DocumentsTableRowType } from '../../../documents-table/columns.jsx';
12
+ import { Card, CardContent, CardHeader, CardTitle } from '../../../ui/card.jsx';
13
13
  import {
14
14
  Table,
15
15
  TableBody,
@@ -17,12 +17,12 @@ import {
17
17
  TableHead,
18
18
  TableHeader,
19
19
  TableRow,
20
- } from '../../../ui/table.js';
20
+ } from '../../../ui/table.jsx';
21
21
 
22
22
  // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
23
23
  /* GraphQL */ `
24
- query RecentClientIssuedDocuments($clientId: UUID!) {
25
- recentDocumentsByClient(clientId: $clientId) {
24
+ query RecentBusinessIssuedDocuments($businessId: UUID!, $limit: Int) {
25
+ recentDocumentsByBusiness(businessId: $businessId, limit: $limit) {
26
26
  id
27
27
  ... on FinancialDocument {
28
28
  issuedDocumentInfo {
@@ -42,25 +42,31 @@ type RowType = DocumentsTableRowType & {
42
42
  };
43
43
  };
44
44
 
45
- interface RecentClientDocsProps {
46
- clientId: string;
45
+ interface RecentBusinessDocsProps {
46
+ businessId: string;
47
47
  linkedDocumentIds: string[];
48
+ limit?: number;
48
49
  }
49
50
 
50
- export function RecentClientDocs({ clientId, linkedDocumentIds }: RecentClientDocsProps) {
51
+ export function RecentBusinessDocs({
52
+ businessId,
53
+ linkedDocumentIds,
54
+ limit,
55
+ }: RecentBusinessDocsProps) {
51
56
  const [{ data, fetching }] = useQuery({
52
- query: RecentClientIssuedDocumentsDocument,
57
+ query: RecentBusinessIssuedDocumentsDocument,
53
58
  variables: {
54
- clientId,
59
+ businessId,
60
+ limit,
55
61
  },
56
62
  });
57
63
 
58
64
  const rows = useMemo(
59
65
  (): RowType[] =>
60
- data?.recentDocumentsByClient?.map(
66
+ data?.recentDocumentsByBusiness?.map(
61
67
  rawDocument => getFragmentData(TableDocumentsRowFieldsFragmentDoc, rawDocument) as RowType,
62
68
  ) ?? [],
63
- [data?.recentDocumentsByClient],
69
+ [data?.recentDocumentsByBusiness],
64
70
  );
65
71
  const limitedColumns = ['date', 'amount', 'vat', 'type', 'serial', 'description', 'file'];
66
72
  const table = useReactTable<RowType>({
@@ -56,6 +56,7 @@ import { ModifyBusinessFields } from './modify-business-fields.js';
56
56
  }
57
57
  optionalVAT
58
58
  irsCode
59
+ pcn874RecordType
59
60
  }
60
61
  }
61
62
  }
@@ -1,12 +1,12 @@
1
1
  import { useEffect, useState, type ReactElement } from 'react';
2
2
  import type { UseFormReturn } from 'react-hook-form';
3
+ import { useAllCountries } from '@/hooks/use-get-countries.js';
3
4
  import {
4
5
  EmailAttachmentType,
5
6
  type InsertNewBusinessInput,
6
- type Pcn874RecordType,
7
7
  type UpdateBusinessInput,
8
8
  } from '../../../gql/graphql.js';
9
- import { dirtyFieldMarker } from '../../../helpers/index.js';
9
+ import { dirtyFieldMarker, pcn874RecordEnum } from '../../../helpers/index.js';
10
10
  import { useGetSortCodes } from '../../../hooks/use-get-sort-codes.js';
11
11
  import { useGetTaxCategories } from '../../../hooks/use-get-tax-categories.js';
12
12
  import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '../../ui/form.js';
@@ -15,22 +15,6 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '.
15
15
  import { Switch } from '../../ui/switch.js';
16
16
  import { ComboBox, MultiSelect, NumberInput, StringArrayInput, TagsInput } from '../index.js';
17
17
 
18
- const pcn874RecordType: Record<Pcn874RecordType, string> = {
19
- C: 'INPUT_SELF_INVOICE',
20
- H: 'INPUT_SINGLE_DOC_BY_LAW',
21
- I: 'SALE_PALESTINIAN_CUSTOMER',
22
- K: 'INPUT_PETTY_CASH',
23
- L1: 'SALE_UNIDENTIFIED_CUSTOMER',
24
- L2: 'SALE_UNIDENTIFIED_ZERO_OR_EXEMPT',
25
- M: 'SALE_SELF_INVOICE',
26
- P: 'INPUT_PALESTINIAN_SUPPLIER',
27
- R: 'INPUT_IMPORT',
28
- S1: 'SALE_REGULAR',
29
- S2: 'SALE_ZERO_OR_EXEMPT',
30
- T: 'INPUT_REGULAR',
31
- Y: 'SALE_EXPORT',
32
- } as const;
33
-
34
18
  type ModalProps<T extends boolean> = {
35
19
  isInsert: T;
36
20
  formManager: UseFormReturn<
@@ -60,9 +44,12 @@ export function ModifyBusinessFields({
60
44
  sortCodes: rawSortCodes,
61
45
  } = useGetSortCodes();
62
46
 
47
+ // Countries handle
48
+ const { countries, fetching: fetchingCountries } = useAllCountries();
49
+
63
50
  useEffect(() => {
64
- setFetching(tagsFetching || fetchingTaxCategories || fetchingSortCodes);
65
- }, [setFetching, tagsFetching, fetchingTaxCategories, fetchingSortCodes]);
51
+ setFetching(tagsFetching || fetchingTaxCategories || fetchingSortCodes || fetchingCountries);
52
+ }, [setFetching, tagsFetching, fetchingTaxCategories, fetchingSortCodes, fetchingCountries]);
66
53
 
67
54
  // on sort code change, update IRS code
68
55
  const sortCode = watch('sortCode');
@@ -138,21 +125,16 @@ export function ModifyBusinessFields({
138
125
  <FormLabel>Locality</FormLabel>
139
126
  <Select required onValueChange={field.onChange} value={field.value ?? undefined}>
140
127
  <FormControl>
141
- <SelectTrigger className="w-full truncate">
142
- <SelectValue placeholder="Select type" />
128
+ <SelectTrigger
129
+ className={(isInsert ? '' : dirtyFieldMarker(fieldState)) + ' w-full truncate'}
130
+ >
131
+ <SelectValue placeholder="Select Country" />
143
132
  </SelectTrigger>
144
133
  </FormControl>
145
134
  <SelectContent onClick={event => event.stopPropagation()}>
146
- {[
147
- { value: 'Israel', label: 'Local' },
148
- { value: 'FOREIGN', label: 'Foreign' },
149
- ].map(({ value, label }) => (
150
- <SelectItem
151
- key={value}
152
- value={value}
153
- className={isInsert ? '' : dirtyFieldMarker(fieldState)}
154
- >
155
- {label}
135
+ {countries.map(({ code, name }) => (
136
+ <SelectItem key={code} value={code}>
137
+ {name}
156
138
  </SelectItem>
157
139
  ))}
158
140
  </SelectContent>
@@ -328,7 +310,7 @@ export function ModifyBusinessFields({
328
310
  </SelectTrigger>
329
311
  </FormControl>
330
312
  <SelectContent onClick={event => event.stopPropagation()}>
331
- {Object.entries(pcn874RecordType).map(([value, label]) => (
313
+ {Object.entries(pcn874RecordEnum).map(([value, label]) => (
332
314
  <SelectItem key={value} value={value}>
333
315
  {`${label} (${value})`}
334
316
  </SelectItem>
@@ -50,7 +50,7 @@ export function ComboBox({
50
50
  if (isDesktop) {
51
51
  return (
52
52
  <Popover modal open={open} onOpenChange={setOpen}>
53
- <PopoverTrigger asChild>
53
+ <PopoverTrigger asChild className="w-fit min-w-40">
54
54
  <Trigger
55
55
  placeholder={placeholder}
56
56
  selectedDatum={selectedDatum}
@@ -1,5 +1,6 @@
1
- import { useState, type ReactElement } from 'react';
1
+ import { useContext, useState, type ReactElement } from 'react';
2
2
  import { useForm, type SubmitHandler } from 'react-hook-form';
3
+ import { UserContext } from '@/providers/user-provider.js';
3
4
  import type { InsertNewBusinessInput } from '../../../gql/graphql.js';
4
5
  import { useInsertBusiness } from '../../../hooks/use-insert-business.js';
5
6
  import { Button } from '../../ui/button.js';
@@ -44,10 +45,11 @@ type CreateBusinessFormProps = {
44
45
  };
45
46
 
46
47
  function CreateBusinessForm({ description, close, onAdd }: CreateBusinessFormProps): ReactElement {
48
+ const { userContext } = useContext(UserContext);
47
49
  const formManager = useForm<InsertNewBusinessInput>({
48
50
  defaultValues: {
49
51
  name: description,
50
- country: 'Israel',
52
+ country: userContext?.context.locality || 'ISR',
51
53
  suggestions: { phrases: [description] },
52
54
  },
53
55
  });
@@ -1,7 +1,7 @@
1
1
  import type { ReactElement } from 'react';
2
2
  import { Text } from '@mantine/core';
3
3
  import { Currency } from '../../../gql/graphql.js';
4
- import { currencyCodeToSymbol, formatStringifyAmount } from '../../../helpers/index.js';
4
+ import { formatAmountWithCurrency } from '../../../helpers/index.js';
5
5
  import { TrialBalanceReportFilters } from './trial-balance-report-filters.js';
6
6
  import {
7
7
  TrialBalanceReportSortCode,
@@ -44,16 +44,14 @@ export const TrialBalanceReportGroup = ({
44
44
  <td colSpan={2}>Group total:</td>
45
45
  <td colSpan={1}>{group.replaceAll('0', '*')}</td>
46
46
  <td colSpan={1}>
47
- {!!data.totalDebit &&
48
- `${currencyCodeToSymbol(Currency.Ils)} ${formatStringifyAmount(data.totalDebit)}`}
47
+ {!!data.totalDebit && formatAmountWithCurrency(data.totalDebit, Currency.Ils)}
49
48
  </td>
50
49
  <td colSpan={1}>
51
- {!!data.totalCredit &&
52
- `${currencyCodeToSymbol(Currency.Ils)} ${formatStringifyAmount(data.totalCredit)}`}
50
+ {!!data.totalCredit && formatAmountWithCurrency(data.totalCredit, Currency.Ils)}
53
51
  </td>
54
52
  <td colSpan={1}>
55
53
  <Text fw={700} c={data.sum > 0 ? 'green' : data.sum < 0 ? 'red' : undefined}>
56
- {`${currencyCodeToSymbol(Currency.Ils)} ${formatStringifyAmount(data.sum)}`}
54
+ {formatAmountWithCurrency(data.sum, Currency.Ils)}
57
55
  </Text>
58
56
  </td>
59
57
  </tr>
@@ -1,7 +1,7 @@
1
1
  import type { ReactElement } from 'react';
2
2
  import { Text } from '@mantine/core';
3
3
  import { Currency } from '../../../gql/graphql.js';
4
- import { currencyCodeToSymbol, formatStringifyAmount } from '../../../helpers/index.js';
4
+ import { formatAmountWithCurrency } from '../../../helpers/index.js';
5
5
  import {
6
6
  TrialBalanceReportBusiness,
7
7
  type ExtendedBusiness,
@@ -55,29 +55,26 @@ export const TrialBalanceReportSortCode = ({
55
55
  <td colSpan={2}>Group total:</td>
56
56
  <td colSpan={1}>{sortCode.key}</td>
57
57
  <td colSpan={1}>
58
- {!!sortCode.totalDebit &&
59
- `${currencyCodeToSymbol(Currency.Ils)} ${formatStringifyAmount(sortCode.totalDebit)}`}
58
+ {!!sortCode.totalDebit && formatAmountWithCurrency(sortCode.totalDebit, Currency.Ils)}
60
59
  </td>
61
60
  <td colSpan={1}>
62
61
  {!!sortCode.totalCredit &&
63
- `${currencyCodeToSymbol(Currency.Ils)} ${formatStringifyAmount(sortCode.totalCredit)}`}
62
+ formatAmountWithCurrency(sortCode.totalCredit, Currency.Ils)}
64
63
  </td>
65
64
  <td colSpan={1}>
66
- <Text
67
- c={sortCode.sum > 0 ? 'green' : sortCode.sum < 0 ? 'red' : undefined}
68
- >{`${currencyCodeToSymbol(Currency.Ils)} ${formatStringifyAmount(sortCode.sum)}`}</Text>
65
+ <Text c={sortCode.sum > 0 ? 'green' : sortCode.sum < 0 ? 'red' : undefined}>
66
+ {formatAmountWithCurrency(sortCode.sum, Currency.Ils)}
67
+ </Text>
69
68
  {!!sortCode.debit && (
70
69
  <>
71
70
  <br />
72
- Total Debit Balances:{' '}
73
- {`${currencyCodeToSymbol(Currency.Ils)} ${formatStringifyAmount(sortCode.debit)}`}
71
+ Total Debit Balances: {formatAmountWithCurrency(sortCode.debit, Currency.Ils)}
74
72
  </>
75
73
  )}
76
74
  {!!sortCode.credit && (
77
75
  <>
78
76
  <br />
79
- Total Credit Balances:{' '}
80
- {`${currencyCodeToSymbol(Currency.Ils)} ${formatStringifyAmount(sortCode.credit)}`}
77
+ Total Credit Balances: {formatAmountWithCurrency(sortCode.credit, Currency.Ils)}
81
78
  </>
82
79
  )}
83
80
  </td>
@@ -0,0 +1,50 @@
1
+ import { useEffect, type ReactElement } from 'react';
2
+ import { useMatch } from 'react-router-dom';
3
+ import { useQuery } from 'urql';
4
+ import Business from '@/components/business/index.js';
5
+ import { BusinessScreenDocument } from '../../../gql/graphql.js';
6
+ import { AccounterLoader } from '../../common/index.js';
7
+
8
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
9
+ /* GraphQL */ `
10
+ query BusinessScreen($businessId: UUID!) {
11
+ business(id: $businessId) {
12
+ id
13
+ ...BusinessPage
14
+ }
15
+ }
16
+ `;
17
+
18
+ export function getBusinessHref(businessId: string): string {
19
+ return `/businesses/${businessId}`;
20
+ }
21
+
22
+ export const BusinessScreen = (): ReactElement => {
23
+ const match = useMatch('/businesses/:businessId');
24
+
25
+ const businessId = match?.params.businessId;
26
+
27
+ const [{ data, fetching }, fetchBusiness] = useQuery({
28
+ query: BusinessScreenDocument,
29
+ pause: !businessId,
30
+ variables: {
31
+ businessId: businessId ?? '',
32
+ },
33
+ });
34
+
35
+ useEffect(() => {
36
+ if (businessId) {
37
+ fetchBusiness();
38
+ }
39
+ }, [businessId, fetchBusiness]);
40
+
41
+ if (fetching && !data?.business) {
42
+ return <AccounterLoader />;
43
+ }
44
+
45
+ if (!businessId || !data?.business) {
46
+ return <div>Business not found</div>;
47
+ }
48
+
49
+ return <Business data={data.business} refetchBusiness={async () => fetchBusiness()} />;
50
+ };
@@ -3,7 +3,7 @@ import { Check, Edit, Eye, FileText, Loader2 } from 'lucide-react';
3
3
  import type { NewDocumentInfoFragment } from '../../../../gql/graphql.js';
4
4
  import { usePreviewDocument } from '../../../../hooks/use-preview-document.js';
5
5
  import { PdfViewer } from '../../../common/documents/issue-document/pdf-viewer.js';
6
- import { RecentClientDocs } from '../../../common/documents/issue-document/recent-client-docs.js';
6
+ import { RecentBusinessDocs } from '../../../common/documents/issue-document/recent-business-docs.js';
7
7
  import { RecentDocsOfSameType } from '../../../common/documents/issue-document/recent-docs-of-same-type.js';
8
8
  import {
9
9
  convertNewDocumentInfoFragmentIntoPreviewDocumentInput,
@@ -201,10 +201,10 @@ export function EditIssueDocumentModal({ onApprove, draft }: Props): ReactElemen
201
201
  </CardContent>
202
202
  </Card>
203
203
 
204
- {/* Previous client documents */}
204
+ {/* Previous business documents */}
205
205
  {document.client?.id && (
206
- <RecentClientDocs
207
- clientId={document.client?.id}
206
+ <RecentBusinessDocs
207
+ businessId={document.client?.id}
208
208
  linkedDocumentIds={document.linkedDocumentIds ?? []}
209
209
  />
210
210
  )}
@@ -0,0 +1,25 @@
1
+ import { forwardRef, type ComponentPropsWithoutRef, type ComponentRef } from 'react';
2
+ import { cn } from '@/lib/utils.js';
3
+ import * as ProgressPrimitive from '@radix-ui/react-progress';
4
+
5
+ const Progress = forwardRef<
6
+ ComponentRef<typeof ProgressPrimitive.Root>,
7
+ ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
8
+ >(({ className, value, ...props }, ref) => (
9
+ <ProgressPrimitive.Root
10
+ ref={ref}
11
+ className={cn(
12
+ 'relative h-2 w-full overflow-hidden rounded-full bg-gray-900/20 dark:bg-gray-50/20',
13
+ className,
14
+ )}
15
+ {...props}
16
+ >
17
+ <ProgressPrimitive.Indicator
18
+ className="h-full w-full flex-1 bg-gray-900 transition-all dark:bg-gray-50"
19
+ style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
20
+ />
21
+ </ProgressPrimitive.Root>
22
+ ));
23
+ Progress.displayName = ProgressPrimitive.Root.displayName;
24
+
25
+ export { Progress };