@hed-hog/finance 0.0.299 → 0.0.301
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dto/create-bank-account.dto.d.ts +1 -0
- package/dist/dto/create-bank-account.dto.d.ts.map +1 -1
- package/dist/dto/create-bank-account.dto.js +7 -0
- package/dist/dto/create-bank-account.dto.js.map +1 -1
- package/dist/dto/update-bank-account.dto.d.ts +1 -0
- package/dist/dto/update-bank-account.dto.d.ts.map +1 -1
- package/dist/dto/update-bank-account.dto.js +7 -0
- package/dist/dto/update-bank-account.dto.js.map +1 -1
- package/dist/finance-bank-accounts.controller.d.ts +3 -0
- package/dist/finance-bank-accounts.controller.d.ts.map +1 -1
- package/dist/finance-data.controller.d.ts +1 -0
- package/dist/finance-data.controller.d.ts.map +1 -1
- package/dist/finance.contract-activated.subscriber.d.ts +24 -0
- package/dist/finance.contract-activated.subscriber.d.ts.map +1 -0
- package/dist/finance.contract-activated.subscriber.js +519 -0
- package/dist/finance.contract-activated.subscriber.js.map +1 -0
- package/dist/finance.contract-activated.subscriber.spec.d.ts +2 -0
- package/dist/finance.contract-activated.subscriber.spec.d.ts.map +1 -0
- package/dist/finance.contract-activated.subscriber.spec.js +302 -0
- package/dist/finance.contract-activated.subscriber.spec.js.map +1 -0
- package/dist/finance.module.d.ts.map +1 -1
- package/dist/finance.module.js +6 -1
- package/dist/finance.module.js.map +1 -1
- package/dist/finance.service.d.ts +4 -0
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +5 -0
- package/dist/finance.service.js.map +1 -1
- package/hedhog/data/dashboard.yaml +6 -0
- package/hedhog/data/dashboard_component.yaml +72 -17
- package/hedhog/data/dashboard_component_role.yaml +30 -0
- package/hedhog/data/dashboard_item.yaml +155 -0
- package/hedhog/data/dashboard_role.yaml +6 -0
- package/hedhog/data/role_menu.yaml +6 -0
- package/hedhog/data/role_route.yaml +127 -0
- package/hedhog/frontend/app/_components/finance-layout.tsx.ejs +108 -0
- package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +91 -106
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +1306 -1145
- package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +288 -268
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +491 -351
- package/hedhog/frontend/app/administration/audit-logs/page.tsx.ejs +157 -173
- package/hedhog/frontend/app/administration/categories/page.tsx.ejs +44 -62
- package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +62 -80
- package/hedhog/frontend/app/administration/period-close/page.tsx.ejs +151 -170
- package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +586 -224
- package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +204 -226
- package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +122 -140
- package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +32 -49
- package/hedhog/frontend/app/planning/cash-flow-forecast/page.tsx.ejs +84 -108
- package/hedhog/frontend/app/planning/receivables-calendar/page.tsx.ejs +53 -70
- package/hedhog/frontend/app/planning/scenarios/page.tsx.ejs +98 -95
- package/hedhog/frontend/app/reports/actual-vs-forecast/page.tsx.ejs +100 -125
- package/hedhog/frontend/app/reports/aging-default/page.tsx.ejs +77 -105
- package/hedhog/frontend/app/reports/cash-position/page.tsx.ejs +99 -134
- package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +147 -182
- package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +49 -61
- package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +49 -67
- package/hedhog/frontend/messages/en.json +224 -70
- package/hedhog/frontend/messages/pt.json +224 -70
- package/hedhog/frontend/widgets/alerts.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/bank-reconciliation-status.tsx.ejs +142 -0
- package/hedhog/frontend/widgets/cash-balance-kpi.tsx.ejs +9 -9
- package/hedhog/frontend/widgets/cash-flow-chart.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/default-kpi.tsx.ejs +9 -9
- package/hedhog/frontend/widgets/payable-30d-kpi.tsx.ejs +9 -9
- package/hedhog/frontend/widgets/pending-approvals-kpi.tsx.ejs +78 -0
- package/hedhog/frontend/widgets/pending-approvals-list.tsx.ejs +147 -0
- package/hedhog/frontend/widgets/pending-reconciliation-kpi.tsx.ejs +84 -0
- package/hedhog/frontend/widgets/receivable-30d-kpi.tsx.ejs +9 -9
- package/hedhog/frontend/widgets/receivable-aging-analysis.tsx.ejs +163 -0
- package/hedhog/frontend/widgets/upcoming-payable.tsx.ejs +1 -1
- package/hedhog/frontend/widgets/upcoming-receivable.tsx.ejs +1 -1
- package/hedhog/table/bank_account.yaml +8 -0
- package/package.json +7 -6
- package/src/dto/create-bank-account.dto.ts +7 -1
- package/src/dto/update-bank-account.dto.ts +7 -1
- package/src/finance.contract-activated.subscriber.spec.ts +392 -0
- package/src/finance.contract-activated.subscriber.ts +780 -0
- package/src/finance.module.ts +6 -1
- package/src/finance.service.ts +4 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Page, PageHeader } from '@/components/entity-list';
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { EmptyState, Page, PageHeader } from '@/components/entity-list';
|
|
4
4
|
import {
|
|
5
5
|
AlertDialog,
|
|
6
6
|
AlertDialogAction,
|
|
@@ -13,13 +13,14 @@ import {
|
|
|
13
13
|
} from '@/components/ui/alert-dialog';
|
|
14
14
|
import { Badge } from '@/components/ui/badge';
|
|
15
15
|
import { Button } from '@/components/ui/button';
|
|
16
|
-
import {
|
|
17
|
-
Card,
|
|
18
|
-
CardContent,
|
|
19
|
-
CardDescription,
|
|
20
|
-
CardHeader,
|
|
21
|
-
CardTitle,
|
|
22
|
-
} from '@/components/ui/card';
|
|
16
|
+
import {
|
|
17
|
+
Card,
|
|
18
|
+
CardContent,
|
|
19
|
+
CardDescription,
|
|
20
|
+
CardHeader,
|
|
21
|
+
CardTitle,
|
|
22
|
+
} from '@/components/ui/card';
|
|
23
|
+
import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
|
|
23
24
|
import {
|
|
24
25
|
Form,
|
|
25
26
|
FormControl,
|
|
@@ -38,13 +39,18 @@ import {
|
|
|
38
39
|
SelectTrigger,
|
|
39
40
|
SelectValue,
|
|
40
41
|
} from '@/components/ui/select';
|
|
41
|
-
import {
|
|
42
|
-
Sheet,
|
|
43
|
-
SheetContent,
|
|
44
|
-
SheetDescription,
|
|
45
|
-
SheetHeader,
|
|
46
|
-
SheetTitle,
|
|
47
|
-
} from '@/components/ui/sheet';
|
|
42
|
+
import {
|
|
43
|
+
Sheet,
|
|
44
|
+
SheetContent,
|
|
45
|
+
SheetDescription,
|
|
46
|
+
SheetHeader,
|
|
47
|
+
SheetTitle,
|
|
48
|
+
} from '@/components/ui/sheet';
|
|
49
|
+
import {
|
|
50
|
+
FinancePageSection,
|
|
51
|
+
FinanceSheetBody,
|
|
52
|
+
FinanceSheetSection,
|
|
53
|
+
} from '../../_components/finance-layout';
|
|
48
54
|
import { useApp, useQuery } from '@hed-hog/next-app-provider';
|
|
49
55
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
50
56
|
import {
|
|
@@ -59,10 +65,11 @@ import {
|
|
|
59
65
|
TrendingUp,
|
|
60
66
|
Upload,
|
|
61
67
|
Wallet,
|
|
68
|
+
type LucideIcon,
|
|
62
69
|
} from 'lucide-react';
|
|
63
70
|
import { useTranslations } from 'next-intl';
|
|
64
71
|
import Link from 'next/link';
|
|
65
|
-
import { useEffect, useState } from 'react';
|
|
72
|
+
import { useEffect, useRef, useState, type ChangeEvent } from 'react';
|
|
66
73
|
import { useForm } from 'react-hook-form';
|
|
67
74
|
import { z } from 'zod';
|
|
68
75
|
|
|
@@ -72,6 +79,7 @@ const bankAccountFormSchema = z.object({
|
|
|
72
79
|
conta: z.string().optional(),
|
|
73
80
|
tipo: z.string().min(1, 'Tipo é obrigatório'),
|
|
74
81
|
descricao: z.string().optional(),
|
|
82
|
+
logoFileId: z.number().int().nullable().optional(),
|
|
75
83
|
saldoInicial: z.number().min(0, 'Saldo inicial inválido'),
|
|
76
84
|
});
|
|
77
85
|
|
|
@@ -85,11 +93,48 @@ type BankAccount = {
|
|
|
85
93
|
agencia: string;
|
|
86
94
|
conta: string;
|
|
87
95
|
tipo: 'corrente' | 'poupanca' | 'investimento' | 'caixa';
|
|
96
|
+
logoFileId: number | null;
|
|
88
97
|
saldoAtual: number;
|
|
89
98
|
saldoConciliado: number;
|
|
90
99
|
ativo: boolean;
|
|
91
100
|
};
|
|
92
101
|
|
|
102
|
+
type UploadedFilePayload = {
|
|
103
|
+
id?: number | string | null;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
function BankAccountLogo({
|
|
107
|
+
account,
|
|
108
|
+
icon: Icon,
|
|
109
|
+
}: {
|
|
110
|
+
account: BankAccount;
|
|
111
|
+
icon: LucideIcon;
|
|
112
|
+
}) {
|
|
113
|
+
const [hasImageError, setHasImageError] = useState(false);
|
|
114
|
+
const apiBaseUrl = String(process.env.NEXT_PUBLIC_API_BASE_URL || '');
|
|
115
|
+
const logoUrl =
|
|
116
|
+
account.logoFileId && account.logoFileId > 0
|
|
117
|
+
? `${apiBaseUrl}/file/open/${account.logoFileId}?v=${account.logoFileId}`
|
|
118
|
+
: null;
|
|
119
|
+
|
|
120
|
+
if (!logoUrl || hasImageError) {
|
|
121
|
+
return (
|
|
122
|
+
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-muted">
|
|
123
|
+
<Icon className="h-5 w-5" />
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<img
|
|
130
|
+
src={logoUrl}
|
|
131
|
+
alt={account.banco}
|
|
132
|
+
className="h-10 w-10 rounded-lg border bg-muted object-cover"
|
|
133
|
+
onError={() => setHasImageError(true)}
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
93
138
|
function NovaContaSheet({
|
|
94
139
|
t,
|
|
95
140
|
onCreated,
|
|
@@ -119,6 +164,21 @@ function NovaContaSheet({
|
|
|
119
164
|
const updateErrorMessage = t.has('messages.updateError')
|
|
120
165
|
? t('messages.updateError')
|
|
121
166
|
: 'Erro ao atualizar conta bancária';
|
|
167
|
+
const logoUploadSuccessMessage = t.has('messages.logoUploadSuccess')
|
|
168
|
+
? t('messages.logoUploadSuccess')
|
|
169
|
+
: 'Logo enviado com sucesso';
|
|
170
|
+
const logoUploadErrorMessage = t.has('messages.logoUploadError')
|
|
171
|
+
? t('messages.logoUploadError')
|
|
172
|
+
: 'Erro ao enviar logo';
|
|
173
|
+
const logoRemoveSuccessMessage = t.has('messages.logoRemoveSuccess')
|
|
174
|
+
? t('messages.logoRemoveSuccess')
|
|
175
|
+
: 'Logo removido com sucesso';
|
|
176
|
+
const logoInvalidTypeMessage = t.has('messages.logoInvalidType')
|
|
177
|
+
? t('messages.logoInvalidType')
|
|
178
|
+
: 'Selecione um arquivo de imagem válido';
|
|
179
|
+
const logoTooLargeMessage = t.has('messages.logoTooLarge')
|
|
180
|
+
? t('messages.logoTooLarge')
|
|
181
|
+
: 'O logo deve ter no máximo 5 MB';
|
|
122
182
|
|
|
123
183
|
const form = useForm<BankAccountFormValues>({
|
|
124
184
|
resolver: zodResolver(bankAccountFormSchema),
|
|
@@ -128,38 +188,204 @@ function NovaContaSheet({
|
|
|
128
188
|
conta: '',
|
|
129
189
|
tipo: '',
|
|
130
190
|
descricao: '',
|
|
191
|
+
logoFileId: null,
|
|
131
192
|
saldoInicial: 0,
|
|
132
193
|
},
|
|
133
194
|
});
|
|
134
195
|
|
|
196
|
+
const [logoFileId, setLogoFileId] = useState<number | null>(null);
|
|
197
|
+
const [persistedLogoFileId, setPersistedLogoFileId] = useState<number | null>(
|
|
198
|
+
null
|
|
199
|
+
);
|
|
200
|
+
const [logoPreviewUrl, setLogoPreviewUrl] = useState<string | null>(null);
|
|
201
|
+
const [isUploadingLogo, setIsUploadingLogo] = useState(false);
|
|
202
|
+
const [logoUploadProgress, setLogoUploadProgress] = useState(0);
|
|
203
|
+
const logoInputRef = useRef<HTMLInputElement | null>(null);
|
|
204
|
+
|
|
205
|
+
const getLogoUrl = (fileId?: number | null) => {
|
|
206
|
+
if (typeof fileId !== 'number' || fileId <= 0) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return `${String(process.env.NEXT_PUBLIC_API_BASE_URL || '')}/file/open/${fileId}`;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const deleteFileById = async (fileId?: number | null) => {
|
|
214
|
+
if (!fileId || fileId <= 0) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
await request({
|
|
220
|
+
url: '/file',
|
|
221
|
+
method: 'DELETE',
|
|
222
|
+
data: { ids: [fileId] },
|
|
223
|
+
});
|
|
224
|
+
} catch {
|
|
225
|
+
// Ignore cleanup failures to keep the form stable.
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const cleanupUnsavedLogo = async () => {
|
|
230
|
+
if (logoFileId && logoFileId !== persistedLogoFileId) {
|
|
231
|
+
await deleteFileById(logoFileId);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const handleSelectLogo = () => {
|
|
236
|
+
if (!logoInputRef.current || isUploadingLogo) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
logoInputRef.current.value = '';
|
|
241
|
+
logoInputRef.current.click();
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const handleLogoUpload = async (event: ChangeEvent<HTMLInputElement>) => {
|
|
245
|
+
const file = event.target.files?.[0];
|
|
246
|
+
|
|
247
|
+
if (!file) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!file.type.startsWith('image/')) {
|
|
252
|
+
showToastHandler?.('error', logoInvalidTypeMessage);
|
|
253
|
+
event.target.value = '';
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (file.size > 5 * 1024 * 1024) {
|
|
258
|
+
showToastHandler?.('error', logoTooLargeMessage);
|
|
259
|
+
event.target.value = '';
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
setIsUploadingLogo(true);
|
|
264
|
+
setLogoUploadProgress(0);
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const previousUploadedLogoId = logoFileId;
|
|
268
|
+
const formData = new FormData();
|
|
269
|
+
formData.append('file', file);
|
|
270
|
+
formData.append('destination', 'finance/bank-account/logo');
|
|
271
|
+
|
|
272
|
+
const { data } = await request<UploadedFilePayload>({
|
|
273
|
+
url: '/file',
|
|
274
|
+
method: 'POST',
|
|
275
|
+
data: formData,
|
|
276
|
+
headers: {
|
|
277
|
+
'Content-Type': 'multipart/form-data',
|
|
278
|
+
},
|
|
279
|
+
onUploadProgress: (progressEvent) => {
|
|
280
|
+
if (!progressEvent.total) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const progress = Math.round(
|
|
285
|
+
(progressEvent.loaded * 100) / progressEvent.total
|
|
286
|
+
);
|
|
287
|
+
setLogoUploadProgress(progress);
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const nextLogoFileId = Number(data?.id);
|
|
292
|
+
|
|
293
|
+
if (!nextLogoFileId) {
|
|
294
|
+
throw new Error('Logo upload failed');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (
|
|
298
|
+
previousUploadedLogoId &&
|
|
299
|
+
previousUploadedLogoId !== persistedLogoFileId
|
|
300
|
+
) {
|
|
301
|
+
await deleteFileById(previousUploadedLogoId);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
setLogoFileId(nextLogoFileId);
|
|
305
|
+
setLogoPreviewUrl(`${getLogoUrl(nextLogoFileId)}?ts=${Date.now()}`);
|
|
306
|
+
setLogoUploadProgress(100);
|
|
307
|
+
form.setValue('logoFileId', nextLogoFileId, {
|
|
308
|
+
shouldDirty: true,
|
|
309
|
+
shouldValidate: true,
|
|
310
|
+
});
|
|
311
|
+
showToastHandler?.('success', logoUploadSuccessMessage);
|
|
312
|
+
} catch {
|
|
313
|
+
showToastHandler?.('error', logoUploadErrorMessage);
|
|
314
|
+
setLogoUploadProgress(0);
|
|
315
|
+
} finally {
|
|
316
|
+
setIsUploadingLogo(false);
|
|
317
|
+
event.target.value = '';
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const handleRemoveLogo = async () => {
|
|
322
|
+
if (isUploadingLogo) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (logoFileId && logoFileId !== persistedLogoFileId) {
|
|
327
|
+
await deleteFileById(logoFileId);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
setLogoFileId(null);
|
|
331
|
+
setLogoPreviewUrl(null);
|
|
332
|
+
setLogoUploadProgress(0);
|
|
333
|
+
form.setValue('logoFileId', null, {
|
|
334
|
+
shouldDirty: true,
|
|
335
|
+
shouldValidate: true,
|
|
336
|
+
});
|
|
337
|
+
showToastHandler?.('success', logoRemoveSuccessMessage);
|
|
338
|
+
};
|
|
339
|
+
|
|
135
340
|
useEffect(() => {
|
|
136
341
|
if (!open) {
|
|
137
342
|
return;
|
|
138
343
|
}
|
|
139
344
|
|
|
140
345
|
if (editingAccount) {
|
|
346
|
+
const currentLogoFileId = editingAccount.logoFileId ?? null;
|
|
347
|
+
|
|
348
|
+
setLogoFileId(currentLogoFileId);
|
|
349
|
+
setPersistedLogoFileId(currentLogoFileId);
|
|
350
|
+
setLogoPreviewUrl(
|
|
351
|
+
currentLogoFileId
|
|
352
|
+
? `${getLogoUrl(currentLogoFileId)}?ts=${Date.now()}`
|
|
353
|
+
: null
|
|
354
|
+
);
|
|
355
|
+
setLogoUploadProgress(0);
|
|
356
|
+
|
|
141
357
|
form.reset({
|
|
142
358
|
banco: editingAccount.banco,
|
|
143
359
|
agencia: editingAccount.agencia === '-' ? '' : editingAccount.agencia,
|
|
144
360
|
conta: editingAccount.conta === '-' ? '' : editingAccount.conta,
|
|
145
361
|
tipo: editingAccount.tipo,
|
|
146
362
|
descricao: editingAccount.descricao,
|
|
363
|
+
logoFileId: currentLogoFileId,
|
|
147
364
|
saldoInicial: editingAccount.saldoAtual,
|
|
148
365
|
});
|
|
149
366
|
return;
|
|
150
367
|
}
|
|
151
368
|
|
|
369
|
+
setLogoFileId(null);
|
|
370
|
+
setPersistedLogoFileId(null);
|
|
371
|
+
setLogoPreviewUrl(null);
|
|
372
|
+
setLogoUploadProgress(0);
|
|
373
|
+
|
|
152
374
|
form.reset({
|
|
153
375
|
banco: '',
|
|
154
376
|
agencia: '',
|
|
155
377
|
conta: '',
|
|
156
378
|
tipo: '',
|
|
157
379
|
descricao: '',
|
|
380
|
+
logoFileId: null,
|
|
158
381
|
saldoInicial: 0,
|
|
159
382
|
});
|
|
160
383
|
}, [editingAccount, form, open]);
|
|
161
384
|
|
|
162
385
|
const handleSubmit = async (values: BankAccountFormValues) => {
|
|
386
|
+
const nextLogoFileId = values.logoFileId ?? null;
|
|
387
|
+
const previousPersistedLogoId = persistedLogoFileId;
|
|
388
|
+
|
|
163
389
|
try {
|
|
164
390
|
if (editingAccount) {
|
|
165
391
|
await request({
|
|
@@ -171,6 +397,7 @@ function NovaContaSheet({
|
|
|
171
397
|
account: values.conta || undefined,
|
|
172
398
|
type: values.tipo,
|
|
173
399
|
description: values.descricao?.trim() || undefined,
|
|
400
|
+
logo_file_id: nextLogoFileId,
|
|
174
401
|
},
|
|
175
402
|
});
|
|
176
403
|
} else {
|
|
@@ -183,13 +410,36 @@ function NovaContaSheet({
|
|
|
183
410
|
account: values.conta || undefined,
|
|
184
411
|
type: values.tipo,
|
|
185
412
|
description: values.descricao?.trim() || undefined,
|
|
413
|
+
logo_file_id: nextLogoFileId,
|
|
186
414
|
initial_balance: values.saldoInicial,
|
|
187
415
|
},
|
|
188
416
|
});
|
|
189
417
|
}
|
|
190
418
|
|
|
191
419
|
await onCreated();
|
|
192
|
-
|
|
420
|
+
|
|
421
|
+
if (
|
|
422
|
+
previousPersistedLogoId &&
|
|
423
|
+
previousPersistedLogoId !== nextLogoFileId
|
|
424
|
+
) {
|
|
425
|
+
await deleteFileById(previousPersistedLogoId);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
setPersistedLogoFileId(nextLogoFileId);
|
|
429
|
+
setLogoFileId(nextLogoFileId);
|
|
430
|
+
setLogoPreviewUrl(
|
|
431
|
+
nextLogoFileId ? `${getLogoUrl(nextLogoFileId)}?ts=${Date.now()}` : null
|
|
432
|
+
);
|
|
433
|
+
setLogoUploadProgress(0);
|
|
434
|
+
form.reset({
|
|
435
|
+
banco: '',
|
|
436
|
+
agencia: '',
|
|
437
|
+
conta: '',
|
|
438
|
+
tipo: '',
|
|
439
|
+
descricao: '',
|
|
440
|
+
logoFileId: null,
|
|
441
|
+
saldoInicial: 0,
|
|
442
|
+
});
|
|
193
443
|
onOpenChange(false);
|
|
194
444
|
onEditingAccountChange(null);
|
|
195
445
|
showToastHandler?.(
|
|
@@ -205,6 +455,7 @@ function NovaContaSheet({
|
|
|
205
455
|
};
|
|
206
456
|
|
|
207
457
|
const handleCancel = () => {
|
|
458
|
+
void cleanupUnsavedLogo();
|
|
208
459
|
form.reset();
|
|
209
460
|
onEditingAccountChange(null);
|
|
210
461
|
onOpenChange(false);
|
|
@@ -214,40 +465,51 @@ function NovaContaSheet({
|
|
|
214
465
|
<Sheet
|
|
215
466
|
open={open}
|
|
216
467
|
onOpenChange={(nextOpen) => {
|
|
468
|
+
if (!nextOpen) {
|
|
469
|
+
void cleanupUnsavedLogo();
|
|
470
|
+
}
|
|
471
|
+
|
|
217
472
|
onOpenChange(nextOpen);
|
|
218
473
|
if (!nextOpen) {
|
|
219
474
|
onEditingAccountChange(null);
|
|
220
475
|
}
|
|
221
476
|
}}
|
|
222
477
|
>
|
|
223
|
-
<SheetContent className="w-full sm:max-w-
|
|
478
|
+
<SheetContent className="flex h-full w-full flex-col sm:max-w-2xl">
|
|
224
479
|
<SheetHeader>
|
|
225
480
|
<SheetTitle>
|
|
226
481
|
{editingAccount ? t('common.edit') : t('newAccount.title')}
|
|
227
482
|
</SheetTitle>
|
|
228
483
|
<SheetDescription>{t('newAccount.description')}</SheetDescription>
|
|
229
484
|
</SheetHeader>
|
|
230
|
-
<Form {...form}>
|
|
231
|
-
<form
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
485
|
+
<Form {...form}>
|
|
486
|
+
<form
|
|
487
|
+
className="flex h-full flex-col"
|
|
488
|
+
onSubmit={form.handleSubmit(handleSubmit)}
|
|
489
|
+
>
|
|
490
|
+
<FinanceSheetBody>
|
|
491
|
+
<FinanceSheetSection
|
|
492
|
+
title={t('sections.accountData.title')}
|
|
493
|
+
description={t('sections.accountData.description')}
|
|
494
|
+
>
|
|
495
|
+
<FormField
|
|
496
|
+
control={form.control}
|
|
497
|
+
name="banco"
|
|
498
|
+
render={({ field }) => (
|
|
499
|
+
<FormItem>
|
|
500
|
+
<FormLabel>{t('fields.bank')}</FormLabel>
|
|
501
|
+
<FormControl>
|
|
502
|
+
<Input
|
|
503
|
+
placeholder={t('fields.bankPlaceholder')}
|
|
504
|
+
{...field}
|
|
505
|
+
/>
|
|
506
|
+
</FormControl>
|
|
507
|
+
<FormMessage />
|
|
508
|
+
</FormItem>
|
|
509
|
+
)}
|
|
510
|
+
/>
|
|
249
511
|
|
|
250
|
-
<div className="grid grid-cols-2
|
|
512
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
251
513
|
<FormField
|
|
252
514
|
control={form.control}
|
|
253
515
|
name="agencia"
|
|
@@ -285,7 +547,7 @@ function NovaContaSheet({
|
|
|
285
547
|
/>
|
|
286
548
|
</div>
|
|
287
549
|
|
|
288
|
-
<div className="grid grid-cols-2
|
|
550
|
+
<div className="grid gap-4 md:grid-cols-2">
|
|
289
551
|
<FormField
|
|
290
552
|
control={form.control}
|
|
291
553
|
name="tipo"
|
|
@@ -344,11 +606,11 @@ function NovaContaSheet({
|
|
|
344
606
|
/>
|
|
345
607
|
</div>
|
|
346
608
|
|
|
347
|
-
<FormField
|
|
348
|
-
control={form.control}
|
|
349
|
-
name="descricao"
|
|
350
|
-
render={({ field }) => (
|
|
351
|
-
<FormItem>
|
|
609
|
+
<FormField
|
|
610
|
+
control={form.control}
|
|
611
|
+
name="descricao"
|
|
612
|
+
render={({ field }) => (
|
|
613
|
+
<FormItem>
|
|
352
614
|
<FormLabel>{t('fields.description')}</FormLabel>
|
|
353
615
|
<FormControl>
|
|
354
616
|
<Input
|
|
@@ -357,17 +619,89 @@ function NovaContaSheet({
|
|
|
357
619
|
value={field.value || ''}
|
|
358
620
|
/>
|
|
359
621
|
</FormControl>
|
|
360
|
-
<FormMessage />
|
|
361
|
-
</FormItem>
|
|
362
|
-
)}
|
|
363
|
-
/>
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
622
|
+
<FormMessage />
|
|
623
|
+
</FormItem>
|
|
624
|
+
)}
|
|
625
|
+
/>
|
|
626
|
+
</FinanceSheetSection>
|
|
627
|
+
|
|
628
|
+
<FinanceSheetSection
|
|
629
|
+
title={t('sections.logo.title')}
|
|
630
|
+
description={t('sections.logo.description')}
|
|
631
|
+
>
|
|
632
|
+
<FormItem>
|
|
633
|
+
<FormLabel>{t('fields.logo')}</FormLabel>
|
|
634
|
+
<div className="flex flex-col gap-4 rounded-xl border border-dashed border-border/70 bg-muted/20 p-4 sm:flex-row sm:items-start">
|
|
635
|
+
<div className="flex h-20 w-20 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted">
|
|
636
|
+
{logoPreviewUrl ? (
|
|
637
|
+
<img
|
|
638
|
+
src={logoPreviewUrl}
|
|
639
|
+
alt={t('fields.logo')}
|
|
640
|
+
className="h-full w-full object-cover"
|
|
641
|
+
/>
|
|
642
|
+
) : (
|
|
643
|
+
<Landmark className="h-8 w-8 text-muted-foreground" />
|
|
644
|
+
)}
|
|
645
|
+
</div>
|
|
646
|
+
|
|
647
|
+
<div className="flex-1 space-y-2">
|
|
648
|
+
<input
|
|
649
|
+
ref={logoInputRef}
|
|
650
|
+
type="file"
|
|
651
|
+
accept="image/*"
|
|
652
|
+
className="hidden"
|
|
653
|
+
onChange={handleLogoUpload}
|
|
654
|
+
/>
|
|
655
|
+
|
|
656
|
+
<div className="flex flex-wrap gap-2">
|
|
657
|
+
<Button
|
|
658
|
+
type="button"
|
|
659
|
+
variant="outline"
|
|
660
|
+
onClick={handleSelectLogo}
|
|
661
|
+
disabled={isUploadingLogo}
|
|
662
|
+
>
|
|
663
|
+
<Upload className="mr-2 h-4 w-4" />
|
|
664
|
+
{t('fields.logoAction')}
|
|
665
|
+
</Button>
|
|
666
|
+
|
|
667
|
+
{logoFileId ? (
|
|
668
|
+
<Button
|
|
669
|
+
type="button"
|
|
670
|
+
variant="ghost"
|
|
671
|
+
onClick={() => void handleRemoveLogo()}
|
|
672
|
+
disabled={isUploadingLogo}
|
|
673
|
+
>
|
|
674
|
+
<Trash2 className="mr-2 h-4 w-4" />
|
|
675
|
+
{t('fields.logoRemove')}
|
|
676
|
+
</Button>
|
|
677
|
+
) : null}
|
|
678
|
+
</div>
|
|
679
|
+
|
|
680
|
+
<p className="text-xs text-muted-foreground">
|
|
681
|
+
{t('fields.logoHint')}
|
|
682
|
+
</p>
|
|
683
|
+
|
|
684
|
+
{isUploadingLogo ? (
|
|
685
|
+
<p className="text-xs text-muted-foreground">
|
|
686
|
+
{t('fields.logoUploading', {
|
|
687
|
+
progress: logoUploadProgress,
|
|
688
|
+
})}
|
|
689
|
+
</p>
|
|
690
|
+
) : null}
|
|
691
|
+
</div>
|
|
692
|
+
</div>
|
|
693
|
+
</FormItem>
|
|
694
|
+
</FinanceSheetSection>
|
|
695
|
+
</FinanceSheetBody>
|
|
696
|
+
|
|
697
|
+
<div className="flex justify-end gap-2 border-t px-4 py-4 sm:px-6">
|
|
367
698
|
<Button type="button" variant="outline" onClick={handleCancel}>
|
|
368
699
|
{t('common.cancel')}
|
|
369
700
|
</Button>
|
|
370
|
-
<Button
|
|
701
|
+
<Button
|
|
702
|
+
type="submit"
|
|
703
|
+
disabled={form.formState.isSubmitting || isUploadingLogo}
|
|
704
|
+
>
|
|
371
705
|
{t('common.save')}
|
|
372
706
|
</Button>
|
|
373
707
|
</div>
|
|
@@ -430,9 +764,47 @@ export default function ContasBancariasPage() {
|
|
|
430
764
|
.filter((c) => c.ativo)
|
|
431
765
|
.reduce((acc, c) => acc + c.saldoAtual, 0);
|
|
432
766
|
|
|
433
|
-
const saldoConciliadoTotal = accounts
|
|
434
|
-
.filter((c) => c.ativo)
|
|
435
|
-
.reduce((acc, c) => acc + c.saldoConciliado, 0);
|
|
767
|
+
const saldoConciliadoTotal = accounts
|
|
768
|
+
.filter((c) => c.ativo)
|
|
769
|
+
.reduce((acc, c) => acc + c.saldoConciliado, 0);
|
|
770
|
+
const activeAccountsCount = accounts.filter((c) => c.ativo).length;
|
|
771
|
+
const inactiveAccountsCount = accounts.length - activeAccountsCount;
|
|
772
|
+
const summaryCards = [
|
|
773
|
+
{
|
|
774
|
+
key: 'balance',
|
|
775
|
+
title: t('cards.totalBalance'),
|
|
776
|
+
value: <Money value={saldoTotal} />,
|
|
777
|
+
description: t('cards.activeAccounts', {
|
|
778
|
+
count: activeAccountsCount,
|
|
779
|
+
}),
|
|
780
|
+
icon: Landmark,
|
|
781
|
+
layout: 'compact' as const,
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
key: 'reconciled',
|
|
785
|
+
title: t('cards.reconciledBalance'),
|
|
786
|
+
value: <Money value={saldoConciliadoTotal} />,
|
|
787
|
+
description: `${t('cards.difference')}: ${new Intl.NumberFormat(
|
|
788
|
+
currentLocaleCode === 'pt' ? 'pt-BR' : 'en-US',
|
|
789
|
+
{
|
|
790
|
+
style: 'currency',
|
|
791
|
+
currency: 'BRL',
|
|
792
|
+
}
|
|
793
|
+
).format(saldoTotal - saldoConciliadoTotal)}`,
|
|
794
|
+
icon: RefreshCw,
|
|
795
|
+
layout: 'compact' as const,
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
key: 'accounts',
|
|
799
|
+
title: t('cards.accountsOverview'),
|
|
800
|
+
value: activeAccountsCount,
|
|
801
|
+
description: t('cards.inactiveAccounts', {
|
|
802
|
+
count: inactiveAccountsCount,
|
|
803
|
+
}),
|
|
804
|
+
icon: Building2,
|
|
805
|
+
layout: 'compact' as const,
|
|
806
|
+
},
|
|
807
|
+
];
|
|
436
808
|
|
|
437
809
|
const handleCreate = () => {
|
|
438
810
|
setEditingAccount(null);
|
|
@@ -514,173 +886,163 @@ export default function ContasBancariasPage() {
|
|
|
514
886
|
</AlertDialogContent>
|
|
515
887
|
</AlertDialog>
|
|
516
888
|
|
|
517
|
-
<
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
<
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
</
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
<Trash2 className="h-4 w-4" />
|
|
675
|
-
</Button>
|
|
676
|
-
</div>
|
|
677
|
-
</div>
|
|
678
|
-
</div>
|
|
679
|
-
</CardContent>
|
|
680
|
-
</Card>
|
|
681
|
-
);
|
|
682
|
-
})}
|
|
683
|
-
</div>
|
|
889
|
+
<KpiCardsGrid items={summaryCards} columns={3} />
|
|
890
|
+
|
|
891
|
+
<FinancePageSection
|
|
892
|
+
title={t('list.title')}
|
|
893
|
+
description={t('list.description')}
|
|
894
|
+
contentClassName="p-4 sm:p-5"
|
|
895
|
+
>
|
|
896
|
+
{accounts.length > 0 ? (
|
|
897
|
+
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
|
898
|
+
{accounts.map((conta) => {
|
|
899
|
+
const tipo =
|
|
900
|
+
tipoConfig[conta.tipo as keyof typeof tipoConfig] ||
|
|
901
|
+
tipoConfig.corrente;
|
|
902
|
+
const TipoIcon = tipo.icon;
|
|
903
|
+
const diferenca = conta.saldoAtual - conta.saldoConciliado;
|
|
904
|
+
|
|
905
|
+
return (
|
|
906
|
+
<Card
|
|
907
|
+
key={conta.id}
|
|
908
|
+
className={!conta.ativo ? 'border-border/60 opacity-70' : ''}
|
|
909
|
+
>
|
|
910
|
+
<CardHeader className="space-y-4">
|
|
911
|
+
<div className="flex items-start justify-between gap-3">
|
|
912
|
+
<div className="flex min-w-0 items-center gap-3">
|
|
913
|
+
<BankAccountLogo account={conta} icon={TipoIcon} />
|
|
914
|
+
<div className="min-w-0">
|
|
915
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
916
|
+
<CardTitle className="text-base">
|
|
917
|
+
{conta.banco}
|
|
918
|
+
</CardTitle>
|
|
919
|
+
{conta.descricao ? (
|
|
920
|
+
<span className="text-xs text-muted-foreground">
|
|
921
|
+
{conta.descricao}
|
|
922
|
+
</span>
|
|
923
|
+
) : null}
|
|
924
|
+
</div>
|
|
925
|
+
<CardDescription className="space-y-0.5">
|
|
926
|
+
{conta.agencia !== '-' ? (
|
|
927
|
+
<span className="block">
|
|
928
|
+
{t('accountCard.bankAccount', {
|
|
929
|
+
agency: conta.agencia,
|
|
930
|
+
account: conta.conta,
|
|
931
|
+
})}
|
|
932
|
+
</span>
|
|
933
|
+
) : null}
|
|
934
|
+
<span className="block">{tipo.label}</span>
|
|
935
|
+
</CardDescription>
|
|
936
|
+
</div>
|
|
937
|
+
</div>
|
|
938
|
+
{!conta.ativo ? (
|
|
939
|
+
<Badge
|
|
940
|
+
variant="outline"
|
|
941
|
+
className="text-muted-foreground"
|
|
942
|
+
>
|
|
943
|
+
{t('status.inactive')}
|
|
944
|
+
</Badge>
|
|
945
|
+
) : null}
|
|
946
|
+
</div>
|
|
947
|
+
</CardHeader>
|
|
948
|
+
<CardContent>
|
|
949
|
+
<div className="space-y-3">
|
|
950
|
+
<div>
|
|
951
|
+
<p className="text-sm text-muted-foreground">
|
|
952
|
+
{t('accountCard.currentBalance')}
|
|
953
|
+
</p>
|
|
954
|
+
<p className="text-2xl font-bold">
|
|
955
|
+
<Money value={conta.saldoAtual} />
|
|
956
|
+
</p>
|
|
957
|
+
</div>
|
|
958
|
+
<div className="flex items-center justify-between text-sm">
|
|
959
|
+
<span className="text-muted-foreground">
|
|
960
|
+
{t('accountCard.reconciledBalance')}
|
|
961
|
+
</span>
|
|
962
|
+
<Money value={conta.saldoConciliado} />
|
|
963
|
+
</div>
|
|
964
|
+
{diferenca !== 0 ? (
|
|
965
|
+
<div className="flex items-center justify-between text-sm">
|
|
966
|
+
<span className="text-muted-foreground">
|
|
967
|
+
{t('accountCard.difference')}
|
|
968
|
+
</span>
|
|
969
|
+
<span
|
|
970
|
+
className={
|
|
971
|
+
diferenca > 0 ? 'text-green-600' : 'text-red-600'
|
|
972
|
+
}
|
|
973
|
+
>
|
|
974
|
+
<Money value={diferenca} showSign />
|
|
975
|
+
</span>
|
|
976
|
+
</div>
|
|
977
|
+
) : null}
|
|
978
|
+
<div className="flex flex-wrap gap-2 pt-2">
|
|
979
|
+
<Button
|
|
980
|
+
variant="outline"
|
|
981
|
+
size="sm"
|
|
982
|
+
className="min-w-0 flex-1 basis-[calc(50%-0.25rem)] bg-transparent"
|
|
983
|
+
asChild
|
|
984
|
+
>
|
|
985
|
+
<Link
|
|
986
|
+
href={`/finance/cash-and-banks/statements?bank_account_id=${conta.id}`}
|
|
987
|
+
>
|
|
988
|
+
<Eye className="mr-2 h-4 w-4" />
|
|
989
|
+
{t('accountCard.statement')}
|
|
990
|
+
</Link>
|
|
991
|
+
</Button>
|
|
992
|
+
<Button
|
|
993
|
+
variant="outline"
|
|
994
|
+
size="sm"
|
|
995
|
+
className="min-w-0 flex-1 basis-[calc(50%-0.25rem)] bg-transparent"
|
|
996
|
+
asChild
|
|
997
|
+
>
|
|
998
|
+
<Link
|
|
999
|
+
href={`/finance/cash-and-banks/bank-reconciliation?bank_account_id=${conta.id}`}
|
|
1000
|
+
>
|
|
1001
|
+
<RefreshCw className="mr-2 h-4 w-4" />
|
|
1002
|
+
{t('accountCard.reconcile')}
|
|
1003
|
+
</Link>
|
|
1004
|
+
</Button>
|
|
1005
|
+
<div className="ml-auto inline-flex shrink-0 overflow-hidden rounded-md border bg-background shadow-sm">
|
|
1006
|
+
<Button
|
|
1007
|
+
variant="ghost"
|
|
1008
|
+
size="sm"
|
|
1009
|
+
className="rounded-none border-0 px-3 hover:bg-muted"
|
|
1010
|
+
onClick={() => handleEdit(conta)}
|
|
1011
|
+
aria-label={t('common.edit')}
|
|
1012
|
+
title={t('common.edit')}
|
|
1013
|
+
>
|
|
1014
|
+
<Pencil className="h-4 w-4" />
|
|
1015
|
+
</Button>
|
|
1016
|
+
<Button
|
|
1017
|
+
variant="ghost"
|
|
1018
|
+
size="sm"
|
|
1019
|
+
className="rounded-none border-0 border-l px-3 text-destructive hover:bg-destructive/10 hover:text-destructive"
|
|
1020
|
+
onClick={() => setAccountIdToDelete(conta.id)}
|
|
1021
|
+
aria-label={deleteDialogTitle}
|
|
1022
|
+
title={deleteDialogTitle}
|
|
1023
|
+
>
|
|
1024
|
+
<Trash2 className="h-4 w-4" />
|
|
1025
|
+
</Button>
|
|
1026
|
+
</div>
|
|
1027
|
+
</div>
|
|
1028
|
+
</div>
|
|
1029
|
+
</CardContent>
|
|
1030
|
+
</Card>
|
|
1031
|
+
);
|
|
1032
|
+
})}
|
|
1033
|
+
</div>
|
|
1034
|
+
) : (
|
|
1035
|
+
<div className="p-6 sm:p-8">
|
|
1036
|
+
<EmptyState
|
|
1037
|
+
icon={<Landmark className="h-12 w-12" />}
|
|
1038
|
+
title={t('empty.title')}
|
|
1039
|
+
description={t('empty.description')}
|
|
1040
|
+
actionLabel={t('newAccount.action')}
|
|
1041
|
+
onAction={handleCreate}
|
|
1042
|
+
/>
|
|
1043
|
+
</div>
|
|
1044
|
+
)}
|
|
1045
|
+
</FinancePageSection>
|
|
684
1046
|
</Page>
|
|
685
1047
|
);
|
|
686
1048
|
}
|