import { $, component$, useStore, useSignal } from "@builder.io/qwik"; import { useCSSTransition } from "qwik-transition"; import Icon from "~/components/core/icon"; import type { Priority, Section, Checklist } from '../../types/PSC'; import { marked } from "marked"; import { useLocalStorage } from "~/hooks/useLocalStorage"; import styles from './psc.module.css'; 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: { essential: true, optional: true, advanced: true, }, }; const filterState = useStore(originalFilters); const getBadgeClass = (priority: Priority, precedeClass: string = '') => { switch (priority.toLocaleLowerCase()) { case 'essential': 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 isIgnored = (pointId: string) => { return ignored.value[pointId] || false; }; const isChecked = (pointId: string) => { if (isIgnored(pointId)) return false; 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 ['essential', '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; }); const calculateProgress = (): { done: number, total: number, percent: number, disabled: number} => { let done = 0; let disabled = 0; let total = 0; props.section.checklist.forEach((item) => { const itemId = generateId(item.point); if (isIgnored(itemId)) { disabled += 1; } else if (isChecked(itemId)) { done += 1; total += 1; } else { total += 1; } }); const percent = Math.round((done / total) * 100); return { done, total: props.section.checklist.length, percent, disabled }; }; const { done, total, percent, disabled } = calculateProgress(); return ( <>

{done} out of {total} ({percent}%) complete, {disabled} ignored

{(sortState.column || JSON.stringify(filterState) !== JSON.stringify(originalFilters)) && ( )}
{showFilters.value && (
{/* Filter by completion */}

Show

{/* Filter by level */}

Filter

)} { [ { id: 'done', text: 'Done?'}, { id: 'advice', text: 'Advice' }, { id: 'level', text: 'Level' } ].map((item) => ( ))} {filteredChecklist.sort(sortChecklist).map((item, index) => { const badgeColor = getBadgeClass(item.priority); const itemId = generateId(item.point); const isItemCompleted = isChecked(itemId); const isItemIgnored = isIgnored(itemId); return ( )} )}
handleSort(item.id)} > {item.text} Details
{ const data = completed.value; data[itemId] = !data[itemId]; setCompleted(data); }} /> { const ignoredData = ignored.value; ignoredData[itemId] = !ignoredData[itemId]; setIgnored(ignoredData); const completedData = completed.value; completedData[itemId] = false; setCompleted(completedData); }} />
{item.priority}
); });