From 11bc282729b896458169d2ff10907b32bdc85dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Sat, 25 Jul 2026 01:46:43 +0300 Subject: [PATCH] =?UTF-8?q?feat(=D1=80=D0=B5=D0=BA=D0=BB=D0=B0=D0=BC=D0=B0?= =?UTF-8?q?-=D1=84=D1=80=D0=BE=D0=BD=D1=82):=20=D1=88=D0=B0=D0=B3=20=C2=AB?= =?UTF-8?q?=D0=91=D1=8E=D0=B4=D0=B6=D0=B5=D1=82=C2=BB=20(=D0=BD=D0=B5?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9/=D0=B4=D0=BD=D0=B5?= =?UTF-8?q?=D0=B2=D0=BD=D0=BE=D0=B9,=20=D1=82=D0=BE=D0=BB=D1=8C=D0=BA?= =?UTF-8?q?=D0=BE=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B4=D0=B0=20=D0=B1=D0=B5?= =?UTF-8?q?=D0=B7=20=D0=BF=D1=80=D0=BE=D0=B3=D0=BD=D0=BE=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../components/advertising/CampaignWizard.vue | 80 ++++++++++++++- .../advertising-campaign-wizard.spec.ts | 99 +++++++++++++++++-- 2 files changed, 168 insertions(+), 11 deletions(-) diff --git a/app/resources/js/components/advertising/CampaignWizard.vue b/app/resources/js/components/advertising/CampaignWizard.vue index 9b938cc3..4d5ec700 100644 --- a/app/resources/js/components/advertising/CampaignWizard.vue +++ b/app/resources/js/components/advertising/CampaignWizard.vue @@ -21,6 +21,7 @@ import { reactive, ref, computed, watch, onMounted } from 'vue'; import { createCampaign, + patchCampaign, fetchAudienceSize, type CampaignCreate, type AudienceSize, @@ -118,11 +119,50 @@ function removeAd(index: number): void { form.ads.splice(index, 1); } +const budgetError = ref(null); +const budgetSaving = ref(false); + function goBack(): void { step.value = Math.max(1, step.value - 1); } -function goNext(): void { +/** + * Уходя с шага 3 «Бюджет» — сохраняем черновик (Р37: фронт денег не считает, + * передаёт строки как ввёл пользователь). Пустой недельный бюджет блокирует + * переход; ошибка PATCH — тоже блокирует, введённое не теряется (форма не чистится). + */ +async function saveBudgetStep(): Promise { + if (!form.weekly_budget_rub || form.weekly_budget_rub.trim() === '') { + budgetError.value = 'Укажите бюджет в неделю.'; + return false; + } + if (draftId.value === null) { + budgetError.value = 'Черновик кампании ещё не создан.'; + return false; + } + budgetError.value = null; + budgetSaving.value = true; + try { + await patchCampaign(draftId.value, { + weekly_budget_rub: form.weekly_budget_rub, + daily_budget_rub: form.daily_budget_rub || null, + audience_days: form.audience_days, + use_uploaded_list: form.use_uploaded_list, + }); + return true; + } catch (e) { + budgetError.value = extractErrorMessage(e, 'Не удалось сохранить бюджет.'); + return false; + } finally { + budgetSaving.value = false; + } +} + +async function goNext(): Promise { + if (step.value === 3) { + const saved = await saveBudgetStep(); + if (!saved) return; + } step.value = Math.min(stepTitles.length, step.value + 1); } @@ -138,7 +178,7 @@ onMounted(() => { void createDraft(); }); -defineExpose({ form, draftId, audienceSize, canLaunch, refreshAudienceSize }); +defineExpose({ form, draftId, audienceSize, canLaunch, refreshAudienceSize, budgetError });