@accounter/client 0.0.8-alpha-20251021150553-5fe662174014d6173ebcea8412cf0fa294abbed2 → 0.0.8-alpha-20251021163440-2ab1a9ffaec95fd99fac5495c3a392b97429ce77

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 (56) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/dist/assets/index-B2UYAO1O.css +1 -0
  3. package/dist/assets/index-BexxGuN6.js +1224 -0
  4. package/dist/assets/{index.es-BaJmdn-u.js → index.es-CWwhWGxX.js} +1 -1
  5. package/dist/index.html +2 -2
  6. package/package.json +2 -1
  7. package/src/app.tsx +35 -25
  8. package/src/components/business/business-header.tsx +68 -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 +885 -0
  12. package/src/components/business/contact-info-section.tsx +536 -0
  13. package/src/components/business/contracts-section.tsx +196 -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 +477 -0
  17. package/src/components/business/transactions-section.tsx +26 -0
  18. package/src/components/business-transactions/business-extended-info.tsx +11 -15
  19. package/src/components/business-transactions/business-transactions-single.tsx +1 -1
  20. package/src/components/business-transactions/index.tsx +1 -1
  21. package/src/components/charges/charge-extended-info-menu.tsx +27 -21
  22. package/src/components/charges/charges-row.tsx +12 -10
  23. package/src/components/charges/charges-table.tsx +15 -9
  24. package/src/components/clients/contracts/modify-contract-dialog.tsx +464 -0
  25. package/src/components/clients/modify-client-dialog.tsx +276 -0
  26. package/src/components/common/documents/issue-document/index.tsx +3 -3
  27. package/src/components/common/documents/issue-document/{recent-client-docs.tsx → recent-business-docs.tsx} +19 -13
  28. package/src/components/common/forms/business-card.tsx +1 -0
  29. package/src/components/common/forms/modify-business-fields.tsx +2 -19
  30. package/src/components/common/inputs/combo-box.tsx +1 -1
  31. package/src/components/layout/sidelinks.tsx +3 -3
  32. package/src/components/reports/trial-balance-report/trial-balance-report-group.tsx +4 -6
  33. package/src/components/reports/trial-balance-report/trial-balance-report-sort-code.tsx +8 -11
  34. package/src/components/screens/businesses/business.tsx +44 -0
  35. package/src/components/screens/documents/issue-documents/edit-issue-document-modal.tsx +4 -4
  36. package/src/components/ui/progress.tsx +25 -0
  37. package/src/components/ui/skeleton.tsx +12 -0
  38. package/src/gql/gql.ts +93 -9
  39. package/src/gql/graphql.ts +300 -9
  40. package/src/helpers/contracts.ts +22 -0
  41. package/src/helpers/currency.ts +5 -0
  42. package/src/helpers/index.ts +2 -0
  43. package/src/helpers/pcn874.ts +17 -0
  44. package/src/hooks/use-add-sort-code.ts +1 -1
  45. package/src/hooks/use-add-tag.ts +1 -1
  46. package/src/hooks/use-create-contract.ts +62 -0
  47. package/src/hooks/use-delete-contract.ts +64 -0
  48. package/src/hooks/use-delete-tag.ts +1 -1
  49. package/src/hooks/use-get-all-contracts.ts +0 -1
  50. package/src/hooks/use-insert-client.ts +80 -0
  51. package/src/hooks/use-merge-businesses.ts +1 -1
  52. package/src/hooks/use-merge-charges.ts +1 -1
  53. package/src/hooks/use-update-client.ts +75 -0
  54. package/src/hooks/use-update-contract.ts +69 -0
  55. package/dist/assets/index-CqaS5jWM.css +0 -1
  56. package/dist/assets/index-IlfO2QvT.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
+ // Handle client update
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
+ // Handle new client creation
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
  }
@@ -4,10 +4,9 @@ import { useAllCountries } from '@/hooks/use-get-countries.js';
4
4
  import {
5
5
  EmailAttachmentType,
6
6
  type InsertNewBusinessInput,
7
- type Pcn874RecordType,
8
7
  type UpdateBusinessInput,
9
8
  } from '../../../gql/graphql.js';
10
- import { dirtyFieldMarker } from '../../../helpers/index.js';
9
+ import { dirtyFieldMarker, pcn874RecordEnum } from '../../../helpers/index.js';
11
10
  import { useGetSortCodes } from '../../../hooks/use-get-sort-codes.js';
12
11
  import { useGetTaxCategories } from '../../../hooks/use-get-tax-categories.js';
13
12
  import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '../../ui/form.js';
@@ -16,22 +15,6 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '.
16
15
  import { Switch } from '../../ui/switch.js';
17
16
  import { ComboBox, MultiSelect, NumberInput, StringArrayInput, TagsInput } from '../index.js';
18
17
 
19
- const pcn874RecordType: Record<Pcn874RecordType, string> = {
20
- C: 'INPUT_SELF_INVOICE',
21
- H: 'INPUT_SINGLE_DOC_BY_LAW',
22
- I: 'SALE_PALESTINIAN_CUSTOMER',
23
- K: 'INPUT_PETTY_CASH',
24
- L1: 'SALE_UNIDENTIFIED_CUSTOMER',
25
- L2: 'SALE_UNIDENTIFIED_ZERO_OR_EXEMPT',
26
- M: 'SALE_SELF_INVOICE',
27
- P: 'INPUT_PALESTINIAN_SUPPLIER',
28
- R: 'INPUT_IMPORT',
29
- S1: 'SALE_REGULAR',
30
- S2: 'SALE_ZERO_OR_EXEMPT',
31
- T: 'INPUT_REGULAR',
32
- Y: 'SALE_EXPORT',
33
- } as const;
34
-
35
18
  type ModalProps<T extends boolean> = {
36
19
  isInsert: T;
37
20
  formManager: UseFormReturn<
@@ -327,7 +310,7 @@ export function ModifyBusinessFields({
327
310
  </SelectTrigger>
328
311
  </FormControl>
329
312
  <SelectContent onClick={event => event.stopPropagation()}>
330
- {Object.entries(pcn874RecordType).map(([value, label]) => (
313
+ {Object.entries(pcn874RecordEnum).map(([value, label]) => (
331
314
  <SelectItem key={value} value={value}>
332
315
  {`${label} (${value})`}
333
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}
@@ -55,13 +55,13 @@ export const sidelinks: SideLink[] = [
55
55
  {
56
56
  title: 'Missing Info Charges',
57
57
  label: '',
58
- href: '/missing-info-charges',
58
+ href: '/charges/missing-info',
59
59
  icon: <RectangleEllipsis size={18} />,
60
60
  },
61
61
  {
62
62
  title: 'Ledger Validation',
63
63
  label: '',
64
- href: '/charges-ledger-validation',
64
+ href: '/charges/ledger-validation',
65
65
  icon: <BookOpenCheck size={18} />,
66
66
  },
67
67
  ],
@@ -166,7 +166,7 @@ export const sidelinks: SideLink[] = [
166
166
  {
167
167
  title: 'Business Transactions',
168
168
  label: '',
169
- href: '/business-transactions',
169
+ href: '/businesses/transactions',
170
170
  icon: <ArrowLeftRight size={18} />,
171
171
  },
172
172
  {
@@ -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,44 @@
1
+ import { 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
+ if (fetching && !data?.business) {
36
+ return <AccounterLoader />;
37
+ }
38
+
39
+ if (!businessId || !data?.business) {
40
+ return <div>Business not found</div>;
41
+ }
42
+
43
+ return <Business data={data.business} refetchBusiness={async () => fetchBusiness()} />;
44
+ };
@@ -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 };
@@ -0,0 +1,12 @@
1
+ import { cn } from '@/lib/utils.js';
2
+
3
+ function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
4
+ return (
5
+ <div
6
+ className={cn('animate-pulse rounded-md bg-gray-900/10 dark:bg-gray-50/10', className)}
7
+ {...props}
8
+ />
9
+ );
10
+ }
11
+
12
+ export { Skeleton };