diff --git a/projects/social_platform/src/app/ui/pages/program/management/management.component.html b/projects/social_platform/src/app/ui/pages/program/management/management.component.html index 83c393718..caafe8926 100644 --- a/projects/social_platform/src/app/ui/pages/program/management/management.component.html +++ b/projects/social_platform/src/app/ui/pages/program/management/management.component.html @@ -820,7 +820,12 @@

Календарь программы

type="datetime-local" [id]="field.key" [formControlName]="field.key" + [attr.aria-invalid]="dateError(field.key) ? 'true' : null" + [attr.aria-describedby]="dateError(field.key) ? field.key + '-error' : null" /> + @if (dateError(field.key); as error) { +

{{ error }}

+ } }

Если отдельный срок сдачи не задан, действует срок окончания регистрации. diff --git a/projects/social_platform/src/app/ui/pages/program/management/management.component.spec.ts b/projects/social_platform/src/app/ui/pages/program/management/management.component.spec.ts index 5b007eadc..3aeaff9b1 100644 --- a/projects/social_platform/src/app/ui/pages/program/management/management.component.spec.ts +++ b/projects/social_platform/src/app/ui/pages/program/management/management.component.spec.ts @@ -113,6 +113,49 @@ describe("Сохранение настроек организатора", () => expect(component.basicForm.dirty).toBe(true); expect(component.formError()).toContain("Данные изменились"); }); + it.each(["2026-09-30T18:00", "2026-10-01T15:00", "2026-10-01T15:00:00"])( + "не отправляет несогласованные сроки и сохраняет введенное окончание %s", + finish => { + const { component, save } = setup(); + component.settingsTab.set("dates"); + component.datesForm.controls.datetimeFinished.setValue(finish); + component.datesForm.controls.datetimeFinished.markAsDirty(); + component.save(); + expect(save).not.toHaveBeenCalled(); + expect(component.dateError("datetimeFinished")).toContain("позже начала"); + expect(component.dateError("datetimeRegistrationEnds")).toContain("не позже окончания"); + expect(component.datesForm.controls.datetimeFinished.value).toBe(finish); + expect(component.datesForm.dirty).toBe(true); + }, + ); + it("разрешает регистрацию после старта вплоть до окончания программы", () => { + const { component, save } = setup(); + component.settingsTab.set("dates"); + const registration = component.datesForm.controls.datetimeRegistrationEnds; + registration.setValue(component.datesForm.controls.datetimeFinished.value); + registration.markAsDirty(); + component.save(); + expect(component.dateError("datetimeRegistrationEnds")).toBeNull(); + expect(save).toHaveBeenCalledWith(1, "dates", { + expectedUpdatedAt: program.updatedAt, + confirmReopen: false, + datetimeRegistrationEnds: new Date(program.datetimeFinished!).toISOString(), + }); + }); + it("не требует полного календаря черновика", () => { + const { component, state, save } = setup(); + state.program.set({ ...program, draft: true }); + component.resetForms(); + component.settingsTab.set("dates"); + component.datesForm.controls.datetimeFinished.setValue(""); + component.datesForm.controls.datetimeFinished.markAsDirty(); + component.save(); + expect(save).toHaveBeenCalledWith(1, "dates", { + expectedUpdatedAt: program.updatedAt, + confirmReopen: false, + datetimeFinished: null, + }); + }); it("сохраняет mobile URL и полный framing; 409 оставляет кадр для повторной попытки", () => { const { component, save } = setup(); component.settingsTab.set("basic"); diff --git a/projects/social_platform/src/app/ui/pages/program/management/management.component.ts b/projects/social_platform/src/app/ui/pages/program/management/management.component.ts index 4171a8935..4fd70c930 100644 --- a/projects/social_platform/src/app/ui/pages/program/management/management.component.ts +++ b/projects/social_platform/src/app/ui/pages/program/management/management.component.ts @@ -1197,6 +1197,18 @@ export class ProgramManagementComponent implements OnInit { control.updateValueAndValidity({ emitEvent: false }); } } + dateError(field: string): string | null { + const values = this.datesForm.getRawValue(); + const timestamp = (value: string | null) => (value ? Date.parse(`${value}+03:00`) : Number.NaN); + const start = timestamp(values.datetimeStarted), + finish = timestamp(values.datetimeFinished), + registration = timestamp(values.datetimeRegistrationEnds); + if (field === "datetimeFinished" && finish <= start) + return "Окончание программы должно быть позже начала."; + if (field === "datetimeRegistrationEnds" && registration > finish) + return "Регистрация должна завершиться не позже окончания программы."; + return null; + } save(confirmReopen = false): void { const p = this.state.program(), section = this.settingsTab(); @@ -1212,9 +1224,13 @@ export class ProgramManagementComponent implements OnInit { ) return; const form = section === "basic" ? this.basicForm : this.datesForm; - if (form.invalid) { + const dateError = + section === "dates" + ? this.dateFields.map(field => this.dateError(field.key)).find(Boolean) + : null; + if (form.invalid || dateError) { form.markAllAsTouched(); - this.formError.set("Заполните обязательные поля."); + this.formError.set(dateError || "Заполните обязательные поля."); return; } const body: ManagementSettingsWrite = { expectedUpdatedAt: p.updatedAt, confirmReopen }; diff --git a/projects/social_platform/src/app/ui/pages/program/management/solution-form/solution-form.component.html b/projects/social_platform/src/app/ui/pages/program/management/solution-form/solution-form.component.html index fdf453c32..551dba054 100644 --- a/projects/social_platform/src/app/ui/pages/program/management/solution-form/solution-form.component.html +++ b/projects/social_platform/src/app/ui/pages/program/management/solution-form/solution-form.component.html @@ -106,6 +106,9 @@

{{ field.label }}{{ field.required ? " *" : "" }}

добавьте его заново.

} + @if (optionsProblem(field); as problem) { +

{{ problem }}

+ }
+ @if (appWidth >= 1000 && listType() === "program" && !publicationsOnly()) { +

{{ info().name }}

+ } + @if (appWidth >= 1000 && listType() === "project") {

{{ info().name }}

} diff --git a/projects/social_platform/src/app/ui/widgets/detail/detail.component.scss b/projects/social_platform/src/app/ui/widgets/detail/detail.component.scss index 2005f167f..c495df4c0 100644 --- a/projects/social_platform/src/app/ui/widgets/detail/detail.component.scss +++ b/projects/social_platform/src/app/ui/widgets/detail/detail.component.scss @@ -181,6 +181,12 @@ $detail-bar-mb: 12px; overflow-wrap: anywhere; } + &__program-heading { + display: block; + margin-bottom: 20px; + overflow-wrap: anywhere; + } + &__cover-frame { position: absolute; inset: 0;