fix(deals): читать проекты из конверта { data } + чинить фикстуры LeadStatus

DealsView крашился (Cannot read properties of undefined reading 'map'): listProjects() читал data.projects, но ProjectController::index() отдаёт { data: [...] } после миграции на JsonResource — availableProjects=undefined ломал .map, фильтр «Проект» был пуст. Фикс: читать data.data ?? []. + deals-api.spec.ts тест на новый конверт + защитный []. + DealDetailHero.spec.ts: фикстуры LeadStatus (isSystem/sortOrder вместо order) — устранён pre-existing type-check error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-05-20 15:20:53 +03:00
parent e35fc6c938
commit c7fd90c08d
3 changed files with 19 additions and 8 deletions
+4 -2
View File
@@ -260,10 +260,12 @@ export interface ApiProject {
}
export async function listProjects(tenantId: number): Promise<ApiProject[]> {
const { data } = await apiClient.get<{ projects: ApiProject[] }>('/api/projects', {
// ProjectController::index() отдаёт { data: ProjectResource::collection(...) }.
// `?? []` — защита от undefined.map в DealsView при нештатном ответе.
const { data } = await apiClient.get<{ data: ApiProject[] }>('/api/projects', {
params: { tenant_id: tenantId },
});
return data.projects;
return data.data ?? [];
}
/**
+3 -3
View File
@@ -8,9 +8,9 @@ import type { LeadStatus } from '../../resources/js/composables/leadStatuses';
const vuetify = createVuetify();
const statuses: LeadStatus[] = [
{ slug: 'new', nameRu: 'Новая сделка', colorHex: '#5b2db2', order: 1 } as LeadStatus,
{ slug: 'viewed', nameRu: 'Просмотрено', colorHex: '#5a2db2', order: 2 } as LeadStatus,
{ slug: 'won', nameRu: 'Куплено', colorHex: '#00A36C', order: 3 } as LeadStatus,
{ slug: 'new', nameRu: 'Новая сделка', isSystem: true, sortOrder: 1, colorHex: '#5b2db2' },
{ slug: 'viewed', nameRu: 'Просмотрено', isSystem: true, sortOrder: 2, colorHex: '#5a2db2' },
{ slug: 'won', nameRu: 'Куплено', isSystem: true, sortOrder: 3, colorHex: '#00A36C' },
];
function makeDeal(over: Partial<MockDeal> = {}): MockDeal {
+12 -3
View File
@@ -168,12 +168,21 @@ describe('api/deals', () => {
expect(r).toHaveLength(1);
});
it('listProjects() GET /api/projects + unwraps data.projects', async () => {
it('listProjects() GET /api/projects + unwraps { data: [...] } (JsonResource collection)', async () => {
// ProjectController::index() отдаёт response()->json(['data' => ProjectResource::collection(...)]).
vi.mocked(apiClient.get).mockResolvedValue({
data: { projects: [{ id: 1, name: 'P', tag: 'site', type: 'webhook' }] },
data: { data: [{ id: 1, name: 'B1_Окна СПб' }, { id: 2, name: 'B2_Двери' }] },
});
const r = await listProjects(1);
expect(apiClient.get).toHaveBeenCalledWith('/api/projects', { params: { tenant_id: 1 } });
expect(r[0].name).toBe('P');
expect(Array.isArray(r)).toBe(true);
expect(r).toHaveLength(2);
expect(r[0].name).toBe('B1_Окна СПб');
});
it('listProjects() возвращает [] при ответе без массива (защита от undefined.map)', async () => {
vi.mocked(apiClient.get).mockResolvedValue({ data: {} });
const r = await listProjects(1);
expect(r).toEqual([]);
});
});