diff --git a/CourageScores/ClientApp/src/components/league/Division.tsx b/CourageScores/ClientApp/src/components/league/Division.tsx
index a5020bfb5..abab8b894 100644
--- a/CourageScores/ClientApp/src/components/league/Division.tsx
+++ b/CourageScores/ClientApp/src/components/league/Division.tsx
@@ -349,15 +349,16 @@ export function Division() {
try {
return (
-
+
{controls || !divisionDataToUse.season ? (
+ <>
{effectiveTab === 'teams' &&
divisionDataToUse.season ? (
@@ -533,9 +528,9 @@ export function Division() {
)}
/>
) : null}
-
+ >
) : null}
-
+
);
} catch (e) {
/* istanbul ignore next */
diff --git a/CourageScores/ClientApp/src/components/league/EditSeason.test.tsx b/CourageScores/ClientApp/src/components/league/EditSeason.test.tsx
index 1f98fb0fe..117a42f7c 100644
--- a/CourageScores/ClientApp/src/components/league/EditSeason.test.tsx
+++ b/CourageScores/ClientApp/src/components/league/EditSeason.test.tsx
@@ -5,6 +5,7 @@ import {
cleanUp,
ErrorState,
iocProps,
+ noop,
renderApp,
TestContext,
} from '../../helpers/tests';
@@ -16,6 +17,9 @@ import { DivisionDto } from '../../interfaces/models/dtos/DivisionDto';
import { divisionBuilder } from '../../helpers/builders/divisions';
import { seasonBuilder } from '../../helpers/builders/seasons';
import { ISeasonApi } from '../../interfaces/apis/ISeasonApi';
+import { renderDate } from '../../helpers/rendering';
+import { DivisionDataContainer } from './DivisionDataContainer';
+import { DivisionFixtureDateDto } from '../../interfaces/models/dtos/Division/DivisionFixtureDateDto';
const mockedUsedNavigate = jest.fn();
@@ -32,16 +36,13 @@ describe('EditSeason', () => {
let updatedSeason: EditSeasonDto | null;
let apiResponse: IClientActionResultDto;
let deletedId: string | null;
+ let updatedData: EditSeasonDto | null;
const seasonApi = api({
- update: async (
- data: EditSeasonDto,
- ): Promise> => {
+ update: async (data: EditSeasonDto) => {
updatedSeason = data;
return apiResponse;
},
- delete: async (
- id: string,
- ): Promise> => {
+ delete: async (id: string) => {
deletedId = id;
return apiResponse;
},
@@ -57,7 +58,9 @@ describe('EditSeason', () => {
saveError = err;
}
- async function onUpdateData(_: EditSeasonDto) {}
+ async function onUpdateData(data: EditSeasonDto) {
+ updatedData = data;
+ }
afterEach(async () => {
await cleanUp(context);
@@ -69,16 +72,25 @@ describe('EditSeason', () => {
saveError = null;
updatedSeason = null;
deletedId = null;
+ updatedData = null;
apiResponse = {
success: true,
};
});
async function renderComponent(
- props: IEditSeasonProps,
+ p: Partial,
seasons: SeasonDto[],
divisions: DivisionDto[],
+ fixtures?: DivisionFixtureDateDto[],
) {
+ const props = {
+ setSaveError,
+ onClose,
+ onSave,
+ onUpdateData,
+ ...p,
+ } as IEditSeasonProps;
context = await renderApp(
iocProps({ seasonApi }),
brandingProps(),
@@ -89,7 +101,12 @@ describe('EditSeason', () => {
},
reportedError,
),
- ,
+
+
+ ,
);
}
@@ -105,16 +122,9 @@ describe('EditSeason', () => {
const divisions = [division1, division2];
it('updates season name', async () => {
- let updatedData: EditSeasonDto;
await renderComponent(
{
data: season,
- onUpdateData: async (update: EditSeasonDto) => {
- updatedData = update;
- },
- setSaveError,
- onClose,
- onSave,
},
[season],
divisions,
@@ -129,16 +139,9 @@ describe('EditSeason', () => {
});
it('updates season dates', async () => {
- let updatedData: EditSeasonDto;
await renderComponent(
{
data: season,
- onUpdateData: async (update: EditSeasonDto) => {
- updatedData = update;
- },
- setSaveError,
- onClose,
- onSave,
},
[season],
divisions,
@@ -157,16 +160,9 @@ describe('EditSeason', () => {
});
it('updates season fixture timings', async () => {
- let updatedData: EditSeasonDto;
await renderComponent(
{
data: season,
- onUpdateData: async (update: EditSeasonDto) => {
- updatedData = update;
- },
- setSaveError,
- onClose,
- onSave,
},
[season],
divisions,
@@ -185,16 +181,9 @@ describe('EditSeason', () => {
});
it('can select a division', async () => {
- let updatedData: EditSeasonDto;
await renderComponent(
{
data: season,
- onUpdateData: async (update: EditSeasonDto) => {
- updatedData = update;
- },
- setSaveError,
- onClose,
- onSave,
},
[season],
divisions,
@@ -214,16 +203,9 @@ describe('EditSeason', () => {
});
it('can unselect a division', async () => {
- let updatedData: EditSeasonDto;
await renderComponent(
{
data: season,
- onUpdateData: async (update: EditSeasonDto) => {
- updatedData = update;
- },
- setSaveError,
- onClose,
- onSave,
},
[season],
divisions,
@@ -246,16 +228,9 @@ describe('EditSeason', () => {
const seasonWithoutId: EditSeasonDto = Object.assign({}, season);
seasonWithoutId.id = undefined;
const otherSeason = seasonBuilder('OTHER SEASON').build();
- let updatedData: EditSeasonDto;
await renderComponent(
{
data: seasonWithoutId as EditSeasonDto & SeasonDto,
- onUpdateData: async (update: EditSeasonDto) => {
- updatedData = update;
- },
- setSaveError,
- onClose,
- onSave,
},
[otherSeason],
divisions,
@@ -274,10 +249,6 @@ describe('EditSeason', () => {
await renderComponent(
{
data: seasonWithoutName,
- setSaveError,
- onClose,
- onSave,
- onUpdateData,
},
[seasonWithoutName],
divisions,
@@ -289,14 +260,58 @@ describe('EditSeason', () => {
expect(saved).toEqual(false);
});
+ it('prevents save when start date is after first fixture', async () => {
+ const earliestFixtureDate = '2026-02-01';
+ const startsAfterFirstFixture = { ...season, startDate: '2026-02-02' };
+ const fixture = {
+ date: earliestFixtureDate,
+ } as DivisionFixtureDateDto;
+ await renderComponent(
+ {
+ data: startsAfterFirstFixture,
+ },
+ [startsAfterFirstFixture],
+ divisions,
+ [fixture],
+ );
+
+ await context.button('Update season').click();
+
+ context.prompts
+ .alertWasShown(`Start date is after some fixtures in the season, this would prevent them from appearing on the fixture list.
+
+Alter the date to ${renderDate(earliestFixtureDate)} so they are included`);
+ expect(saved).toEqual(false);
+ });
+
+ it('prevents save when end date is before last fixture', async () => {
+ const latestFixtureDate = '2026-02-02';
+ const endsBeforeLastFixture = { ...season, endDate: '2026-02-01' };
+ const fixture = {
+ date: latestFixtureDate,
+ } as DivisionFixtureDateDto;
+ await renderComponent(
+ {
+ data: endsBeforeLastFixture,
+ },
+ [endsBeforeLastFixture],
+ divisions,
+ [fixture],
+ );
+
+ await context.button('Update season').click();
+
+ context.prompts
+ .alertWasShown(`End date is before some fixtures in the season, this would prevent them from appearing on the fixture list.
+
+Alter the date to ${renderDate(latestFixtureDate)} so they are included`);
+ expect(saved).toEqual(false);
+ });
+
it('saves season updates', async () => {
await renderComponent(
{
data: season,
- setSaveError,
- onClose,
- onSave,
- onUpdateData,
},
[season],
divisions,
@@ -316,10 +331,6 @@ describe('EditSeason', () => {
await renderComponent(
{
data: season,
- setSaveError,
- onClose,
- onSave,
- onUpdateData,
},
[season],
divisions,
@@ -339,10 +350,6 @@ describe('EditSeason', () => {
await renderComponent(
{
data: season,
- setSaveError,
- onClose,
- onSave,
- onUpdateData,
},
[season],
divisions,
@@ -350,7 +357,7 @@ describe('EditSeason', () => {
reportedError.verifyNoError();
context.prompts.respondToConfirm(
'Are you sure you want to delete the SEASON season?',
- true,
+ false,
);
await context.button('Delete season').click();
@@ -365,10 +372,6 @@ describe('EditSeason', () => {
await renderComponent(
{
data: season,
- setSaveError,
- onClose,
- onSave,
- onUpdateData,
},
[season],
divisions,
@@ -389,10 +392,6 @@ describe('EditSeason', () => {
await renderComponent(
{
data: season,
- setSaveError,
- onClose,
- onSave,
- onUpdateData,
},
[season],
divisions,
@@ -417,10 +416,6 @@ describe('EditSeason', () => {
await renderComponent(
{
data: season,
- setSaveError,
- onClose,
- onSave,
- onUpdateData,
},
[season],
divisions,
diff --git a/CourageScores/ClientApp/src/components/league/EditSeason.tsx b/CourageScores/ClientApp/src/components/league/EditSeason.tsx
index 9b72c0a59..8d89ca252 100644
--- a/CourageScores/ClientApp/src/components/league/EditSeason.tsx
+++ b/CourageScores/ClientApp/src/components/league/EditSeason.tsx
@@ -10,6 +10,8 @@ import { EditSeasonDto } from '../../interfaces/models/dtos/Season/EditSeasonDto
import { UntypedPromise } from '../../interfaces/UntypedPromise';
import { IClientActionResultDto } from '../common/IClientActionResultDto';
import { SeasonDto } from '../../interfaces/models/dtos/Season/SeasonDto';
+import { useDivisionData } from './DivisionDataContainer';
+import { renderDate } from '../../helpers/rendering';
export interface IEditSeasonProps {
onClose(): UntypedPromise;
@@ -30,7 +32,13 @@ export function EditSeason({
const [deleting, setDeleting] = useState(false);
const { seasonApi } = useDependencies();
const { seasons, divisions, onError } = useApp();
+ const { fixtures: fixtureDates } = useDivisionData();
const navigate = useNavigate();
+ const orderedDates = fixtureDates?.sort(sortBy('date')) ?? [];
+ const earliestFixtureDate = orderedDates[0]?.date;
+ const latestFixtureDate = orderedDates[orderedDates.length - 1]?.date;
+ const fixturesBeforeStartDate = data.startDate > earliestFixtureDate;
+ const fixturesAfterEndDate = data.endDate < latestFixtureDate;
async function saveSeason() {
/* istanbul ignore next */
@@ -39,6 +47,24 @@ export function EditSeason({
return;
}
+ if (fixturesBeforeStartDate) {
+ window.alert(
+ `Start date is after some fixtures in the season, this would prevent them from appearing on the fixture list.
+
+Alter the date to ${renderDate(earliestFixtureDate)} so they are included`,
+ );
+ return;
+ }
+
+ if (fixturesAfterEndDate) {
+ window.alert(
+ `End date is before some fixtures in the season, this would prevent them from appearing on the fixture list.
+
+Alter the date to ${renderDate(latestFixtureDate)} so they are included`,
+ );
+ return;
+ }
+
if (!data.name) {
window.alert('Enter a season name');
return;
@@ -142,7 +168,8 @@ export function EditSeason({
onChange={valueChanged(data, onUpdateData)}
value={(data.startDate || '').substring(0, 10)}
type="date"
- className="form-control margin-right"
+ title={`Earliest fixture is on ${renderDate(earliestFixtureDate)}`}
+ className={`form-control margin-right${fixturesBeforeStartDate || !data.startDate ? ' is-invalid' : ''}`}
/>