mirror of
https://github.com/artemkorsakov/project-euler-stats.git
synced 2026-07-22 05:42:55 +00:00
Combine all tasks
This commit is contained in:
parent
556bfaed29
commit
0d71301b23
3 changed files with 181 additions and 136 deletions
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
174
helpers/tasksBlock.ts
Normal 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;
|
||||
}
|
||||
Loading…
Reference in a new issue