Files
portal/app/resources/js/api/sales.ts
T
Дмитрий a85148555c feat(sales): экран «Мои клиенты» (фронт)
Task 1.3b: SalesClientsView — таблица 11 колонок по демо #page-clients (Клиент/Тип/Активность/Баланс/Запас/Проектов/Пришло/Оборот/Тариф/Заработал/Статус), бейдж типа лица, чипы статуса (Триал/Активен/Просрочка/Приостановлен), HelpHint на терминах, деньги ru-RU + JetBrains Mono, перезагрузка при смене периода, клик по строке → карточка. Заработал=«—» до Фазы 3. listSalesClients в api/sales.ts. Vitest 6/6, lint/type-check 0. Один эскейп на сессию.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 06:40:56 +03:00

145 lines
4.8 KiB
TypeScript

import axios from 'axios';
/**
* API-клиент для портала отдела продаж (/api/sales/*).
*
* Использует Bearer-токен из salesAuth store (localStorage 'sales_token').
* НЕ использует Sanctum cookie/CSRF — это отдельный auth через токен.
*
* Base path: /api/sales
*/
export interface SalesUser {
id: number;
name: string;
email: string;
role: 'manager' | 'head';
}
export interface SalesLoginResponse {
token: string;
user: SalesUser;
}
// ─── helpers ────────────────────────────────────────────────────────────────
function getToken(): string | null {
try {
return localStorage.getItem('sales_token');
} catch {
return null;
}
}
function authHeaders(): Record<string, string> {
const token = getToken();
return token ? { Authorization: `Bearer ${token}` } : {};
}
/**
* Извлекает читаемое сообщение об ошибке из ответа API.
*/
export function extractSalesErrorMessage(error: unknown, fallback = 'Произошла ошибка. Попробуйте позже.'): string {
if (axios.isAxiosError(error)) {
const data = error.response?.data as { message?: string } | undefined;
if (data?.message) return data.message;
if (error.response?.status === 401) return 'Неверный email или пароль.';
if (error.response?.status === 403) return 'Нет прав на это действие.';
if (error.response?.status === 422) {
const errData = error.response.data as { errors?: Record<string, string[]> } | undefined;
const firstField = errData?.errors ? Object.values(errData.errors)[0] : undefined;
if (firstField?.[0]) return firstField[0];
}
if (error.response?.status === 500) return 'Внутренняя ошибка сервера.';
}
return fallback;
}
// ─── types ───────────────────────────────────────────────────────────────────
export interface SalesClientRow {
tenant_id: number;
organization_name: string;
inn: string | null;
subject_type: string | null;
last_activity_at: string | null; // ISO datetime or null
balance_rub: string;
status: 'trial' | 'suspended' | 'overdue' | 'active' | string;
tariff_name: string | null;
projects_count: number;
runway_days: number | null;
leads_delivered: number;
oborot_rub: number;
earned_rub: null;
}
export interface SalesClientsParams {
period: string;
from?: string;
to?: string;
search?: string;
}
// ─── auth endpoints ──────────────────────────────────────────────────────────
/**
* POST /api/sales/auth/login → { token, user }
*/
export async function salesLogin(email: string, password: string): Promise<SalesLoginResponse> {
const { data } = await axios.post<SalesLoginResponse>(
'/api/sales/auth/login',
{ email, password },
{ headers: { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest' } },
);
return data;
}
/**
* GET /api/sales/auth/me (Bearer) → { id, name, email, role }
*/
export async function salesMe(): Promise<SalesUser> {
const { data } = await axios.get<SalesUser>('/api/sales/auth/me', {
headers: {
Accept: 'application/json',
'X-Requested-With': 'XMLHttpRequest',
...authHeaders(),
},
});
return data;
}
/**
* POST /api/sales/auth/logout (Bearer)
*/
export async function salesLogout(): Promise<void> {
await axios.post(
'/api/sales/auth/logout',
{},
{
headers: {
Accept: 'application/json',
'X-Requested-With': 'XMLHttpRequest',
...authHeaders(),
},
},
);
}
// ─── clients endpoint ─────────────────────────────────────────────────────────
/**
* GET /api/sales/clients?period=...&from=...&to=...&search=... (Bearer)
* → { data: SalesClientRow[] }
*/
export async function listSalesClients(params: SalesClientsParams): Promise<SalesClientRow[]> {
const { data } = await axios.get<{ data: SalesClientRow[] }>('/api/sales/clients', {
params,
headers: {
Accept: 'application/json',
'X-Requested-With': 'XMLHttpRequest',
...authHeaders(),
},
});
return data.data;
}