mirror of
https://github.com/Lissy93/personal-security-checklist.git
synced 2024-12-28 00:39:39 -05:00
Adds progress stats, for each priority
This commit is contained in:
parent
3cf85cdde0
commit
fd639567dd
@ -36,6 +36,7 @@
|
||||
"dependencies": {
|
||||
"@builder.io/qwik": "^1.1.4",
|
||||
"marked": "^12.0.0",
|
||||
"progressbar.js": "^1.1.1",
|
||||
"sharp": "^0.33.2"
|
||||
},
|
||||
"resolutions": {
|
||||
|
@ -1,24 +1,34 @@
|
||||
import { $, component$, useTask$, useSignal, useOnWindow, useContext } from "@builder.io/qwik";
|
||||
import { $, component$, useSignal, useOnWindow, useContext } from "@builder.io/qwik";
|
||||
|
||||
import type { Priority, Sections, Section } from '../../types/PSC';
|
||||
import { useLocalStorage } from "~/hooks/useLocalStorage";
|
||||
import { ChecklistContext } from "~/store/checklist-context";
|
||||
import type { Priority, Sections, Section } from '~/types/PSC';
|
||||
|
||||
/**
|
||||
* Component for client-side user progress metrics.
|
||||
* Combines checklist data with progress from local storage,
|
||||
* calculates percentage completion for each priority level,
|
||||
* and renders some pretty pie charts to visualize results
|
||||
*/
|
||||
export default component$(() => {
|
||||
|
||||
// All checklist data, from store
|
||||
const checklists = useContext(ChecklistContext);
|
||||
|
||||
const totalProgress = useSignal(0);
|
||||
|
||||
const STORAGE_KEY = 'PSC_PROGRESS';
|
||||
const [checkedItems] = useLocalStorage(STORAGE_KEY, {});
|
||||
// Completed items, from local storage
|
||||
const [checkedItems] = useLocalStorage('PSC_PROGRESS', {});
|
||||
// Store to hold calculated progress results
|
||||
const totalProgress = useSignal({ completed: 0, outOf: 0 });
|
||||
|
||||
/**
|
||||
* Given an array of sections, returns the percentage completion of all included checklists.
|
||||
* Calculates the users progress over specified sections.
|
||||
* Given an array of sections, reads checklists in each,
|
||||
* counts total number of checklist items
|
||||
* counts the number of completed items from local storage
|
||||
* and returns the percentage of completion
|
||||
*/
|
||||
const calculateProgress = $((sections: Sections): number => {
|
||||
const calculateProgress = $((sections: Sections): { completed: number, outOf: number } => {
|
||||
if (!checkedItems.value || !sections.length) {
|
||||
return 0;
|
||||
return { completed: 0, outOf: 0 };
|
||||
}
|
||||
const totalItems = sections.reduce((total: number, section: Section) => total + section.checklist.length, 0);
|
||||
let totalComplete = 0;
|
||||
@ -31,20 +41,141 @@ export default component$(() => {
|
||||
}
|
||||
});
|
||||
});
|
||||
return Math.round((totalComplete / totalItems) * 100);
|
||||
return { completed: totalComplete, outOf: totalItems };
|
||||
// return Math.round((totalComplete / totalItems) * 100);
|
||||
});
|
||||
|
||||
/**
|
||||
* Filters the checklist items in a given array of sections,
|
||||
* so only the ones of a given priority are returned
|
||||
* @param sections - Array of sections to filter
|
||||
* @param priority - The priority to filter by
|
||||
*/
|
||||
const filterByPriority = $((sections: Sections, priority: Priority): Sections => {
|
||||
const normalize = (pri: string) => pri.toLowerCase().replace(/ /g, '-');
|
||||
return sections.map(section => ({
|
||||
...section,
|
||||
checklist: section.checklist.filter(item => normalize(item.priority) === normalize(priority))
|
||||
}));
|
||||
});
|
||||
|
||||
/**
|
||||
* Draws a completion chart using ProgressBar.js
|
||||
* Illustrating a given percent rendered to a given target element
|
||||
* @param percentage - The percentage of completion (0-100)
|
||||
* @param target - The ID of the element to draw the chart in
|
||||
* @param color - The color of the progress chart, defaults to Tailwind primary
|
||||
*/
|
||||
const drawProgress = $((percentage: number, target: string, color?: string) => {
|
||||
// Get a given color value from Tailwind CSS variable
|
||||
const getCssVariableValue = (variableName: string, fallback = '') => {
|
||||
return getComputedStyle(document.documentElement)
|
||||
.getPropertyValue(variableName)
|
||||
.trim()
|
||||
|| fallback;
|
||||
}
|
||||
// Define colors and styles for progress chart
|
||||
const primaryColor = color || 'hsl(var(--pf, 220, 13%, 69%))';
|
||||
const foregroundColor = 'hsl(var(--nc, 220, 13%, 69%))';
|
||||
const red = `hsl(${getCssVariableValue('--er', '0 91% 71%')})`;
|
||||
const green = `hsl(${getCssVariableValue('--su', '158 64% 52%')})`;
|
||||
const labelStyles = {
|
||||
color: foregroundColor, position: 'absolute', right: '0.5rem', top: '2rem'
|
||||
};
|
||||
// Animations to occur on each step of the progress bar
|
||||
const stepFunction = (state: any, bar: any) => {
|
||||
const value = Math.round(bar.value() * 100);
|
||||
bar.path.setAttribute('stroke', state.color);
|
||||
bar.setText(value ? `${value}%` : '');
|
||||
if (value >= percentage) {
|
||||
bar.path.setAttribute('stroke', primaryColor);
|
||||
}
|
||||
};
|
||||
// Define config settings for progress chart
|
||||
const progressConfig = {
|
||||
strokeWidth: 6,
|
||||
trailWidth: 3,
|
||||
color: primaryColor,
|
||||
trailColor: foregroundColor,
|
||||
text: { style: labelStyles },
|
||||
from: { color: red },
|
||||
to: { color: green },
|
||||
step: stepFunction,
|
||||
};
|
||||
// Initiate ProgressBar.js passing in config, to draw the progress chart
|
||||
import('progressbar.js').then((ProgressBar) => {
|
||||
const line = new ProgressBar.SemiCircle(target, progressConfig);
|
||||
line.animate(percentage / 100);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Given a priority, filters the checklist, calculates data, renders chart
|
||||
* @param priority - The priority to filter by
|
||||
* @param color - The color override for the chart
|
||||
*/
|
||||
const makeDataAndDrawChart = $((priority: Priority, color?: string) => {
|
||||
filterByPriority(checklists.value, priority)
|
||||
.then((sections: Sections) => {
|
||||
calculateProgress(sections)
|
||||
.then((progress) => {
|
||||
const { completed, outOf } = progress;
|
||||
const percent = Math.round((completed / outOf) * 100)
|
||||
drawProgress(percent, `#${priority}-container`, color)
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* When the window has loaded (client-side only)
|
||||
* Initiate the filtering, calculation and rendering of progress charts
|
||||
*/
|
||||
useOnWindow('load', $(() => {
|
||||
|
||||
calculateProgress(checklists.value)
|
||||
.then(percentage => {
|
||||
totalProgress.value = percentage;
|
||||
});
|
||||
.then((progress) => {
|
||||
totalProgress.value = progress;
|
||||
})
|
||||
|
||||
makeDataAndDrawChart('recommended', 'hsl(var(--su, 158 64% 52%))');
|
||||
makeDataAndDrawChart('optional', 'hsl(var(--wa, 43 96% 56%))');
|
||||
makeDataAndDrawChart('advanced', 'hsl(var(--er, 0 91% 71%))');
|
||||
}));
|
||||
|
||||
const items = [
|
||||
{ id: 'recommended-container', label: 'Essential' },
|
||||
{ id: 'optional-container', label: 'Optional' },
|
||||
{ id: 'advanced-container', label: 'Advanced' },
|
||||
];
|
||||
|
||||
// Beware, some god-awful markup ahead (thank Tailwind for that!)
|
||||
return (
|
||||
<div>
|
||||
<p>{totalProgress}</p>
|
||||
</div>
|
||||
<div class="flex justify-center flex-col w-full items-center">
|
||||
<div class="mb-4">
|
||||
<div class="rounded-box bg-neutral-content bg-opacity-5 w-96 p-4">
|
||||
<h3 class="text-primary text-2xl">Your Progress</h3>
|
||||
<p class="text-lg">
|
||||
You've completed <b>{totalProgress.value.completed} out of {totalProgress.value.outOf}</b> items
|
||||
</p>
|
||||
<progress
|
||||
class="progress w-80"
|
||||
value={totalProgress.value.completed}
|
||||
max={totalProgress.value.outOf}>
|
||||
</progress>
|
||||
</div>
|
||||
</div>
|
||||
<div class="carousel rounded-box mb-8">
|
||||
{items.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
class="flex flex-col justify-items-center carousel-item w-20 p-4
|
||||
bg-neutral-content bg-opacity-5 mx-2.5 rounded-box">
|
||||
<div class="relative" id={item.id}></div>
|
||||
<p class="text-center">{item.label}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
1
web/src/types/progressbar.d.ts
vendored
Normal file
1
web/src/types/progressbar.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module 'progressbar.js';
|
@ -1697,7 +1697,7 @@ fs.realpath@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
||||
|
||||
fsevents@~2.3.2, fsevents@~2.3.3:
|
||||
fsevents@^2.3.2, fsevents@~2.3.2, fsevents@~2.3.3:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||
@ -2975,6 +2975,14 @@ prelude-ls@^1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
progressbar.js@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/progressbar.js/-/progressbar.js-1.1.1.tgz#8c7dc52ce4cc8845c4f3055da75afc366a0543e9"
|
||||
integrity sha512-FBsw3BKsUbb+hNeYfiP3xzvAAQrPi4DnGDw66bCmfuRCDLcslxyxv2GyYUdBSKFGSIBa73CUP5WMcl6F8AAXlw==
|
||||
dependencies:
|
||||
lodash.merge "^4.6.2"
|
||||
shifty "^2.8.3"
|
||||
|
||||
property-information@^6.0.0:
|
||||
version "6.4.1"
|
||||
resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.4.1.tgz#de8b79a7415fd2107dfbe65758bb2cc9dfcf60ac"
|
||||
@ -3196,6 +3204,13 @@ shebang-regex@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||
|
||||
shifty@^2.8.3:
|
||||
version "2.20.4"
|
||||
resolved "https://registry.yarnpkg.com/shifty/-/shifty-2.20.4.tgz#fb2ec81697b808b250024fa9548b5b93fadd78cf"
|
||||
integrity sha512-4Y0qRkg8ME5XN8yGNAwmFOmsIURGFKT9UQfNL6DDJQErYtN5HsjyoBuJn41ZQfTkuu2rIbRMn9qazjKsDpO2TA==
|
||||
optionalDependencies:
|
||||
fsevents "^2.3.2"
|
||||
|
||||
side-channel@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
|
||||
|
Loading…
Reference in New Issue
Block a user