Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,30 @@ import {
useCampaignMessagesController,
} from './CampaignMessagesLogic';
import type { CampaignMessagesScheduleType } from './CampaignMessagesLogic';
import type { CampaignFrequency } from '../../../services/api/ServiceApi';
import './CampaignMessages.css';

interface CampaignMessagesProps {
campaignId?: string;
campaignStartDate?: string;
campaignEndDate?: string;
campaignFrequency?: CampaignFrequency;
isCampaignCancelled?: boolean;
}

const CampaignMessages: React.FC<CampaignMessagesProps> = ({
campaignId,
campaignStartDate,
campaignEndDate,
campaignFrequency,
isCampaignCancelled,
}) => {
const { t } = useTranslation();
const controller = useCampaignMessagesController({
campaignId,
campaignStartDate,
campaignEndDate,
campaignFrequency,
isCampaignCancelled,
translate: (key) => String(t(key)),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ import {
} from 'react';
import { ServiceConfig } from '../../../services/ServiceConfig';
import {
CampaignFrequency,
CampaignMessagingQueryParams,
CampaignMessagingRow,
} from '../../../services/api/ServiceApi';
import { hasCampaignWriteAccess } from '../../../services/api/campaignListingHelpers';
import { Json } from '../../../services/database';
import {
buildFrequencyTimelineDates,
DEFAULT_FREQUENCY,
} from '../campaignSetup/campaignAssignmentUtils';
import { buildCampaignDurationTimelineDates } from '../campaignSetup/campaignCommunicationUtils';
import { useAppSelector } from '../../../redux/hooks';
import { AuthState } from '../../../redux/slices/auth/authSlice';
Expand Down Expand Up @@ -119,6 +124,7 @@ interface UseCampaignMessagesControllerParams {
campaignId?: string;
campaignStartDate?: string;
campaignEndDate?: string;
campaignFrequency?: CampaignFrequency;
isCampaignCancelled?: boolean;
translate: (key: string) => string;
}
Expand Down Expand Up @@ -681,6 +687,7 @@ export const useCampaignMessagesController = ({
campaignId,
campaignStartDate,
campaignEndDate,
campaignFrequency = DEFAULT_FREQUENCY,
isCampaignCancelled = false,
translate,
}: UseCampaignMessagesControllerParams): CampaignMessagesController => {
Expand Down Expand Up @@ -716,6 +723,17 @@ export const useCampaignMessagesController = ({
Record<string, boolean>
>({});
const timelineDates = useMemo(
() =>
campaignStartDate && campaignEndDate
? buildFrequencyTimelineDates(
campaignStartDate,
campaignEndDate,
campaignFrequency,
)
: [],
[campaignEndDate, campaignFrequency, campaignStartDate],
);
const campaignRangeDates = useMemo(
() =>
campaignStartDate && campaignEndDate
? buildCampaignDurationTimelineDates(campaignStartDate, campaignEndDate)
Expand Down Expand Up @@ -755,8 +773,8 @@ export const useCampaignMessagesController = ({
// Fetch the full calendar range so persisted Sunday rows cannot displace
// later non-Sunday campaign dates from the paginated response.
const pageSize =
timelineDates.length > 0
? timelineDates.length
campaignRangeDates.length > 0
? campaignRangeDates.length
: CAMPAIGN_MESSAGES_PAGE_SIZE;
const loadedMessagesData = await loadCampaignMessagesData(
campaignId,
Expand All @@ -778,7 +796,7 @@ export const useCampaignMessagesController = ({
return () => {
isMounted = false;
};
}, [campaignId, displayTimelineDates, timelineDates.length]);
}, [campaignId, campaignRangeDates.length, displayTimelineDates]);

useEffect(
() => () => {
Expand Down Expand Up @@ -1082,8 +1100,8 @@ export const useCampaignMessagesController = ({
{
page: 1,
pageSize:
timelineDates.length > 0
? timelineDates.length
campaignRangeDates.length > 0
? campaignRangeDates.length
: CAMPAIGN_MESSAGES_PAGE_SIZE,
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const CampaignCommunicationTimelineStep: React.FC<
CampaignCommunicationTimelineStepProps
> = ({
form,
frequency,
assignmentDrafts,
selectedSchoolIds,
communicationState,
Expand All @@ -31,8 +32,8 @@ const CampaignCommunicationTimelineStep: React.FC<
const { t } = useTranslation();
const timeOptions = useMemo(() => buildTimeOptions(), []);
const timelineDates = useMemo(
() => buildCommunicationTimelineDates(assignmentDrafts, form),
[assignmentDrafts, form],
() => buildCommunicationTimelineDates(assignmentDrafts, form, frequency),
[assignmentDrafts, form, frequency],
);
const { campaignReach, loadingReach } = useCampaignReach(selectedSchoolIds);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {
CampaignAssignmentChapterOption,
CampaignAssignmentSubjectOption,
CampaignFrequency,
} from '../../../services/api/ServiceApi';
import { CampaignSetupFormState } from './types';

export type Frequency = 'daily' | 'alternate_days' | 'alternate_week';
export type Frequency = CampaignFrequency;

export type AssignmentRow = {
rowId: string;
Expand Down Expand Up @@ -151,13 +152,18 @@ const getScheduleDates = (
return dates;
};

export const buildFrequencyTimelineDates = (
startDate: string,
endDate: string,
frequency: Frequency,
): string[] =>
getScheduleDates(startDate, endDate, frequency, Number.MAX_SAFE_INTEGER);

export const getRequiredAssignmentCount = (
startDate: string,
endDate: string,
frequency: Frequency,
) =>
getScheduleDates(startDate, endDate, frequency, Number.MAX_SAFE_INTEGER)
.length;
) => buildFrequencyTimelineDates(startDate, endDate, frequency).length;

const distributeDatesAcrossAssignments = (
dates: string[],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CampaignAssignmentDraft } from './campaignAssignmentUtils';
import { CampaignAssignmentDraft, Frequency } from './campaignAssignmentUtils';
import {
CampaignCommunicationRowState,
CampaignCommunicationState,
Expand All @@ -8,6 +8,7 @@ import { CampaignSetupFormState } from './types';

export type CampaignCommunicationTimelineStepProps = {
form: CampaignSetupFormState;
frequency: Frequency;
assignmentDrafts: CampaignAssignmentDraft[];
selectedSchoolIds: string[];
communicationState: CampaignCommunicationState;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { t } from 'i18next';
import { CAMPAIGN_OBJECTIVE } from '../../../common/constants';
import { CampaignAssignmentDraft } from './campaignAssignmentUtils';
import {
buildFrequencyTimelineDates,
CampaignAssignmentDraft,
Frequency,
} from './campaignAssignmentUtils';
import { CampaignSetupFormState } from './types';

export type CampaignCommunicationRowState = {
Expand Down Expand Up @@ -51,11 +55,16 @@ export const createEmptyCommunicationRow =
export const buildCommunicationTimelineDates = (
assignmentDrafts: CampaignAssignmentDraft[],
form?: CampaignSetupFormState,
frequency?: Frequency,
): string[] => {
if (form?.objective === CAMPAIGN_OBJECTIVE.HOMEPAGE_LEARNING_PATHWAY) {
return buildCampaignDurationTimelineDates(form.startDate, form.endDate);
}

if (form && frequency) {
return buildFrequencyTimelineDates(form.startDate, form.endDate, frequency);
}
Comment on lines +64 to +66

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime errors or passing undefined values to buildFrequencyTimelineDates, we should ensure that both form.startDate and form.endDate are defined before calling the function.

Suggested change
if (form && frequency) {
return buildFrequencyTimelineDates(form.startDate, form.endDate, frequency);
}
if (form?.startDate && form?.endDate && frequency) {
return buildFrequencyTimelineDates(form.startDate, form.endDate, frequency);
}


return Array.from(new Set(assignmentDrafts.map((draft) => draft.startsAt)))
.filter(Boolean)
.sort();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ const CampaignsOverview: React.FC<CampaignsOverviewProps> = ({
campaignEndDate={
campaignOverviewData?.data?.campaign?.end_date ?? undefined
}
campaignFrequency={
campaignOverviewData?.data?.campaign?.frequency ?? undefined
}
/>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { CampaignFrequency } from '../../../services/api/ServiceApi';

export type CampaignsOverviewDisplayValue =
| string
| number
Expand Down Expand Up @@ -72,6 +74,7 @@ export interface CampaignsOverviewApiCampaign {
objective?: string | null;
start_date?: string | null;
end_date?: string | null;
frequency?: CampaignFrequency | null;
updated_at?: string | null;
campaign_status?: CampaignStatus | null;
manager?: CampaignsOverviewApiPerson | null;
Expand Down
8 changes: 8 additions & 0 deletions src/ops-console/hooks/useCampaignSetupForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ export const useCampaignSetupForm = () => {
const [submitAttempted, setSubmitAttempted] = useState(false);
const [rewardSubmitAttempted, setRewardSubmitAttempted] = useState(false);
const [message, setMessage] = useState<CampaignSetupMessage>(null);
const assignmentFrequency = useMemo(
() =>
assignmentConfigs[activeAssignmentGradeId]?.frequency ??
Object.values(assignmentConfigs)[0]?.frequency ??
DEFAULT_FREQUENCY,
[activeAssignmentGradeId, assignmentConfigs],
);
Comment on lines +97 to +103

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The DEFAULT_FREQUENCY constant is used here but it is not imported in this file. Please make sure to import it from ../components/campaignSetup/campaignAssignmentUtils at the top of the file:

import { DEFAULT_FREQUENCY } from '../components/campaignSetup/campaignAssignmentUtils';

Additionally, to prevent potential runtime errors if assignmentConfigs is null or undefined, we should use optional chaining and a fallback empty object when calling Object.values.

Suggested change
const assignmentFrequency = useMemo(
() =>
assignmentConfigs[activeAssignmentGradeId]?.frequency ??
Object.values(assignmentConfigs)[0]?.frequency ??
DEFAULT_FREQUENCY,
[activeAssignmentGradeId, assignmentConfigs],
);
const assignmentFrequency = useMemo(
() =>
assignmentConfigs?.[activeAssignmentGradeId]?.frequency ??
Object.values(assignmentConfigs ?? {})[0]?.frequency ??
DEFAULT_FREQUENCY,
[activeAssignmentGradeId, assignmentConfigs],
);


const audience = useCampaignAudienceSelection({
api,
Expand Down Expand Up @@ -431,6 +438,7 @@ export const useCampaignSetupForm = () => {
assignmentOptions,
areRewardsValid,
assignmentConfigs,
assignmentFrequency,
assignmentDrafts,
campaignRewards,
createdCampaignId,
Expand Down
9 changes: 8 additions & 1 deletion src/ops-console/pages/CampaignSetupPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,13 @@ const CampaignSetupPage: React.FC = () => {
buildCommunicationTimelineDates(
campaignSetup.assignmentDrafts,
campaignSetup.form,
campaignSetup.assignmentFrequency,
),
[campaignSetup.assignmentDrafts, campaignSetup.form],
[
campaignSetup.assignmentDrafts,
campaignSetup.assignmentFrequency,
campaignSetup.form,
],
);

const communicationValidation = useMemo(
Expand Down Expand Up @@ -292,6 +297,7 @@ const CampaignSetupPage: React.FC = () => {
const campaign = {
programId: campaignSetup.form.programId,
campaignName: campaignSetup.form.campaignName.trim(),
frequency: campaignSetup.assignmentFrequency,
objective: campaignSetup.form.objective as CampaignObjective,
targetType:
campaignSetup.form.objective === CAMPAIGN_OBJECTIVE.HOMEWORK
Expand Down Expand Up @@ -525,6 +531,7 @@ const CampaignSetupPage: React.FC = () => {
) : campaignSetup.activeStep === 3 ? (
<CampaignCommunicationTimelineStep
form={campaignSetup.form}
frequency={campaignSetup.assignmentFrequency}
assignmentDrafts={campaignSetup.assignmentDrafts}
selectedSchoolIds={selectedAssignmentSchoolIds}
communicationState={communicationState}
Expand Down
3 changes: 3 additions & 0 deletions src/services/api/ServiceApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ export type CampaignTargetType = 'percentage_completion' | 'number_of_lessons';

export type CampaignRewardType = 'digital_rewards' | 'physical_rewards';

export type CampaignFrequency = 'daily' | 'alternate_days' | 'alternate_week';

export type CampaignOption = {
id: string;
name: string;
Expand Down Expand Up @@ -296,6 +298,7 @@ export type CampaignAudiencePayload = {

export type CreateCampaignSetupPayload = CampaignAudiencePayload & {
campaignName: string;
frequency: CampaignFrequency;
objective: CampaignObjective;
targetType?: CampaignTargetType;
targetValue?: number;
Expand Down
3 changes: 2 additions & 1 deletion src/services/api/SupabaseApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10015,7 +10015,7 @@ export class SupabaseApi implements ServiceApi {
manager_search:user!campaign_manager_id_fkey(),
program_search:program!campaign_program_id_fkey()`
: '';
const campaignListingSelect = `id, name, objective, start_date, end_date,
const campaignListingSelect = `id, name, objective, start_date, end_date, frequency,
campaign_status, manager_id, program_id, target_audience_id,
created_at, updated_at, is_deleted, target_type, target_value, rewards,
manager:user!campaign_manager_id_fkey(id, name),
Expand Down Expand Up @@ -10508,6 +10508,7 @@ export class SupabaseApi implements ServiceApi {
manager_id: payload.managerId,
start_date: payload.startDate,
end_date: payload.endDate,
frequency: payload.frequency,
rewards: payload.rewards ? JSON.stringify(payload.rewards) : null,
};

Expand Down
3 changes: 3 additions & 0 deletions src/services/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@ export type Database = {
comments: string | null;
created_at: string | null;
end_date: string;
frequency: 'daily' | 'alternate_days' | 'alternate_week';
id: string;
is_deleted: boolean | null;
manager_id: string | null;
Expand All @@ -609,6 +610,7 @@ export type Database = {
comments?: string | null;
created_at?: string | null;
end_date: string;
frequency?: 'daily' | 'alternate_days' | 'alternate_week';
id?: string;
is_deleted?: boolean | null;
manager_id?: string | null;
Expand All @@ -630,6 +632,7 @@ export type Database = {
comments?: string | null;
created_at?: string | null;
end_date?: string;
frequency?: 'daily' | 'alternate_days' | 'alternate_week';
id?: string;
is_deleted?: boolean | null;
manager_id?: string | null;
Expand Down
Loading