feat: clerk ui

This commit is contained in:
2025-05-27 21:55:13 +04:00
parent b8a9409dad
commit 6b1f57a3b9
10 changed files with 405 additions and 84 deletions

View File

@@ -33,13 +33,16 @@ public class ReportContract(IClientStorageContract clientStorage, ICurrencyStora
public async Task<List<ClientsByCreditProgramDataModel>> GetDataClientsByCreditProgramAsync(List<string>? creditProgramIds, CancellationToken ct)
{
_logger.LogInformation("Get data ClientsByCreditProgram");
if (creditProgramIds is null || creditProgramIds.Count == 0)
{
return [];
}
var clients = await Task.Run(() => _clientStorage.GetList(), ct);
var creditPrograms = await Task.Run(() => _creditProgramStorage.GetList(), ct);
var currencies = await Task.Run(() => _currencyStorage.GetList(), ct);
var filteredPrograms = creditPrograms
.Where(cp => cp.Currencies.Any()) // Проверяем, что у кредитной программы есть связанные валюты
.Where(cp => creditProgramIds == null || creditProgramIds.Contains(cp.Id));
.Where(cp => creditProgramIds.Contains(cp.Id));
return filteredPrograms
.Select(cp => new ClientsByCreditProgramDataModel

View File

@@ -0,0 +1,18 @@
namespace BankContracts.BindingModels;
public class ReportMailSendInfoBindingModel
{
public string Email { get; set; } = string.Empty;
public string Subject { get; set; } = string.Empty;
public string Body { get; set; } = string.Empty;
}
public class CreditProgramReportMailSendInfoBindingModel : ReportMailSendInfoBindingModel
{
public List<string> CreditProgramIds { get; set; } = new();
}
public class DepositReportMailSendInfoBindingModel : ReportMailSendInfoBindingModel
{
// Для отчетов по депозитам дополнительные поля передаются через query параметры
}

View File

@@ -1,5 +1,6 @@
using BankBusinessLogic.Implementations;
using BankContracts.AdapterContracts;
using BankContracts.BindingModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -76,7 +77,7 @@ public class ReportController(IReportAdapter adapter) : ControllerBase
[Consumes("application/octet-stream")]
public async Task<IActionResult> LoadClientsByDeposit(DateTime fromDate, DateTime toDate, CancellationToken cancellationToken)
{
return (await _adapter.CreateDocumentClientsByDepositAsync(fromDate,toDate, cancellationToken)).GetResponse(Request, Response);
return (await _adapter.CreateDocumentClientsByDepositAsync(fromDate, toDate, cancellationToken)).GetResponse(Request, Response);
}
/// <summary>
@@ -151,34 +152,36 @@ public class ReportController(IReportAdapter adapter) : ControllerBase
/// <summary>
/// Отправка word отчета Клиентов по Кредитным программам
/// </summary>
/// <param name="email"></param>
/// <param name="creditProgramIds"></param>
/// <param name="mailInfo"></param>
/// <param name="ct"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> SendReportByCreditProgram(string email, [FromQuery] List<string>? creditProgramIds, CancellationToken ct)
public async Task<IActionResult> SendReportByCreditProgram([FromBody] CreditProgramReportMailSendInfoBindingModel mailInfo, CancellationToken ct)
{
try
{
var report = await _adapter.CreateDocumentClientsByCreditProgramAsync(creditProgramIds, ct);
var report = await _adapter.CreateDocumentClientsByCreditProgramAsync(mailInfo.CreditProgramIds, ct);
var response = report.GetResponse(Request, Response);
if (response is FileStreamResult fileResult)
{
var tempPath = Path.GetTempFileName();
using (var fileStream = new FileStream(tempPath, FileMode.Create))
var tempPathWithExtension = Path.ChangeExtension(tempPath, ".docx");
using (var fileStream = new FileStream(tempPathWithExtension, FileMode.Create))
{
await fileResult.FileStream.CopyToAsync(fileStream);
}
await _emailService.SendReportAsync(
toEmail: email,
subject: "Отчет по клиентам по кредитным программам",
body: "<h1>Отчет по клиентам по кредитным программам</h1><p>В приложении находится отчет по клиентам по кредитным программам.</p>",
attachmentPath: tempPath
toEmail: mailInfo.Email,
subject: mailInfo.Subject,
body: mailInfo.Body,
attachmentPath: tempPathWithExtension
);
System.IO.File.Delete(tempPath);
System.IO.File.Delete(tempPathWithExtension);
return Ok("Отчет успешно отправлен на почту");
}
@@ -191,37 +194,40 @@ public class ReportController(IReportAdapter adapter) : ControllerBase
}
/// <summary>
/// Отправка pdf отчета Клиентов по Валютам
/// Отправка pdf отчета Клиентов по Депозитам
/// </summary>
/// <param name="email"></param>
/// <param name="mailInfo"></param>
/// <param name="fromDate"></param>
/// <param name="toDate"></param>
/// <param name="ct"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> SendReportByDeposit(string email, DateTime fromDate, DateTime toDate, CancellationToken ct)
public async Task<IActionResult> SendReportByDeposit([FromBody] DepositReportMailSendInfoBindingModel mailInfo, DateTime fromDate, DateTime toDate, CancellationToken ct)
{
try
{
var report = await _adapter.CreateDocumentClientsByDepositAsync(fromDate, toDate, ct);
var response = report.GetResponse(Request, Response);
if (response is FileStreamResult fileResult)
{
var tempPath = Path.GetTempFileName();
using (var fileStream = new FileStream(tempPath, FileMode.Create))
var tempPathWithExtension = Path.ChangeExtension(tempPath, ".pdf");
using (var fileStream = new FileStream(tempPathWithExtension, FileMode.Create))
{
await fileResult.FileStream.CopyToAsync(fileStream);
}
await _emailService.SendReportAsync(
toEmail: email,
subject: "Отчет по клиентам по вкладам",
body: $"<h1>Отчет по клиентам по вкладам</h1><p>Отчет за период с {fromDate:dd.MM.yyyy} по {toDate:dd.MM.yyyy}</p>",
attachmentPath: tempPath
toEmail: mailInfo.Email,
subject: mailInfo.Subject,
body: mailInfo.Body,
attachmentPath: tempPathWithExtension
);
System.IO.File.Delete(tempPath);
System.IO.File.Delete(tempPathWithExtension);
return Ok("Отчет успешно отправлен на почту");
}
@@ -249,11 +255,13 @@ public class ReportController(IReportAdapter adapter) : ControllerBase
{
var report = await _adapter.CreateDocumentDepositAndCreditProgramByCurrencyAsync(fromDate, toDate, ct);
var response = report.GetResponse(Request, Response);
if (response is FileStreamResult fileResult)
{
var tempPath = Path.GetTempFileName();
using (var fileStream = new FileStream(tempPath, FileMode.Create))
var tempPathWithExtension = Path.ChangeExtension(tempPath, ".pdf");
using (var fileStream = new FileStream(tempPathWithExtension, FileMode.Create))
{
await fileResult.FileStream.CopyToAsync(fileStream);
}
@@ -262,10 +270,11 @@ public class ReportController(IReportAdapter adapter) : ControllerBase
toEmail: email,
subject: "Отчет по вкладам и кредитным программам по валютам",
body: $"<h1>Отчет по вкладам и кредитным программам по валютам</h1><p>Отчет за период с {fromDate:dd.MM.yyyy} по {toDate:dd.MM.yyyy}</p>",
attachmentPath: tempPath
attachmentPath: tempPathWithExtension
);
System.IO.File.Delete(tempPath);
System.IO.File.Delete(tempPathWithExtension);
return Ok("Отчет успешно отправлен на почту");
}
@@ -278,36 +287,38 @@ public class ReportController(IReportAdapter adapter) : ControllerBase
}
/// <summary>
/// Отправка excel отчета Клиентов по Кредитных программ
/// Отправка excel отчета Клиентов по Кредитным программам
/// </summary>
/// <param name="email"></param>
/// <param name="creditProgramIds"></param>
/// <param name="mailInfo"></param>
/// <param name="ct"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> SendExcelReportByCreditProgram(string email, [FromQuery] List<string>? creditProgramIds, CancellationToken ct)
public async Task<IActionResult> SendExcelReportByCreditProgram([FromBody] CreditProgramReportMailSendInfoBindingModel mailInfo, CancellationToken ct)
{
try
{
var report = await _adapter.CreateExcelDocumentClientsByCreditProgramAsync(creditProgramIds, ct);
var report = await _adapter.CreateExcelDocumentClientsByCreditProgramAsync(mailInfo.CreditProgramIds, ct);
var response = report.GetResponse(Request, Response);
if (response is FileStreamResult fileResult)
{
var tempPath = Path.GetTempFileName();
using (var fileStream = new FileStream(tempPath, FileMode.Create))
var tempPathWithExtension = Path.ChangeExtension(tempPath, ".xlsx");
using (var fileStream = new FileStream(tempPathWithExtension, FileMode.Create))
{
await fileResult.FileStream.CopyToAsync(fileStream);
}
await _emailService.SendReportAsync(
toEmail: email,
subject: "Excel отчет по клиентам по кредитным программам",
body: "<h1>Excel отчет по клиентам по кредитным программам</h1><p>В приложении находится Excel отчет по клиентам по кредитным программам.</p>",
attachmentPath: tempPath
toEmail: mailInfo.Email,
subject: mailInfo.Subject,
body: mailInfo.Body,
attachmentPath: tempPathWithExtension
);
System.IO.File.Delete(tempPath);
System.IO.File.Delete(tempPathWithExtension);
return Ok("Excel отчет успешно отправлен на почту");
}
@@ -333,11 +344,13 @@ public class ReportController(IReportAdapter adapter) : ControllerBase
{
var report = await _adapter.CreateDocumentDepositByCreditProgramAsync(creditProgramIds, ct);
var response = report.GetResponse(Request, Response);
if (response is FileStreamResult fileResult)
{
var tempPath = Path.GetTempFileName();
using (var fileStream = new FileStream(tempPath, FileMode.Create))
var tempPathWithExtension = Path.ChangeExtension(tempPath, ".docx");
using (var fileStream = new FileStream(tempPathWithExtension, FileMode.Create))
{
await fileResult.FileStream.CopyToAsync(fileStream);
}
@@ -346,10 +359,11 @@ public class ReportController(IReportAdapter adapter) : ControllerBase
toEmail: email,
subject: "Отчет по вкладам по кредитным программам",
body: "<h1>Отчет по вкладам по кредитным программам</h1><p>В приложении находится отчет по вкладам по кредитным программам.</p>",
attachmentPath: tempPath
attachmentPath: tempPathWithExtension
);
System.IO.File.Delete(tempPath);
System.IO.File.Delete(tempPathWithExtension);
return Ok("Отчет успешно отправлен на почту");
}
@@ -375,11 +389,13 @@ public class ReportController(IReportAdapter adapter) : ControllerBase
{
var report = await _adapter.CreateExcelDocumentDepositByCreditProgramAsync(creditProgramIds, ct);
var response = report.GetResponse(Request, Response);
if (response is FileStreamResult fileResult)
{
var tempPath = Path.GetTempFileName();
using (var fileStream = new FileStream(tempPath, FileMode.Create))
var tempPathWithExtension = Path.ChangeExtension(tempPath, ".xlsx");
using (var fileStream = new FileStream(tempPathWithExtension, FileMode.Create))
{
await fileResult.FileStream.CopyToAsync(fileStream);
}
@@ -388,10 +404,11 @@ public class ReportController(IReportAdapter adapter) : ControllerBase
toEmail: email,
subject: "Excel отчет по вкладам по кредитным программам",
body: "<h1>Excel отчет по вкладам по кредитным программам</h1><p>В приложении находится Excel отчет по вкладам по кредитным программам.</p>",
attachmentPath: tempPath
attachmentPath: tempPathWithExtension
);
System.IO.File.Delete(tempPath);
System.IO.File.Delete(tempPathWithExtension);
return Ok("Excel отчет успешно отправлен на почту");
}

View File

@@ -149,10 +149,16 @@ export const reportsApi = {
`api/Report/GetClientByDeposit?fromDate=${fromDate}&toDate=${toDate}`,
),
sendDepositsPdfReport: (fromDate: string, toDate: string, email: string) =>
sendDepositsPdfReport: (
fromDate: string,
toDate: string,
email: string,
subject: string,
body: string,
) =>
postEmailData(
`api/Report/SendReportByDeposit?fromDate=${fromDate}&toDate=${toDate}`,
{ email },
{ email, subject, body },
),
// Word отчеты по кредитным программам
@@ -166,9 +172,16 @@ export const reportsApi = {
)}`,
),
sendCreditProgramsWordReport: (creditProgramIds: string[], email: string) =>
sendCreditProgramsWordReport: (
creditProgramIds: string[],
email: string,
subject: string,
body: string,
) =>
postEmailData('api/Report/SendReportByCreditProgram', {
email,
subject,
body,
creditProgramIds,
}),
@@ -178,9 +191,16 @@ export const reportsApi = {
`api/Report/LoadExcelClientByCreditProgram?${creditProgramIds}`,
),
sendCreditProgramsExcelReport: (creditProgramIds: string[], email: string) =>
sendCreditProgramsExcelReport: (
creditProgramIds: string[],
email: string,
subject: string,
body: string,
) =>
postEmailData('api/Report/SendExcelReportByCreditProgram', {
email,
subject,
body,
creditProgramIds,
}),
};

View File

@@ -0,0 +1,159 @@
import React from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Button } from '@/components/ui/button';
const emailSchema = z.object({
email: z.string().email('Введите корректный email адрес'),
subject: z.string().min(1, 'Введите тему письма'),
body: z.string().min(1, 'Введите текст письма'),
});
type EmailFormData = z.infer<typeof emailSchema>;
interface EmailDialogProps {
isOpen: boolean;
onClose: () => void;
onSubmit: (data: EmailFormData) => void;
isLoading?: boolean;
title?: string;
description?: string;
defaultSubject?: string;
defaultBody?: string;
}
export const EmailDialog = ({
isOpen,
onClose,
onSubmit,
isLoading = false,
title = 'Отправить отчет на почту',
description = 'Заполните данные для отправки отчета',
defaultSubject = 'Отчет из банковской системы',
defaultBody = 'Во вложении находится запрошенный отчет.',
}: EmailDialogProps) => {
const form = useForm<EmailFormData>({
resolver: zodResolver(emailSchema),
defaultValues: {
email: '',
subject: defaultSubject,
body: defaultBody,
},
});
React.useEffect(() => {
if (isOpen) {
form.reset({
email: '',
subject: defaultSubject,
body: defaultBody,
});
}
}, [isOpen, form, defaultSubject, defaultBody]);
const handleSubmit = (data: EmailFormData) => {
onSubmit(data);
onClose();
};
const handleClose = () => {
form.reset();
onClose();
};
return (
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{title}</DialogTitle>
<DialogDescription>{description}</DialogDescription>
</DialogHeader>
<Form {...form}>
<form
onSubmit={form.handleSubmit(handleSubmit)}
className="space-y-4"
>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email адрес</FormLabel>
<FormControl>
<Input
placeholder="example@email.com"
type="email"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="subject"
render={({ field }) => (
<FormItem>
<FormLabel>Тема письма</FormLabel>
<FormControl>
<Input placeholder="Тема письма" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="body"
render={({ field }) => (
<FormItem>
<FormLabel>Текст письма</FormLabel>
<FormControl>
<Textarea
placeholder="Текст письма"
className="min-h-[100px]"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex justify-end gap-2">
<Button
type="button"
variant="outline"
onClick={handleClose}
disabled={isLoading}
>
Отмена
</Button>
<Button type="submit" disabled={isLoading}>
{isLoading ? 'Отправка...' : 'Отправить'}
</Button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
);
};

View File

@@ -16,14 +16,14 @@ export const ReportSidebar = ({
onReset,
}: ReportSidebarProps) => {
return (
<div className="w-64 border-r bg-muted/10 p-4">
<div className="w-70 border-r bg-muted/10 p-4">
<div className="space-y-4">
<div>
<h3 className="text-lg font-semibold mb-3">Категории отчетов</h3>
<div className="space-y-2">
<Button
variant={selectedCategory === 'deposits' ? 'default' : 'outline'}
className="w-full justify-start"
className="w-full"
onClick={() => onCategorySelect('deposits')}
>
Отчеты по депозитам
@@ -32,7 +32,7 @@ export const ReportSidebar = ({
variant={
selectedCategory === 'creditPrograms' ? 'default' : 'outline'
}
className="w-full justify-start"
className="w-full text-wrap p-5"
onClick={() => onCategorySelect('creditPrograms')}
>
Отчеты по кредитным программам

View File

@@ -35,6 +35,7 @@ import {
FormMessage,
} from '@/components/ui/form';
import { PdfViewer } from './PdfViewer';
import { EmailDialog } from './EmailDialog';
import { useCreditPrograms } from '@/hooks/useCreditPrograms';
import type { ReportCategory } from './ReportSidebar';
@@ -69,6 +70,8 @@ interface ReportViewerProps {
type: string,
data: Record<string, unknown>,
email: string,
subject: string,
body: string,
) => void;
pdfReport: { blob: Blob; fileName: string; mimeType: string } | null;
isLoading: boolean;
@@ -84,6 +87,15 @@ export const ReportViewer = ({
}: ReportViewerProps) => {
const { creditPrograms } = useCreditPrograms();
// Состояние для EmailDialog
const [isEmailDialogOpen, setIsEmailDialogOpen] = React.useState(false);
const [emailDialogData, setEmailDialogData] = React.useState<{
type: string;
data: Record<string, unknown>;
defaultSubject: string;
defaultBody: string;
} | null>(null);
// Формы для разных типов отчетов
const depositsForm = useForm<DepositsReportForm>({
resolver: zodResolver(depositsReportSchema),
@@ -112,15 +124,20 @@ export const ReportViewer = ({
});
};
const handleSendDepositsEmail = (data: DepositsReportForm, email: string) => {
onSendEmail(
'deposits-pdf',
{
const handleSendDepositsEmail = (data: DepositsReportForm) => {
setEmailDialogData({
type: 'deposits-pdf',
data: {
fromDate: format(data.fromDate, 'yyyy-MM-dd'),
toDate: format(data.toDate, 'yyyy-MM-dd'),
},
email,
);
defaultSubject: 'Отчет по депозитам',
defaultBody: `Отчет по депозитам за период с ${format(
data.fromDate,
'dd.MM.yyyy',
)} по ${format(data.toDate, 'dd.MM.yyyy')}.`,
});
setIsEmailDialogOpen(true);
};
// Обработчики для отчетов по кредитным программам
@@ -132,17 +149,21 @@ export const ReportViewer = ({
});
};
const handleSendCreditProgramsEmail = (
data: CreditProgramsReportForm,
email: string,
) => {
onSendEmail(
`creditPrograms-${data.format}`,
{
const handleSendCreditProgramsEmail = (data: CreditProgramsReportForm) => {
const selectedPrograms = data.creditProgramIds
.map((id) => creditPrograms?.find((p) => p.id === id)?.name)
.filter(Boolean)
.join(', ');
setEmailDialogData({
type: `creditPrograms-${data.format}`,
data: {
creditProgramIds: data.creditProgramIds,
},
email,
);
defaultSubject: `Отчет по кредитным программам (${data.format.toUpperCase()})`,
defaultBody: `Отчет по кредитным программам: ${selectedPrograms}.`,
});
setIsEmailDialogOpen(true);
};
// Проверка валидности форм
@@ -175,6 +196,23 @@ export const ReportViewer = ({
creditProgramsForm.setValue('creditProgramIds', newValues);
};
// Обработчик отправки email
const handleEmailSubmit = (emailData: {
email: string;
subject: string;
body: string;
}) => {
if (emailDialogData) {
onSendEmail(
emailDialogData.type,
emailDialogData.data,
emailData.email,
emailData.subject,
emailData.body,
);
}
};
if (!category) {
return (
<div className="flex-1 flex items-center justify-center">
@@ -314,12 +352,7 @@ export const ReportViewer = ({
<Button
type="button"
variant="outline"
onClick={depositsForm.handleSubmit((data) => {
const email = prompt('Введите email для отправки:');
if (email) {
handleSendDepositsEmail(data, email);
}
})}
onClick={depositsForm.handleSubmit(handleSendDepositsEmail)}
disabled={!isDepositsFormValid || isLoading}
className="flex items-center gap-2"
>
@@ -459,23 +492,39 @@ export const ReportViewer = ({
<Button
type="button"
variant="outline"
onClick={creditProgramsForm.handleSubmit((data) => {
const email = prompt('Введите email для отправки:');
if (email) {
handleSendCreditProgramsEmail(data, email);
}
})}
onClick={creditProgramsForm.handleSubmit(
handleSendCreditProgramsEmail,
)}
disabled={!isCreditProgramsFormValid || isLoading}
className="flex items-center gap-2"
className="flex flex-col items-center gap-1 h-auto py-2 px-3 min-w-[100px]"
>
<Mail className="h-4 w-4" />
Отправить на почту
<span className="text-xs leading-tight text-center">
Отправить на почту
</span>
</Button>
</div>
</form>
</Form>
</div>
)}
{/* Email Dialog */}
<EmailDialog
isOpen={isEmailDialogOpen}
onClose={() => setIsEmailDialogOpen(false)}
onSubmit={handleEmailSubmit}
isLoading={isLoading}
title={emailDialogData?.defaultSubject || 'Отправить отчет на почту'}
description="Заполните данные для отправки отчета"
defaultSubject={
emailDialogData?.defaultSubject || 'Отчет из банковской системы'
}
defaultBody={
emailDialogData?.defaultBody ||
'Во вложении находится запрошенный отчет.'
}
/>
</div>
);
};

View File

@@ -191,11 +191,13 @@ export const Reports = (): React.JSX.Element => {
type: string,
data: Record<string, unknown>,
email: string,
subject: string,
body: string,
) => {
if (type === 'deposits-pdf') {
const { fromDate, toDate } = data as { fromDate: string; toDate: string };
sendDepositsPdfReport(
{ fromDate, toDate, email },
{ fromDate, toDate, email, subject, body },
{
onSuccess: () => {
toast.success(`PDF отчет успешно отправлен на ${email}`);
@@ -209,7 +211,7 @@ export const Reports = (): React.JSX.Element => {
} else if (type === 'creditPrograms-word') {
const { creditProgramIds } = data as { creditProgramIds: string[] };
sendCreditProgramsWordReport(
{ creditProgramIds, email },
{ creditProgramIds, email, subject, body },
{
onSuccess: () => {
toast.success(`Word отчет успешно отправлен на ${email}`);
@@ -223,7 +225,7 @@ export const Reports = (): React.JSX.Element => {
} else if (type === 'creditPrograms-excel') {
const { creditProgramIds } = data as { creditProgramIds: string[] };
sendCreditProgramsExcelReport(
{ creditProgramIds, email },
{ creditProgramIds, email, subject, body },
{
onSuccess: () => {
toast.success(`Excel отчет успешно отправлен на ${email}`);

View File

@@ -0,0 +1,22 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}
{...props}
/>
);
},
);
Textarea.displayName = 'Textarea';
export { Textarea };

View File

@@ -28,11 +28,22 @@ export const useReports = () => {
fromDate,
toDate,
email,
subject,
body,
}: {
fromDate: string;
toDate: string;
email: string;
}) => reportsApi.sendDepositsPdfReport(fromDate, toDate, email),
subject: string;
body: string;
}) =>
reportsApi.sendDepositsPdfReport(
fromDate,
toDate,
email,
subject,
body,
),
});
// Word отчеты по кредитным программам
@@ -63,10 +74,20 @@ export const useReports = () => {
mutationFn: ({
creditProgramIds,
email,
subject,
body,
}: {
creditProgramIds: string[];
email: string;
}) => reportsApi.sendCreditProgramsWordReport(creditProgramIds, email),
subject: string;
body: string;
}) =>
reportsApi.sendCreditProgramsWordReport(
creditProgramIds,
email,
subject,
body,
),
});
// Excel отчеты по кредитным программам
@@ -89,10 +110,20 @@ export const useReports = () => {
mutationFn: ({
creditProgramIds,
email,
subject,
body,
}: {
creditProgramIds: string[];
email: string;
}) => reportsApi.sendCreditProgramsExcelReport(creditProgramIds, email),
subject: string;
body: string;
}) =>
reportsApi.sendCreditProgramsExcelReport(
creditProgramIds,
email,
subject,
body,
),
});
return {