Extracts table into own component, starts Progress component, adds icons to nav

pull/232/head
Alicia Sykes 2024-02-06 17:05:46 +00:00
parent 7a1801cc85
commit 3cf85cdde0
6 changed files with 156 additions and 73 deletions

View File

@ -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
}

View File

@ -89,7 +89,7 @@ export default component$(() => {
<li><a href="/"><Icon class="mr-2" icon="homepage" width={16} height={16} />Home</a></li>
<li><a href="/"><Icon class="mr-2" icon="github" width={16} height={16} />GitHub</a></li>
<li>
<a href="/checklist"><Icon class="mr-2" icon="all" width={16} height={16} />Checklists</a>
<a href="/checklist"><Icon class="mr-2" icon="all" width={16} height={16} />Checklists</a>
<ul>
{data.map((item: Section, index: number) => (
<li key={`checklist-side-${index}`} class={`hover:bg-${item.color}-600 hover:bg-opacity-15`}>
@ -102,14 +102,18 @@ export default component$(() => {
</ul>
</li>
<li>
<a href="/article">Articles</a>
<a href="/article">
<Icon class="mr-2" icon="articles" width={16} height={16} />Articles
</a>
<ul>
<li><a href="/article/1">Article 1</a></li>
<li><a href="/article/2">Article 2</a></li>
</ul>
</li>
<li>
<a href="/about">About</a>
<a href="/about">
<Icon class="mr-2" icon="about" width={16} height={16} />About
</a>
<ul>
<li>
<a href="#">Contributing</a>

View File

@ -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 (
<table class="table">
<thead>
<tr>
<th>Done?</th>
<th>Advice</th>
<th>Level</th>
<th>Details</th>
</tr>
</thead>
<tbody>
{props.section?.checklist.map((item, index) => (
<tr key={index} class={`rounded-sm hover:bg-opacity-5 hover:bg-${getBadgeClass(item.priority)}`}>
<td>
<input
type="checkbox"
class="checkbox"
id={generateId(item.point)}
checked={isChecked(item.point)}
onClick$={() => {
const id = item.point.toLowerCase().replace(/ /g, '-');
const data = value.value;
data[id] = !data[id];
setValue(data);
}}
/>
</td>
<td>
<label class="text-base font-bold" for={generateId(item.point)}>
{item.point}
</label>
</td>
<td>
<div class={`badge gap-2 ${getBadgeClass(item.priority, 'badge-')}`}>
{item.priority}
</div>
</td>
<td dangerouslySetInnerHTML={parseMarkdown(item.details)}></td>
</tr>
))}
</tbody>
</table>
);
});

View File

@ -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 (
<div>
<p>{totalProgress}</p>
</div>
);
});

View File

@ -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 (
<div class="md:my-8 md:px-16 sm:px-2 rounded-md">
@ -55,48 +30,9 @@ export default component$(() => {
<p class="py-2" dangerouslySetInnerHTML={parseMarkdown(section?.intro)}></p>
<div class="overflow-x-auto">
<table class="table">
<thead>
<tr>
<th>Done?</th>
<th>Advice</th>
<th>Level</th>
<th>Details</th>
</tr>
</thead>
<tbody>
{section?.checklist.map((item, index) => (
<tr key={index} class={`rounded-sm hover:bg-opacity-5 hover:bg-${getBadgeClass(item.priority)}`}>
<td>
<input
type="checkbox"
class="checkbox"
id={generateId(item.point)}
checked={isChecked(item.point)}
onClick$={() => {
const id = item.point.toLowerCase().replace(/ /g, '-');
const data = value.value;
data[id] = !data[id];
setValue(data);
}}
/>
</td>
<td>
<label class="text-base font-bold" for={generateId(item.point)}>
{item.point}
</label>
</td>
<td>
<div class={`badge gap-2 ${getBadgeClass(item.priority, 'badge-')}`}>
{item.priority}
</div>
</td>
<td dangerouslySetInnerHTML={parseMarkdown(item.details)}></td>
</tr>
))}
</tbody>
</table>
{section && (<Table section={section} />)}
</div>
</article>
</div>
);

View File

@ -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 (
<>
<Hero />
<Progress />
<SectionLinkGrid sections={checklists.value} />
</>
);