From d29d5e266492ca89aeb78885164bbfd8a8929d65 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Thu, 8 Feb 2024 20:58:51 +0000 Subject: [PATCH] Adds filters, sorting and more to checklist page --- web/package.json | 1 + web/src/components/core/icon.tsx | 12 +- web/src/components/psc/checklist-table.tsx | 169 ++++++++++++++++++--- web/tailwind.config.js | 2 +- web/yarn.lock | 5 + 5 files changed, 162 insertions(+), 27 deletions(-) diff --git a/web/package.json b/web/package.json index acdb04a..c995617 100644 --- a/web/package.json +++ b/web/package.json @@ -38,6 +38,7 @@ "chart.js": "^4.4.1", "marked": "^12.0.0", "progressbar.js": "^1.1.1", + "qwik-transition": "^0.0.7", "sharp": "^0.33.2" }, "resolutions": { diff --git a/web/src/components/core/icon.tsx b/web/src/components/core/icon.tsx index db66ba7..352c74e 100644 --- a/web/src/components/core/icon.tsx +++ b/web/src/components/core/icon.tsx @@ -114,6 +114,16 @@ const getSvgPath = (icon: string) => { vb: "0 0 512 512", path: "M3.9 54.9C10.5 40.9 24.5 32 40 32H472c15.5 0 29.5 8.9 36.1 22.9s4.6 30.5-5.2 42.5L320 320.9V448c0 12.1-6.8 23.2-17.7 28.6s-23.8 4.3-33.5-3l-64-48c-8.1-6-12.8-15.5-12.8-25.6V320.9L9 97.3C-.7 85.4-2.8 68.8 3.9 54.9z", }; + case 'sort': + return { + vb: "0 0 512 512", + path: "M137.4 41.4c12.5-12.5 32.8-12.5 45.3 0l128 128c9.2 9.2 11.9 22.9 6.9 34.9s-16.6 19.8-29.6 19.8H32c-12.9 0-24.6-7.8-29.6-19.8s-2.2-25.7 6.9-34.9l128-128zm0 429.3l-128-128c-9.2-9.2-11.9-22.9-6.9-34.9s16.6-19.8 29.6-19.8H288c12.9 0 24.6 7.8 29.6 19.8s2.2 25.7-6.9 34.9l-128 128c-12.5 12.5-32.8 12.5-45.3 0z", + }; + case 'clear': + return { + vb: "0 0 512 512", + path: "M0 128C0 92.7 28.7 64 64 64H370.7c17 0 33.3 6.7 45.3 18.7L566.6 233.4c6 6 9.4 14.1 9.4 22.6s-3.4 16.6-9.4 22.6L416 429.3c-12 12-28.3 18.7-45.3 18.7H64c-35.3 0-64-28.7-64-64V128zm143 47c-9.4 9.4-9.4 24.6 0 33.9l47 47-47 47c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l47-47 47 47c9.4 9.4 24.6 9.4 33.9 0s9.4-24.6 0-33.9l-47-47 47-47c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-47 47-47-47c-9.4-9.4-24.6-9.4-33.9 0z", + }; default: return { vb: "", path: "" }; // Default path or a placeholder icon } @@ -150,7 +160,7 @@ const IconComponent = component$((props: IconProps) => { const { vb, path } = getSvgPath(props.icon); const svgClass = props.class || ''; const width = props.width || 80; - const height = props.height || 50; + const height = props.height || props.width || 50; return ( diff --git a/web/src/components/psc/checklist-table.tsx b/web/src/components/psc/checklist-table.tsx index 729bb0f..23b82dd 100644 --- a/web/src/components/psc/checklist-table.tsx +++ b/web/src/components/psc/checklist-table.tsx @@ -1,7 +1,8 @@ -import { component$ } from "@builder.io/qwik"; +import { $, component$, useStore, useSignal } from "@builder.io/qwik"; +import { useCSSTransition } from "qwik-transition"; import Icon from "~/components/core/icon"; -import type { Priority, Section } from '../../types/PSC'; +import type { Priority, Section, Checklist } from '../../types/PSC'; import { marked } from "marked"; import { useLocalStorage } from "~/hooks/useLocalStorage"; @@ -10,6 +11,24 @@ export default component$((props: { section: Section }) => { const [completed, setCompleted] = useLocalStorage('PSC_PROGRESS', {}); const [ignored, setIgnored] = useLocalStorage('PSC_IGNORED', {}); + const showFilters = useSignal(false); + const { stage } = useCSSTransition(showFilters, { timeout: 300 }); + + const sortState = useStore({ column: '', ascending: true }); + + const checklist = useSignal(props.section.checklist); + + const originalFilters = { + show: 'all', // 'all', 'remaining', 'completed' + levels: { + recommended: true, + optional: true, + advanced: true, + }, + }; + + const filterState = useStore(originalFilters); + const getBadgeClass = (priority: Priority, precedeClass: string = '') => { switch (priority.toLocaleLowerCase()) { case 'recommended': @@ -41,29 +60,102 @@ export default component$((props: { section: Section }) => { return completed.value[pointId] || false; }; + const filteredChecklist = checklist.value.filter((item) => { + const itemId = generateId(item.point); + const itemCompleted = isChecked(itemId); + const itemIgnored = isIgnored(itemId); + const itemLevel = item.priority; + + // Filter by completion status + if (filterState.show === 'remaining' && (itemCompleted || itemIgnored)) return false; + if (filterState.show === 'completed' && !itemCompleted) return false; + + // Filter by level + return filterState.levels[itemLevel.toLocaleLowerCase() as Priority]; + }); + + const sortChecklist = (a: Checklist, b: Checklist) => { + const getValue = (item: Checklist) => { + switch (sortState.column) { + case 'done': + if (isIgnored(generateId(item.point))) { + return 2; + } + return isChecked(generateId(item.point)) ? 0 : 1; + case 'advice': + return item.point; + case 'level': + return ['recommended', 'optional', 'advanced'].indexOf(item.priority.toLowerCase()); + default: + return 0; + } + }; + const valueA = getValue(a); + const valueB = getValue(b); + + if (valueA === valueB) { + return 0; + } else if (sortState.ascending) { + return valueA < valueB ? -1 : 1; + } else { + return valueA > valueB ? -1 : 1; + } + }; + + const handleSort = $((column: string) => { + if (sortState.column === column) { // Reverse direction if same column + sortState.ascending = !sortState.ascending; + } else { // Sort table by column + sortState.column = column; + sortState.ascending = true; // Default to ascending + } + }); + + const resetFilters = $(() => { + checklist.value = props.section.checklist; + sortState.column = ''; + sortState.ascending = true; + filterState.levels = originalFilters.levels; + filterState.show = originalFilters.show; + }); + return ( <> -
- -
- -
-
+ +
+ {(sortState.column || JSON.stringify(filterState) !== JSON.stringify(originalFilters)) && ( + + )} + +
+ + {showFilters.value && ( +
{/* Filter by completion */}

Show

-
{/* Filter by level */} @@ -71,33 +163,60 @@ export default component$((props: { section: Section }) => {

Filter

-
-
- - + )} - - - + { [ + { id: 'done', text: 'Done?'}, + { id: 'advice', text: 'Advice' }, + { id: 'level', text: 'Level' } + ].map((item) => ( + + ))} - {props.section.checklist.map((item, index) => { + {filteredChecklist.sort(sortChecklist).map((item, index) => { const badgeColor = getBadgeClass(item.priority); const itemId = generateId(item.point); const isItemCompleted = isChecked(itemId); diff --git a/web/tailwind.config.js b/web/tailwind.config.js index 94bbe09..d1f98c9 100644 --- a/web/tailwind.config.js +++ b/web/tailwind.config.js @@ -39,7 +39,7 @@ module.exports = { ], }, safelist: [ - { + { // TODO: This adds a lot of overhead. Go through code, and remove any un-needed variants. pattern: /(bg|outline|text|tw-color|border)-(yellow|lime|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|red)-(200|300|400|500|600)/, variants: ['light', 'dark', 'hover', 'focus'], }, diff --git a/web/yarn.lock b/web/yarn.lock index e467b94..fd53b81 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -3010,6 +3010,11 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +qwik-transition@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/qwik-transition/-/qwik-transition-0.0.7.tgz#57c3d6db5fd0d0bca47a6ddef76a77b39a0b3c8a" + integrity sha512-U1wEJ/RWYuwfuWl8cWToTJASkRogoBGlBKbr/lKhzQYGeLNXk2K7IUXCMtELcUNHEI7J1MyNbjYc5lA6fMbQ8w== + read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
Done?AdviceLevel handleSort(item.id)} + > + + + {item.text} + + Details