diff --git a/web/src/components/core/icon.tsx b/web/src/components/core/icon.tsx
index edee290..db1b1d1 100644
--- a/web/src/components/core/icon.tsx
+++ b/web/src/components/core/icon.tsx
@@ -99,6 +99,16 @@ const getSvgPath = (icon: string) => {
vb: "0 0 512 512",
path: "M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z",
};
+ case 'articles':
+ return {
+ vb: "0 0 512 512",
+ path: "M96 96c0-35.3 28.7-64 64-64H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H80c-44.2 0-80-35.8-80-80V128c0-17.7 14.3-32 32-32s32 14.3 32 32V400c0 8.8 7.2 16 16 16s16-7.2 16-16V96zm64 24v80c0 13.3 10.7 24 24 24H296c13.3 0 24-10.7 24-24V120c0-13.3-10.7-24-24-24H184c-13.3 0-24 10.7-24 24zm208-8c0 8.8 7.2 16 16 16h48c8.8 0 16-7.2 16-16s-7.2-16-16-16H384c-8.8 0-16 7.2-16 16zm0 96c0 8.8 7.2 16 16 16h48c8.8 0 16-7.2 16-16s-7.2-16-16-16H384c-8.8 0-16 7.2-16 16zM160 304c0 8.8 7.2 16 16 16H432c8.8 0 16-7.2 16-16s-7.2-16-16-16H176c-8.8 0-16 7.2-16 16zm0 96c0 8.8 7.2 16 16 16H432c8.8 0 16-7.2 16-16s-7.2-16-16-16H176c-8.8 0-16 7.2-16 16z",
+ };
+ case 'about':
+ return {
+ vb: "0 0 512 512",
+ path: "M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z",
+ };
default:
return { vb: "", path: "" }; // Default path or a placeholder icon
}
diff --git a/web/src/components/furniture/nav.tsx b/web/src/components/furniture/nav.tsx
index 2482cb7..7e1780a 100644
--- a/web/src/components/furniture/nav.tsx
+++ b/web/src/components/furniture/nav.tsx
@@ -89,7 +89,7 @@ export default component$(() => {
Home
GitHub
- Checklists
+ Checklists
{data.map((item: Section, index: number) => (
-
@@ -102,14 +102,18 @@ export default component$(() => {
- Articles
+
+ Articles
+
- About
+
+ About
+
-
Contributing
diff --git a/web/src/components/psc/checklist-table.tsx b/web/src/components/psc/checklist-table.tsx
new file mode 100644
index 0000000..9094d5a
--- /dev/null
+++ b/web/src/components/psc/checklist-table.tsx
@@ -0,0 +1,81 @@
+import { component$ } from "@builder.io/qwik";
+
+import type { Priority, Section } from '../../types/PSC';
+import { marked } from "marked";
+import { useLocalStorage } from "~/hooks/useLocalStorage";
+
+export default component$((props: { section: Section }) => {
+
+ const STORAGE_KEY = 'PSC_PROGRESS';
+ const [value, setValue] = useLocalStorage(STORAGE_KEY, {});
+
+ const getBadgeClass = (priority: Priority, precedeClass: string = '') => {
+ switch (priority.toLocaleLowerCase()) {
+ case 'recommended':
+ return `${precedeClass}success`;
+ case 'optional':
+ return `${precedeClass}warning`;
+ case 'advanced':
+ return `${precedeClass}error`;
+ default:
+ return `${precedeClass}neutral`;
+ }
+ };
+
+ const generateId = (title: string) => {
+ return title.toLowerCase().replace(/ /g, '-');
+ };
+
+ const parseMarkdown = (text: string | undefined): string => {
+ return marked.parse(text || '', { async: false }) as string || '';
+ };
+
+ const isChecked = (point: string) => {
+ const pointId = generateId(point);
+ return value.value[pointId] || false;
+ };
+
+ return (
+
+ );
+});
diff --git a/web/src/components/psc/progress.tsx b/web/src/components/psc/progress.tsx
new file mode 100644
index 0000000..ba565ea
--- /dev/null
+++ b/web/src/components/psc/progress.tsx
@@ -0,0 +1,50 @@
+import { $, component$, useTask$, 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";
+
+export default component$(() => {
+
+ const checklists = useContext(ChecklistContext);
+
+ const totalProgress = useSignal(0);
+
+ const STORAGE_KEY = 'PSC_PROGRESS';
+ const [checkedItems] = useLocalStorage(STORAGE_KEY, {});
+
+ /**
+ * Given an array of sections, returns the percentage completion of all included checklists.
+ */
+ const calculateProgress = $((sections: Sections): number => {
+ if (!checkedItems.value || !sections.length) {
+ return 0;
+ }
+ const totalItems = sections.reduce((total: number, section: Section) => total + section.checklist.length, 0);
+ let totalComplete = 0;
+ sections.forEach((section: Section) => {
+ section.checklist.forEach((item) => {
+ const id = item.point.toLowerCase().replace(/ /g, '-');
+ const isComplete = checkedItems.value[id];
+ if (isComplete) {
+ totalComplete++;
+ }
+ });
+ });
+ return Math.round((totalComplete / totalItems) * 100);
+ });
+
+ useOnWindow('load', $(() => {
+ calculateProgress(checklists.value)
+ .then(percentage => {
+ totalProgress.value = percentage;
+ });
+ }));
+
+ return (
+
+ );
+});
+
diff --git a/web/src/routes/checklist/[title]/index.tsx b/web/src/routes/checklist/[title]/index.tsx
index 3fa0934..963da4e 100644
--- a/web/src/routes/checklist/[title]/index.tsx
+++ b/web/src/routes/checklist/[title]/index.tsx
@@ -4,8 +4,8 @@ import { marked } from 'marked';
import Icon from '~/components/core/icon';
import { ChecklistContext } from '~/store/checklist-context';
-import { useLocalStorage } from '~/hooks/useLocalStorage';
-import type { Section, Priority } from "~/types/PSC";
+import type { Section } from "~/types/PSC";
+import Table from '~/components/psc/checklist-table';
export default component$(() => {
@@ -16,34 +16,9 @@ export default component$(() => {
const section: Section | undefined = checklists.value.find((item: Section) => item.slug === slug);
- const getBadgeClass = (priority: Priority, precedeClass: string = '') => {
- switch (priority.toLocaleLowerCase()) {
- case 'recommended':
- return `${precedeClass}success`;
- case 'optional':
- return `${precedeClass}warning`;
- case 'advanced':
- return `${precedeClass}error`;
- default:
- return `${precedeClass}neutral`;
- }
- };
-
- const generateId = (title: string) => {
- return title.toLowerCase().replace(/ /g, '-');
- };
-
const parseMarkdown = (text: string | undefined): string => {
return marked.parse(text || '', { async: false }) as string || '';
};
-
- const STORAGE_KEY = 'PSC_PROGRESS';
- const [value, setValue] = useLocalStorage(STORAGE_KEY, {});
-
- const isChecked = (point: string) => {
- const pointId = generateId(point);
- return value.value[pointId] || false;
- };
return (
@@ -55,48 +30,9 @@ export default component$(() => {
+
);
diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx
index 44f9965..7cf4083 100644
--- a/web/src/routes/index.tsx
+++ b/web/src/routes/index.tsx
@@ -1,8 +1,9 @@
import { component$, useContext } from '@builder.io/qwik';
import { type DocumentHead } from "@builder.io/qwik-city";
-import Hero from "../components/furniture/hero";
-import SectionLinkGrid from "../components/psc/section-link-grid";
+import Hero from "~/components/furniture/hero";
+import SectionLinkGrid from "~/components/psc/section-link-grid";
+import Progress from "~/components/psc/progress";
import { ChecklistContext } from '~/store/checklist-context';
@@ -12,6 +13,7 @@ export default component$(() => {
return (
<>
+
>
);