Combine all tasks

This commit is contained in:
Artem Korsakov 2025-03-27 15:19:53 +03:00
parent 556bfaed29
commit 0d71301b23
3 changed files with 181 additions and 136 deletions

View file

@ -11,6 +11,7 @@ import {
} from './parsers';
import { generateProfileHTML, generateImageHTML } from './profileBlock';
import { generateProgressTableHTML } from './progressBlock';
import { generateTasksTableHTML } from './tasksBlock';
import { generateRatingTableHTML } from './ratingBlock';
import { generateLevelsTableHTML } from './levelsBlock';
import { generateAwardsTableHTML } from './awardsBlock';
@ -145,9 +146,12 @@ function generateHTML(cache: CacheData, source: Source): HTMLElement {
const imageElement = generateImageHTML(cache.accountData.account);
container.appendChild(imageElement);
const progressElement = generateProgressTableHTML(cache, source);
const progressElement = generateProgressTableHTML(cache);
container.appendChild(progressElement);
const tasksElement = generateTasksTableHTML(cache, source);
container.appendChild(tasksElement);
const locationRatingElement = generateRatingTableHTML(cache.locationUrl, cache.accountData.location, cache.progressData.solved, cache.locationRating);
container.appendChild(locationRatingElement);

View file

@ -1,53 +1,6 @@
import { AccountData, AwardData, AwardBlockData, CacheData, PersonalTask, ProgressData, RatingData, Source } from './types';
import { AccountData, CacheData, ProgressData, RatingData } from './types';
import { createProgressBar, createSectionHeader } from './commonBlocks';
interface Result {
message: string;
percentage: number;
}
function determineTop(solved: number, rating: RatingData): Result {
const thresholds = [
{ limit: 100, top: rating.top100, title: 'Top 100' },
{ limit: 50, top: rating.top50, title: 'Top 50' },
{ limit: 25, top: rating.top25, title: 'Top 25' },
{ limit: 10, top: rating.top10, title: 'Top 10' },
{ limit: 5, top: rating.top5, title: 'Top 5' },
{ limit: 1, top: rating.top1, title: 'Top 1' },
];
for (let i = 0; i < thresholds.length; i++) {
const { limit, top, title } = thresholds[i];
if (rating.place > limit) {
const prevTop = i > 0 ? thresholds[i - 1].top : 0; // Get the previous top
const rest = top - solved + 1;
const all = top - prevTop + 1;
const percentage = all > 0 ? Math.round(((all - rest) / all) * 100) : 0;
const message = `${rest} problems away from ${title}`;
return { message, percentage };
}
}
return { message: 'You are in the Top 1', percentage: 100 };
}
/** Function to determine AwardData with the maximum number of members.
*
* @param awardBlocks - AwardBlockData array.
* @returns AwardData with the maximum number of members or null if there are no unfinished awards.
*/
function findAwardWithMaxMembers(awardBlocks: AwardBlockData[]): AwardData | null {
const allAwards = awardBlocks.flatMap(block => block.awards).filter(award => !award.isCompleted);;
return allAwards.reduce((maxAward, currentAward) => {
const currentMembers = parseInt(currentAward.members.replace(/\D/g, '')) || 0;
const maxMembers = parseInt(maxAward.members.replace(/\D/g, '')) || 0;
return currentMembers > maxMembers ? currentAward : maxAward;
}, allAwards[0] || null);
}
function createTableRow(label: string | HTMLElement, status: string, progressBar?: HTMLElement): HTMLElement {
const row = document.createElement('tr');
const labelCell = document.createElement('td');
@ -140,80 +93,13 @@ function createProgressTable(
return table;
}
function createPersonalTasksList(personalTasks: PersonalTask[]): HTMLElement {
const tasksList = document.createElement('ul');
tasksList.className = 'tasks-list';
personalTasks.forEach(personalTask => {
const li = document.createElement('li');
li.textContent = personalTask.task;
li.appendChild(createProgressBar(personalTask.percentage));
tasksList.appendChild(li);
});
return tasksList;
}
function createTasksList(
progressData: ProgressData,
accountData: AccountData,
locationRating: RatingData,
languageRating: RatingData,
awardsData: AwardBlockData[]
): HTMLElement {
const tasksList = document.createElement('ul');
tasksList.className = 'tasks-list';
// Progress to the next level
const matchToTheNext = progressData.toTheNext.match(/(\d+)/);
const countToTheNext = matchToTheNext ? (25 - parseInt(matchToTheNext[1], 10)) * 4 : 0;
const toTheNextPercentage = createProgressBar(countToTheNext);
const levelTask = document.createElement('li');
levelTask.textContent = `${progressData.level}: ${progressData.toTheNext}. `;
levelTask.appendChild(toTheNextPercentage);
tasksList.appendChild(levelTask);
// Location progress
const locationResult = determineTop(progressData.solved, locationRating);
const locationLi = document.createElement('li');
locationLi.textContent = `${accountData.location}: ${locationResult.message}. `;
locationLi.appendChild(createProgressBar(locationResult.percentage));
tasksList.appendChild(locationLi);
// Language progress
const languageResult = determineTop(progressData.solved, languageRating);
const languageLi = document.createElement('li');
languageLi.textContent = `${accountData.language}: ${languageResult.message}. `;
languageLi.appendChild(createProgressBar(languageResult.percentage));
tasksList.appendChild(languageLi);
// Award progress
const award = findAwardWithMaxMembers(awardsData);
if (award) {
const awardLi = document.createElement('li');
const awardLink = document.createElement('a');
awardLink.href = award.link;
awardLink.textContent = award.award;
awardLi.textContent = `Most unresolved award: `;
awardLi.appendChild(awardLink);
awardLi.append(` (${award.description}) by ${award.members}. `);
awardLi.appendChild(createProgressBar(award.percentage));
tasksList.appendChild(awardLi);
}
return tasksList;
}
/**
* Generates HTML for the progress table.
* @param cache - The cache data.
* @param source - The source data.
* @returns The generated HTMLElement (progress table container).
*/
export function generateProgressTableHTML(
cache: CacheData,
source: Source
): HTMLElement {
export function generateProgressTableHTML(cache: CacheData): HTMLElement {
const progressContainer = document.createElement('div');
progressContainer.appendChild(createSectionHeader('Progress'));
progressContainer.appendChild(createProgressBar(cache.progressData.percentage));
@ -226,24 +112,5 @@ export function generateProgressTableHTML(
cache.locationRating,
cache.languageRating
));
// Personal tasks section
const personalTasksHeader = document.createElement('h3');
personalTasksHeader.textContent = 'Personal tasks';
progressContainer.appendChild(personalTasksHeader);
progressContainer.appendChild(createPersonalTasksList(source.tasks));
// Tasks section
const tasksHeader = document.createElement('h3');
tasksHeader.textContent = 'Tasks';
progressContainer.appendChild(tasksHeader);
progressContainer.appendChild(createTasksList(
cache.progressData,
cache.accountData,
cache.locationRating,
cache.languageRating,
cache.awardsData
));
return progressContainer;
}

174
helpers/tasksBlock.ts Normal file
View file

@ -0,0 +1,174 @@
import { AccountData, AwardData, AwardBlockData, CacheData, PersonalTask, ProgressData, RatingData, Source } from './types';
import { createProgressBar, createSectionHeader } from './commonBlocks';
interface Result {
message: string;
percentage: number;
rest: number;
}
/**
* Determines the user's position relative to rating thresholds
* @param solved - Number of solved problems
* @param rating - Rating data containing place and thresholds
* @returns Result object with message, percentage and remaining problems
*/
function determineTop(solved: number, rating: RatingData): Result {
const thresholds = [
{ limit: 100, top: rating.top100, title: 'Top 100' },
{ limit: 50, top: rating.top50, title: 'Top 50' },
{ limit: 25, top: rating.top25, title: 'Top 25' },
{ limit: 10, top: rating.top10, title: 'Top 10' },
{ limit: 5, top: rating.top5, title: 'Top 5' },
{ limit: 1, top: rating.top1, title: 'Top 1' },
];
for (let i = 0; i < thresholds.length; i++) {
const { limit, top, title } = thresholds[i];
if (rating.place > limit) {
const prevTop = i > 0 ? thresholds[i - 1].top : 0;
const rest = top - solved + 1;
const range = top - prevTop + 1;
const percentage = range > 0 ? Math.round(((range - rest) / range) * 100) : 0;
return {
message: `${rest} problems away from ${title}`,
percentage,
rest
};
}
}
return {
message: 'You are in the Top 1',
percentage: 100,
rest: 0
};
}
/**
* Finds the award with the most members that isn't completed
* @param awardBlocks - Array of award blocks
* @returns AwardData with max members or null if none found
*/
function findAwardWithMaxMembers(awardBlocks: AwardBlockData[]): AwardData | null {
const allAwards = awardBlocks
.flatMap(block => block.awards)
.filter(award => !award.isCompleted);
if (allAwards.length === 0) return null;
return allAwards.reduce((maxAward, currentAward) => {
const currentMembers = parseInt(currentAward.members.replace(/\D/g, '')) || 0;
const maxMembers = parseInt(maxAward.members.replace(/\D/g, '')) || 0;
return currentMembers > maxMembers ? currentAward : maxAward;
});
}
/**
* Creates a prioritized list of tasks
* @param tasks - Configuration for task prioritization
* @returns HTMLElement containing the tasks list
*/
function createTasksList({
personalTasks,
progressData,
accountData,
locationRating,
languageRating,
awardsData
}: {
personalTasks: PersonalTask[];
progressData: ProgressData;
accountData: AccountData;
locationRating: RatingData;
languageRating: RatingData;
awardsData: AwardBlockData[];
}): HTMLElement {
const tasksList = document.createElement('ul');
tasksList.className = 'tasks-list';
// Add personal tasks
personalTasks.forEach(task => {
const li = document.createElement('li');
li.textContent = task.task;
li.appendChild(createProgressBar(task.percentage));
tasksList.appendChild(li);
});
// Calculate level progress
const levelRest = parseInt(progressData.toTheNext.match(/(\d+)/)?.[1] ?? '25', 10);
const levelPercentage = (25 - levelRest) * 4;
const levelTask = document.createElement('li');
levelTask.textContent = `${progressData.level}: ${progressData.toTheNext}. `;
levelTask.appendChild(createProgressBar(levelPercentage));
// Calculate location and language progress
const locationResult = determineTop(progressData.solved, locationRating);
const languageResult = determineTop(progressData.solved, languageRating);
const locationLi = document.createElement('li');
locationLi.textContent = `${accountData.location}: ${locationResult.message}. `;
locationLi.appendChild(createProgressBar(locationResult.percentage));
const languageLi = document.createElement('li');
languageLi.textContent = `${accountData.language}: ${languageResult.message}. `;
languageLi.appendChild(createProgressBar(languageResult.percentage));
// Prioritize tasks based on remaining problems
const tasksToSort = [
{ element: levelTask, rest: levelRest },
{ element: locationLi, rest: locationResult.rest },
{ element: languageLi, rest: languageResult.rest }
];
tasksToSort
.sort((a, b) => a.rest - b.rest)
.forEach(({ element }) => tasksList.appendChild(element));
// Add award if exists
const award = findAwardWithMaxMembers(awardsData);
if (award) {
const awardLi = document.createElement('li');
const awardLink = document.createElement('a');
awardLink.href = award.link;
awardLink.textContent = award.award;
awardLi.append(
'Most unresolved award: ',
awardLink,
` (${award.description}) by ${award.members}. `
);
awardLi.appendChild(createProgressBar(award.percentage));
tasksList.appendChild(awardLi);
}
return tasksList;
}
/**
* Generates HTML for the tasks table
* @param cache - Cache data
* @param source - Source data
* @returns HTMLElement containing the tasks table
*/
export function generateTasksTableHTML(
cache: CacheData,
source: Source
): HTMLElement {
const tasksContainer = document.createElement('div');
tasksContainer.append(
createSectionHeader('Tasks'),
createTasksList({
personalTasks: source.tasks,
progressData: cache.progressData,
accountData: cache.accountData,
locationRating: cache.locationRating,
languageRating: cache.languageRating,
awardsData: cache.awardsData
})
);
return tasksContainer;
}