feat: clerk ui
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 параметры
|
||||
}
|
||||
@@ -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 отчет успешно отправлен на почту");
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
};
|
||||
|
||||
159
TheBank/bankuiclerk/src/components/features/EmailDialog.tsx
Normal file
159
TheBank/bankuiclerk/src/components/features/EmailDialog.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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')}
|
||||
>
|
||||
Отчеты по кредитным программам
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
22
TheBank/bankuiclerk/src/components/ui/textarea.tsx
Normal file
22
TheBank/bankuiclerk/src/components/ui/textarea.tsx
Normal 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 };
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user