# Руководство по API для фронтенда: Генерация и история отчётов

## Базовый URL

```
/api/parser/report
```

## 1. Генерация отчёта

### POST /api/parser/report/generate

Создаёт новый отчёт и возвращает файл для скачивания.

**Заголовки:**

```
Content-Type: application/json
```

**Тело запроса:**

```json
{
    "reportTitle": "Еженедельный анализ новостного фона",
    "authorName": "Имя Аналитика",
    "companyName": "Название Компании Клиента",
    "startDate": "2025-09-10T00:00:00",
    "endDate": "2025-09-17T23:59:59",
    "format": "PDF"
}
```

**Параметры:**

-   `reportTitle` (string, обязательный) - заголовок отчёта
-   `authorName` (string, обязательный) - имя автора
-   `companyName` (string, обязательный) - название компании
-   `startDate` (string, обязательный) - дата начала периода в формате ISO 8601
-   `endDate` (string, обязательный) - дата окончания периода в формате ISO 8601
-   `format` (string, обязательный) - формат файла: "PDF" или "DOCX"

**Успешный ответ (200 OK):**

```
Content-Type: application/pdf (или application/vnd.openxmlformats-officedocument.wordprocessingml.document)
Content-Disposition: attachment; filename="report_2025-09-17-1234567890.pdf"
[бинарные данные файла]
```

**Пример на JavaScript:**

```javascript
async function generateReport(reportData) {
    try {
        const response = await fetch('/api/parser/report/generate', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(reportData)
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        // Получаем имя файла из заголовка
        const contentDisposition = response.headers.get('Content-Disposition');
        const filename = contentDisposition ? contentDisposition.split('filename=')[1].replace(/"/g, '') : 'report.pdf';

        // Создаём blob и скачиваем файл
        const blob = await response.blob();
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        window.URL.revokeObjectURL(url);
        document.body.removeChild(a);
    } catch (error) {
        console.error('Ошибка при генерации отчёта:', error);
    }
}

// Использование
const reportData = {
    reportTitle: 'Еженедельный анализ новостного фона',
    authorName: 'Иван Петров',
    companyName: 'ООО Рога и Копыта',
    startDate: '2025-01-01T00:00:00',
    endDate: '2025-01-07T23:59:59',
    format: 'PDF'
};

generateReport(reportData);
```

## 2. Получение истории отчётов

### GET /api/parser/report/history

Возвращает пагинированный список всех сгенерированных отчётов.

**Параметры запроса:**

-   `page` (int, опциональный, по умолчанию: 0) - номер страницы (начиная с 0)
-   `size` (int, опциональный, по умолчанию: 20) - количество элементов на странице
-   `sort` (string, опциональный, по умолчанию: "createdAt,desc") - поле и направление сортировки

**Пример запроса:**

```
GET /api/parser/report/history?page=0&size=10&sort=createdAt,desc
```

**Успешный ответ (200 OK):**

```json
{
    "content": [
        {
            "id": "507f1f77bcf86cd799439011",
            "reportTitle": "Еженедельный анализ новостного фона",
            "authorName": "Имя Аналитика",
            "companyName": "Название Компании Клиента",
            "startDate": "2025-09-10T00:00:00",
            "endDate": "2025-09-17T23:59:59",
            "format": "PDF",
            "filename": "report_2025-09-17-1234567890.pdf",
            "fileSize": 1234567,
            "createdAt": "2025-09-17T21:11:58"
        }
    ],
    "pageable": {
        "sort": {
            "sorted": true,
            "unsorted": false,
            "empty": false
        },
        "pageNumber": 0,
        "pageSize": 20,
        "offset": 0,
        "paged": true,
        "unpaged": false
    },
    "totalElements": 1,
    "totalPages": 1,
    "last": true,
    "first": true,
    "numberOfElements": 1,
    "size": 20,
    "number": 0,
    "sort": {
        "sorted": true,
        "unsorted": false,
        "empty": false
    },
    "empty": false
}
```

**Пример на JavaScript:**

```javascript
async function getReportHistory(page = 0, size = 20, sort = 'createdAt,desc') {
    try {
        const params = new URLSearchParams({
            page: page.toString(),
            size: size.toString(),
            sort: sort
        });

        const response = await fetch(`/api/parser/report/history?${params}`);

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Ошибка при получении истории отчётов:', error);
        throw error;
    }
}

// Использование
getReportHistory(0, 10, 'createdAt,desc')
    .then((data) => {
        console.log('Отчёты:', data.content);
        console.log('Всего элементов:', data.totalElements);
        console.log('Всего страниц:', data.totalPages);
    })
    .catch((error) => {
        console.error('Ошибка:', error);
    });
```

## 3. Скачивание отчёта из истории

### GET /api/parser/report/history/{id}

Скачивает конкретный отчёт по его ID.

**Параметры:**

-   `id` (string, обязательный) - ID отчёта из истории

**Успешный ответ (200 OK):**

```
Content-Type: application/pdf (или application/vnd.openxmlformats-officedocument.wordprocessingml.document)
Content-Disposition: attachment; filename="report_2025-09-17-1234567890.pdf"
[бинарные данные файла]
```

**Ошибки:**

-   `404 Not Found` - отчёт с указанным ID не найден
-   `500 Internal Server Error` - ошибка при чтении файла

**Пример на JavaScript:**

```javascript
async function downloadReport(reportId) {
    try {
        const response = await fetch(`/api/parser/report/history/${reportId}`);

        if (response.status === 404) {
            throw new Error('Отчёт не найден');
        }

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        // Получаем имя файла из заголовка
        const contentDisposition = response.headers.get('Content-Disposition');
        const filename = contentDisposition ? contentDisposition.split('filename=')[1].replace(/"/g, '') : 'report.pdf';

        // Создаём blob и скачиваем файл
        const blob = await response.blob();
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        window.URL.revokeObjectURL(url);
        document.body.removeChild(a);
    } catch (error) {
        console.error('Ошибка при скачивании отчёта:', error);
        throw error;
    }
}

// Использование
downloadReport('507f1f77bcf86cd799439011')
    .then(() => console.log('Отчёт скачан'))
    .catch((error) => console.error('Ошибка:', error));
```

## 4. Полный пример React компонента

```jsx
import React, { useState, useEffect } from 'react';

const ReportManager = () => {
    const [reports, setReports] = useState([]);
    const [loading, setLoading] = useState(false);
    const [currentPage, setCurrentPage] = useState(0);
    const [totalPages, setTotalPages] = useState(0);
    const [formData, setFormData] = useState({
        reportTitle: '',
        authorName: '',
        companyName: '',
        startDate: '',
        endDate: '',
        format: 'PDF'
    });

    // Загрузка истории отчётов
    const loadReports = async (page = 0) => {
        setLoading(true);
        try {
            const data = await getReportHistory(page, 10);
            setReports(data.content);
            setTotalPages(data.totalPages);
            setCurrentPage(page);
        } catch (error) {
            console.error('Ошибка загрузки истории:', error);
        } finally {
            setLoading(false);
        }
    };

    // Генерация нового отчёта
    const handleGenerate = async (e) => {
        e.preventDefault();
        setLoading(true);
        try {
            await generateReport(formData);
            // Перезагружаем историю после генерации
            await loadReports(currentPage);
        } catch (error) {
            console.error('Ошибка генерации:', error);
        } finally {
            setLoading(false);
        }
    };

    // Скачивание отчёта
    const handleDownload = async (reportId) => {
        try {
            await downloadReport(reportId);
        } catch (error) {
            console.error('Ошибка скачивания:', error);
        }
    };

    useEffect(() => {
        loadReports();
    }, []);

    return (
        <div className="report-manager">
            <h2>Генерация отчётов</h2>

            {/* Форма генерации */}
            <form onSubmit={handleGenerate} className="report-form">
                <div>
                    <label>Заголовок отчёта:</label>
                    <input type="text" value={formData.reportTitle} onChange={(e) => setFormData({ ...formData, reportTitle: e.target.value })} required />
                </div>

                <div>
                    <label>Автор:</label>
                    <input type="text" value={formData.authorName} onChange={(e) => setFormData({ ...formData, authorName: e.target.value })} required />
                </div>

                <div>
                    <label>Компания:</label>
                    <input type="text" value={formData.companyName} onChange={(e) => setFormData({ ...formData, companyName: e.target.value })} required />
                </div>

                <div>
                    <label>Дата начала:</label>
                    <input type="datetime-local" value={formData.startDate} onChange={(e) => setFormData({ ...formData, startDate: e.target.value })} required />
                </div>

                <div>
                    <label>Дата окончания:</label>
                    <input type="datetime-local" value={formData.endDate} onChange={(e) => setFormData({ ...formData, endDate: e.target.value })} required />
                </div>

                <div>
                    <label>Формат:</label>
                    <select value={formData.format} onChange={(e) => setFormData({ ...formData, format: e.target.value })}>
                        <option value="PDF">PDF</option>
                        <option value="DOCX">DOCX</option>
                    </select>
                </div>

                <button type="submit" disabled={loading}>
                    {loading ? 'Генерация...' : 'Сгенерировать отчёт'}
                </button>
            </form>

            {/* История отчётов */}
            <h2>История отчётов</h2>

            {loading ? (
                <div>Загрузка...</div>
            ) : (
                <>
                    <div className="reports-list">
                        {reports.map((report) => (
                            <div key={report.id} className="report-item">
                                <h3>{report.reportTitle}</h3>
                                <p>Автор: {report.authorName}</p>
                                <p>Компания: {report.companyName}</p>
                                <p>
                                    Период: {new Date(report.startDate).toLocaleDateString()} - {new Date(report.endDate).toLocaleDateString()}
                                </p>
                                <p>Формат: {report.format}</p>
                                <p>Размер: {(report.fileSize / 1024).toFixed(1)} KB</p>
                                <p>Создан: {new Date(report.createdAt).toLocaleString()}</p>
                                <button onClick={() => handleDownload(report.id)}>Скачать</button>
                            </div>
                        ))}
                    </div>

                    {/* Пагинация */}
                    <div className="pagination">
                        <button onClick={() => loadReports(currentPage - 1)} disabled={currentPage === 0}>
                            Предыдущая
                        </button>
                        <span>
                            Страница {currentPage + 1} из {totalPages}
                        </span>
                        <button onClick={() => loadReports(currentPage + 1)} disabled={currentPage >= totalPages - 1}>
                            Следующая
                        </button>
                    </div>
                </>
            )}
        </div>
    );
};

export default ReportManager;
```

## 5. Обработка ошибок

### Типичные ошибки и их обработка:

1. **400 Bad Request** - неверные данные в запросе
2. **404 Not Found** - отчёт не найден (при скачивании)
3. **500 Internal Server Error** - внутренняя ошибка сервера

```javascript
async function handleApiCall(apiFunction, ...args) {
    try {
        return await apiFunction(...args);
    } catch (error) {
        if (error.message.includes('404')) {
            alert('Отчёт не найден');
        } else if (error.message.includes('400')) {
            alert('Неверные данные запроса');
        } else {
            alert('Произошла ошибка: ' + error.message);
        }
        throw error;
    }
}
```

## 6. Форматы дат

Все даты должны передаваться в формате ISO 8601:

-   `2025-01-01T00:00:00` - 1 января 2025, 00:00:00
-   `2025-01-01T23:59:59` - 1 января 2025, 23:59:59

Для HTML input[type="datetime-local"] используйте формат:

-   `2025-01-01T00:00` (без секунд и часового пояса)

## 7. Размеры файлов

Размеры файлов возвращаются в байтах. Для отображения пользователю конвертируйте:

```javascript
function formatFileSize(bytes) {
    if (bytes === 0) return '0 Bytes';
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
```
