// Базовый URL API const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api'; const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:8000'; // Типы для API export interface Address { address_line1: string; address_line2?: string; city: string; postal_code: string; country: string; } export interface ApiResponse { data: T; success: boolean; error?: string; } // Базовая функция для выполнения запросов export async function fetchApi( endpoint: string, options: RequestInit = {} ): Promise> { try { const url = `${API_URL}${endpoint}`; console.log(`API запрос: ${options.method || 'GET'} ${url}`); // Добавляем заголовки по умолчанию const headers: Record = { ...(options.headers as Record || {}), }; // Content-Type добавляем только если это не FormData if (!(options.body instanceof FormData)) { headers['Content-Type'] = 'application/json'; } else { console.log('Отправка FormData, Content-Type не устанавливается'); } // Получаем токен из localStorage (только на клиенте) if (typeof window !== 'undefined') { const token = localStorage.getItem('token'); if (token) { headers['Authorization'] = `Bearer ${token}`; } } // Логируем заголовки для отладки console.log('Заголовки запроса:', headers); const response = await fetch(url, { ...options, headers, }); // Логируем ответ console.log(`Ответ API: ${response.status} ${response.statusText}`); // Проверяем статус ответа на ошибки аутентификации if (response.status === 401 || response.status === 403) { console.error("Ошибка аутентификации:", response.status); // Очищаем токен при ошибке аутентификации if (typeof window !== 'undefined') { localStorage.removeItem('token'); // Если это не запрос на вход, перенаправляем на страницу входа if (!endpoint.includes('/auth/login')) { console.log("Перенаправление на страницу входа из-за ошибки аутентификации"); window.location.href = '/admin/login'; } } } // Пытаемся распарсить ответ как JSON let data; const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { data = await response.json(); console.log('Полученные данные:', data); // Если ответ содержит обертку data.product, извлекаем данные из нее if (data && typeof data === 'object') { if (data.product) { data = data.product; } else if (data.success && data.data) { data = data.data; } } } return { data, success: response.status === 200, error: response.ok ? undefined : data?.error || 'Неизвестная ошибка', }; } catch (error) { console.error("API Error:", error); return { data: null as unknown as T, success: false, error: error instanceof Error ? error.message : 'Неизвестная ошибка', }; } } // Типы для админки export interface DashboardStats { ordersCount: number; totalSales: number; customersCount: number; productsCount: number; } export interface Order { id: number; user_id: number; user_name?: string; user_email?: string; status: string; total: number; created_at: string; updated_at?: string; items_count: number; items?: OrderItem[]; shipping_address?: Address | string; payment_method?: string; tracking_number?: string; notes?: string; } export interface OrderItem { id: number; order_id: number; product_id: number; variant_id?: number; quantity: number; price: number; product: { id: number; name: string; image?: string; sku?: string; }; variant_name?: string; } export interface Product { id: number; name: string; slug: string; description?: string; price: number; discount_price?: number | null; care_instructions?: Record | string | null; is_active: boolean; category_id?: number; collection_id?: number; stock?: number; category?: { id: number; name: string; }; collection?: { id: number; name: string; }; images?: Array; variants?: Array<{ id: number; size_id: number; stock: number; sku?: string; is_active?: boolean; size?: { id: number; name: string; code: string; }; }>; created_at?: string; updated_at?: string; } export interface ProductVariant { id: number; product_id: number; size_id: number; sku: string; stock: number; is_active?: boolean; size?: { id: number; name: string; code?: string; }; } export interface ProductsResponse { items: Product[]; total: number; total_pages: number; page: number; } export interface Size { id: number; name: string; code: string; description?: string; } // Функции API для админки export async function fetchDashboardStats(): Promise> { return fetchApi('/analytics/dashboard/stats'); } export async function fetchRecentOrders(params: { limit?: number } = {}): Promise> { const queryParams = new URLSearchParams(); if (params.limit) { queryParams.append('limit', params.limit.toString()); } return fetchApi(`/orders?${queryParams.toString()}`); } export async function fetchPopularProducts(params: { limit?: number } = {}): Promise> { const queryParams = new URLSearchParams(); if (params.limit) { queryParams.append('limit', params.limit.toString()); } return fetchApi(`/catalog/products/popular?${queryParams.toString()}`); } // Функции API для работы с товарами export async function fetchProducts(params: { page?: number; limit?: number; search?: string; category_id?: number; } = {}): Promise> { const queryParams = new URLSearchParams(); if (params.page) { queryParams.append('page', params.page.toString()); } if (params.limit) { queryParams.append('limit', params.limit.toString()); } if (params.search) { queryParams.append('search', params.search); } if (params.category_id) { queryParams.append('category_id', params.category_id.toString()); } return fetchApi(`/catalog/products?${queryParams.toString()}`); } // Функция для получения данных о продукте (по ID или slug) export async function fetchProduct(idOrSlug: string | number): Promise> { let endpoint; if (typeof idOrSlug === 'number' || !isNaN(Number(idOrSlug))) { // Если передан числовой ID endpoint = `/catalog/products/${idOrSlug}`; } else { // Если передан строковый slug endpoint = `/catalog/products/slug/${idOrSlug}`; } return fetchApi(endpoint); } export async function createProduct(data: Partial): Promise> { return fetchApi('/catalog/products', { method: 'POST', body: JSON.stringify(data), }); } export async function updateProduct(id: number, data: Partial): Promise> { return fetchApi(`/catalog/products/${id}`, { method: 'PUT', body: JSON.stringify(data), }); } export async function deleteProduct(id: number): Promise> { return fetchApi(`/catalog/products/${id}`, { method: 'DELETE', }); } // Функция для получения списка размеров export async function fetchSizes(): Promise> { return fetchApi('/catalog/sizes'); } // Утилиты для работы с URL изображений export function getImageUrl(imagePath: string): string { if (!imagePath) return ''; // Если путь уже содержит протокол (http/https) или абсолютный URL, возвращаем как есть if (imagePath.startsWith('http://') || imagePath.startsWith('https://') || imagePath.startsWith('//')) { return imagePath; } // Если путь начинается с '/', то это относительный путь от корня сервера if (imagePath.startsWith('/')) { return `${BASE_URL}${imagePath}`; } // В противном случае это частично путь, добавляем базовый URL return `${BASE_URL}/${imagePath}`; } // Функция для загрузки изображения на сервер export async function uploadProductImage(productId: number, file: File): Promise> { const formData = new FormData(); // Имя поля file должно совпадать с ожидаемым именем на сервере formData.append('file', file); formData.append('is_primary', 'false'); // Логируем содержимое FormData для отладки console.log('Загрузка изображения:', { productId, fileName: file.name, fileType: file.type, fileSize: file.size, formDataFields: ['file', 'is_primary'] }); return fetchApi<{id: number; image_url: string; is_primary: boolean}>(`/catalog/products/${productId}/images`, { method: 'POST', body: formData, // Не устанавливаем Content-Type для FormData - fetchApi автоматически его пропустит }); } // Функция для установки главного изображения товара export async function setProductImageAsPrimary(productId: number, imageId: number): Promise> { return fetchApi(`/catalog/products/${productId}/images/${imageId}/primary`, { method: 'PUT', body: JSON.stringify({ is_primary: true }) }); } // Функция для удаления изображения товара export async function deleteProductImage(productId: number, imageId: number): Promise> { return fetchApi(`/catalog/products/${productId}/images/${imageId}`, { method: 'DELETE' }); } // Функции API для работы с заказами export async function fetchOrders(params: { skip?: number; limit?: number; status?: string; dateFrom?: string; dateTo?: string; search?: string; } = {}): Promise> { const queryParams = new URLSearchParams(); if (params.skip) queryParams.append('skip', params.skip.toString()); if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.status) queryParams.append('status', params.status); if (params.dateFrom) queryParams.append('date_from', params.dateFrom); if (params.dateTo) queryParams.append('date_to', params.dateTo); if (params.search) queryParams.append('search', params.search); return fetchApi(`/orders?${queryParams.toString()}`); } export async function fetchOrder(id: number): Promise> { return fetchApi(`/orders/${id}`); } export async function updateOrder(id: number, data: Partial): Promise> { return fetchApi(`/orders/${id}`, { method: 'PUT', body: JSON.stringify(data), }); } export async function cancelOrder(id: number): Promise> { return fetchApi(`/orders/${id}/cancel`, { method: 'POST', }); } // Создаем класс api для обеспечения обратной совместимости const api = { get: async (endpoint: string, options: any = {}): Promise<{ data: T }> => { const params = options.params ? new URLSearchParams(Object.entries(options.params) .filter(([_, v]) => v !== undefined) .map(([k, v]) => [k, String(v)])) : new URLSearchParams(); const queryString = params.toString(); const url = queryString ? `${endpoint}?${queryString}` : endpoint; const response = await fetchApi(url); return { data: response.data }; }, post: async (endpoint: string, data: any, options: any = {}): Promise<{ data: T }> => { const response = await fetchApi(endpoint, { method: 'POST', body: JSON.stringify(data), ...options }); return { data: response.data }; }, put: async (endpoint: string, data: any, options: any = {}): Promise<{ data: T }> => { const response = await fetchApi(endpoint, { method: 'PUT', body: JSON.stringify(data), ...options }); return { data: response.data }; }, delete: async (endpoint: string, options: any = {}): Promise<{ data: T }> => { const response = await fetchApi(endpoint, { method: 'DELETE', ...options }); return { data: response.data }; } }; // Экспортируем API класс export default api;