Add cadence target calculation for timeframe-based goal progress resolution:
- Implemented `computeCadenceTargetForTimeframe` utility to calculate dynamic targets based on timeframe and cadence criteria. - Extended tests to validate timeframe-specific progress key selection, slice resolution, and cadence-based targets. - Refactored `DepartmentGoals` to integrate cadence-aware target resolution and fallback logic. - Enhanced e2e tests to cover bi-weekly cadence goals and dynamic target updates across timeframes.
This commit is contained in:
@@ -4,8 +4,12 @@ import { bookingTestData, loginAsOperator } from './fixtures';
|
||||
type GoalProgressBucket = {
|
||||
count: number;
|
||||
target: number;
|
||||
date_from?: string;
|
||||
date_end?: string;
|
||||
};
|
||||
|
||||
type GoalProgressByKey = Record<string, GoalProgressBucket>;
|
||||
|
||||
type MockGoal = {
|
||||
id: number;
|
||||
created_by: number;
|
||||
@@ -27,15 +31,11 @@ type MockGoal = {
|
||||
progress_alert_weekdays: string[];
|
||||
progress_alert_time_of_day: string | null;
|
||||
department_daily_targets: Record<string, number>;
|
||||
target_duration?: 'ENTIRE_DURATION' | 'WEEKS' | 'MONTHS' | 'YEARS' | null;
|
||||
target_duration_every?: number | null;
|
||||
};
|
||||
progress: {
|
||||
all: GoalProgressBucket;
|
||||
today: GoalProgressBucket;
|
||||
week: GoalProgressBucket;
|
||||
month: GoalProgressBucket;
|
||||
year: GoalProgressBucket;
|
||||
to_date: GoalProgressBucket;
|
||||
departmental_distribution: Record<string, Record<'all' | 'today' | 'week' | 'month' | 'year' | 'to_date', GoalProgressBucket>>;
|
||||
progress: GoalProgressByKey & {
|
||||
departmental_distribution: Record<string, GoalProgressByKey>;
|
||||
};
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
@@ -321,6 +321,110 @@ test.describe('Admin Module - Goals', () => {
|
||||
await expect(timeframeTabs.locator('li').nth(0)).toHaveClass(/is-active/);
|
||||
});
|
||||
|
||||
test('updates progress target when timeframe changes for cadence goals', async ({ page }) => {
|
||||
const departmentId = bookingTestData.departmentId;
|
||||
const nowIso = new Date().toISOString();
|
||||
|
||||
const cadenceGoal: MockGoal = {
|
||||
id: 301,
|
||||
created_by: 11,
|
||||
departments: [departmentId],
|
||||
criteria: {
|
||||
label: 'Cadence Biweekly Goal',
|
||||
type: 'PRODUCT',
|
||||
target: 7,
|
||||
start: '2026-01-01T00:00:00+01:00',
|
||||
end: '2026-12-31T23:59:59+01:00',
|
||||
products: [1],
|
||||
users: [],
|
||||
departments: [departmentId],
|
||||
progress_alert_frequency: 'NONE',
|
||||
progress_alert_destination: 'NONE',
|
||||
progress_alert_progress_type: 'ALL',
|
||||
progress_alert_style: 'NONE',
|
||||
progress_alert_format: null,
|
||||
progress_alert_weekdays: ['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'],
|
||||
progress_alert_time_of_day: null,
|
||||
department_daily_targets: {},
|
||||
target_duration: 'WEEKS',
|
||||
target_duration_every: 2,
|
||||
},
|
||||
progress: {
|
||||
all: { count: 11, target: 189, date_from: '2026-01-01T00:00:00+01:00', date_end: '2026-12-31T23:59:59+01:00' },
|
||||
to_date: { count: 6, target: 42, date_from: '2026-01-01T00:00:00+01:00', date_end: '2026-03-24T23:59:59+01:00' },
|
||||
today: { count: 1, target: 2, date_from: '2026-04-04T00:00:00+01:00', date_end: '2026-04-04T23:59:59+01:00' },
|
||||
week: { count: 4, target: 7, date_from: '2026-03-23T00:00:00+01:00', date_end: '2026-03-29T23:59:59+01:00' },
|
||||
month: { count: 9, date_from: '2026-04-01T00:00:00+01:00', date_end: '2026-04-30T23:59:59+01:00' },
|
||||
year: { count: 20, date_from: '2026-01-01T00:00:00+01:00', date_end: '2026-12-31T23:59:59+01:00' },
|
||||
every_2_weeks: { count: 5, target: 14, date_from: '2026-03-12T00:00:00+01:00', date_end: '2026-03-25T23:59:59+01:00' },
|
||||
departmental_distribution: {
|
||||
[String(departmentId)]: {
|
||||
all: { count: 11, target: 189 },
|
||||
to_date: { count: 6, target: 42 },
|
||||
today: { count: 1, target: 2 },
|
||||
week: { count: 4, target: 7 },
|
||||
month: { count: 9 },
|
||||
year: { count: 20 },
|
||||
every_2_weeks: { count: 5, target: 14 },
|
||||
},
|
||||
},
|
||||
},
|
||||
created_at: nowIso,
|
||||
updated_at: nowIso,
|
||||
};
|
||||
|
||||
await page.unroute('**/goals/department**');
|
||||
await page.route('**/goals/department**', async (route) => {
|
||||
const method = route.request().method();
|
||||
if (method === 'GET') {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ data: [cadenceGoal] }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ data: {} }),
|
||||
});
|
||||
});
|
||||
|
||||
await page.reload();
|
||||
const timeframeTabs = page.locator('.tabs.is-toggle').nth(1);
|
||||
const card = page.locator('.goal-card', { hasText: 'Cadence Biweekly Goal' }).first();
|
||||
const progressBar = card.locator('progress.progress').first();
|
||||
|
||||
const expectProgress = async (value: number, max: number) => {
|
||||
await expect(progressBar).toHaveAttribute('value', new RegExp(`^${value}(?:\\.0+)?$`));
|
||||
await expect(progressBar).toHaveAttribute('max', new RegExp(`^${max}(?:\\.0+)?$`));
|
||||
};
|
||||
|
||||
await expectProgress(11, 189);
|
||||
|
||||
await timeframeTabs.locator('li').nth(1).click(); // Indtil nu
|
||||
await expect(timeframeTabs.locator('li').nth(1)).toHaveClass(/is-active/);
|
||||
await expectProgress(6, 42);
|
||||
|
||||
await timeframeTabs.locator('li').nth(2).click(); // I dag
|
||||
await expect(timeframeTabs.locator('li').nth(2)).toHaveClass(/is-active/);
|
||||
await expectProgress(1, 2);
|
||||
|
||||
await timeframeTabs.locator('li').nth(3).click(); // Denne uge
|
||||
await expect(timeframeTabs.locator('li').nth(3)).toHaveClass(/is-active/);
|
||||
await expectProgress(5, 14);
|
||||
|
||||
await timeframeTabs.locator('li').nth(4).click(); // Denne måned
|
||||
await expect(timeframeTabs.locator('li').nth(4)).toHaveClass(/is-active/);
|
||||
await expectProgress(9, 21);
|
||||
|
||||
await timeframeTabs.locator('li').nth(5).click(); // År
|
||||
await expect(timeframeTabs.locator('li').nth(5)).toHaveClass(/is-active/);
|
||||
await expectProgress(20, 189);
|
||||
});
|
||||
|
||||
test('opens create goal modal and supports cancel', async ({ page }) => {
|
||||
await openCreateGoalModalProgrammatically(page, bookingTestData.departmentId);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user