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;