Skip to content

Commit

Permalink
start work on audit (very much in progress...)
Browse files Browse the repository at this point in the history
  • Loading branch information
dtemkin1 committed Jan 9, 2025
1 parent 5db7d16 commit e8fcea3
Show file tree
Hide file tree
Showing 12 changed files with 1,405 additions and 998 deletions.
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"check": "biome check --write ./src"
},
"dependencies": {
"@ark-ui/solid": "^4.7.0",
"@ark-ui/solid": "^4.8.0",
"@fontsource-variable/inter": "^5.1.1",
"@fontsource-variable/roboto-mono": "^5.1.1",
"@solid-primitives/cookies": "^0.0.1",
Expand All @@ -26,9 +26,8 @@
"@solidjs/meta": "^0.29.4",
"@solidjs/router": "^0.15.2",
"@solidjs/start": "^1.0.11",
"localforage": "^1.10.0",
"lucide-solid": "^0.469.0",
"solid-js": "^1.9.3",
"solid-js": "^1.9.4",
"ua-parser-js": "^2.0.0",
"vinxi": "^0.4.3",
"vite-plugin-solid": "^2.11.0",
Expand All @@ -49,9 +48,9 @@
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^22.10.5",
"jsdom": "^25.0.1",
"jsdom": "^26.0.0",
"postcss": "^8.4.49",
"typescript": "^5.7.2",
"typescript": "^5.7.3",
"typescript-eslint": "^8.19.1",
"vitest": "^2.1.8"
},
Expand Down
1,906 changes: 930 additions & 976 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

252 changes: 249 additions & 3 deletions src/components/Audit.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,253 @@
import type { Component } from "solid-js";
import {
type Component,
For,
Index,
createDeferred,
createEffect,
createMemo,
createSignal,
on,
} from "solid-js";
import { Stack } from "styled-system/jsx";

const Audit: Component = (props) => {
return <div />;
import { CheckIcon, ChevronsUpDownIcon, Store, XIcon } from "lucide-solid";
import { Combobox, createListCollection } from "~/components/ui/combobox";
import { IconButton } from "~/components/ui/icon-button";
import { TagsInput } from "~/components/ui/tags-input";

import { useCombobox, useTagsInput } from "@ark-ui/solid";
import { combobox } from "styled-system/recipes";
import { useCourseDataContext } from "~/context/create";
import type {
CourseRequirements,
CourseRequirementsWithKey,
Reqs,
} from "~/context/types";

const Audit: Component<{
reqList: CourseRequirementsWithKey[];
}> = (props) => {
return (
<Stack>
<SelectProgram reqList={props.reqList} />
</Stack>
);
};

const SelectProgram: Component<{
reqList: CourseRequirementsWithKey[];
}> = (props) => {
// TODO: this entire component is hacky af, try to clean it up later

const [store, { removeReq, addReq }] = useCourseDataContext();
const getCourses = createMemo(() => {
const courses = props.reqList ?? [];
courses.sort(sortCourses);

const courseItems = courses.map((course) => ({
value: course.key,
label: course["medium-title"],
}));
return courseItems;
});
const [items, setItems] = createSignal(getCourses());

const collection = createMemo(() =>
createListCollection({
items: getCourses(),
}),
);

const handleChange = (e: Combobox.InputValueChangeDetails) => {
const filtered = getCourses().filter((item) =>
item.label.toLowerCase().includes(e.inputValue.toLowerCase()),
);
setItems(filtered.length > 0 ? filtered : getCourses());
};

const setSelected = (newReqs: string[]) => {
// TODO: doesnt update components when changing activeRoad, need to fix.
// if (currentReqs().length > newReqs.length) {
// const diff = currentReqs().find((x) => !newReqs.includes(x));
// if (diff) removeReq(diff);
// } else if (currentReqs().length < newReqs.length) {
// const newReq = newReqs[newReqs.length - 1];
// addReq(newReq);
// }

tagsInput()?.setValue(newReqs);
comboboxInput()?.setValue(newReqs);

const currentReqs = store.roads[store.activeRoad].contents.coursesOfStudy;

for (const req of newReqs) {
if (!currentReqs.includes(req)) addReq(req);
}

for (const req of currentReqs) {
if (!newReqs.includes(req)) removeReq(req);
}
};

const currentReqs = createMemo(
() => store.roads[store.activeRoad].contents.coursesOfStudy,
);

const tagsInput = useTagsInput({
editable: false,
value: currentReqs(),
onValueChange: (e) => {
comboboxInput()?.setValue(e.value);
setSelected(e.value);
},
});
const comboboxInput = useCombobox({
onInputValueChange: handleChange,
collection: collection(),
inputBehavior: "autohighlight",
value: currentReqs(),
onValueChange: (e) => {
tagsInput()?.setValue(e.value);
tagsInput()?.clearInputValue();
setSelected(e.value);
},
openOnClick: true,
multiple: true,
});

return (
<Combobox.RootProvider value={comboboxInput}>
{/* @ts-expect-error: it works i swear */}
<TagsInput.RootProvider value={tagsInput}>
<Combobox.Label>Your Programs</Combobox.Label>

<Combobox.Control
asChild={(controlProps) => (
<TagsInput.Control {...controlProps}>
<Index each={tagsInput().value}>
{(value, index) => (
<TagsInput.Item index={index} value={value()}>
<TagsInput.ItemPreview minHeight="fit-content">
<TagsInput.ItemText>
{
getCourses().find((elem) => elem.value === value())
?.label
}
</TagsInput.ItemText>
<TagsInput.ItemInput />
<TagsInput.ItemDeleteTrigger
asChild={(triggerProps) => (
<IconButton
variant="link"
size="xs"
{...triggerProps()}
>
<XIcon />
</IconButton>
)}
/>
<TagsInput.ItemInput />
<TagsInput.HiddenInput />
</TagsInput.ItemPreview>
</TagsInput.Item>
)}
</Index>

<TagsInput.Input
placeholder={
comboboxInput().hasSelectedItems
? undefined
: "Click to add a major"
}
asChild={(inputProps) => <Combobox.Input {...inputProps()} />}
/>
<Combobox.Trigger
asChild={(triggerProps) => (
<IconButton
variant="link"
aria-label="open"
size="xs"
{...triggerProps()}
>
<ChevronsUpDownIcon />
</IconButton>
)}
/>
</TagsInput.Control>
)}
/>

<Combobox.Positioner>
<Combobox.Content maxH="200px" overflowY="auto">
<Combobox.ItemGroup>
<For
each={items()}
fallback={
<Combobox.Item item={null}>
<Combobox.ItemText>No results found</Combobox.ItemText>
</Combobox.Item>
}
>
{(item) => (
<Combobox.Item item={item} minH="fit-content">
<Combobox.ItemText>{item.label}</Combobox.ItemText>
<Combobox.ItemIndicator>
<CheckIcon />
</Combobox.ItemIndicator>
</Combobox.Item>
)}
</For>
</Combobox.ItemGroup>
</Combobox.Content>
</Combobox.Positioner>
</TagsInput.RootProvider>
</Combobox.RootProvider>
);
};

const sortCourses = (c1: CourseRequirements, c2: CourseRequirements) => {
const sortKey = "medium-title" as const;

const a = c1[sortKey].toLowerCase();
const b = c2[sortKey].toLowerCase();

// same type of program
if (
(a.includes("major") && b.includes("major")) ||
(a.includes("minor") && b.includes("minor"))
) {
// get course numbers
let n1 = a.split(" ")[0].split("-")[0];
let n2 = b.split(" ")[0].split("-")[0];

n1 =
Number.isNaN(Number.parseInt(n1)) &&
!Number.isNaN(Number.parseInt(n1.slice(0, -1)))
? n1.slice(0, -1)
: n1;
n2 =
Number.isNaN(Number.parseInt(n2)) &&
!Number.isNaN(Number.parseInt(n2.slice(0, -1)))
? n2.slice(0, -1)
: n2;

if (n1 === n2) return a.localeCompare(b);

return (!Number.isNaN(Number.parseInt(n1)) &&
!Number.isNaN(Number.parseInt(n2))) ||
(Number.isNaN(Number.parseInt(n1)) && Number.isNaN(Number.parseInt(n2)))
? // @ts-expect-error the original function returned n1 - n2 which should be NaN?? idk why this works but oh well
n1 - n2
: !Number.isNaN(Number.parseInt(n1))
? -1
: 1;
}

if (a.includes("major") && b.includes("minor")) return -1;
if (b.includes("major") && a.includes("minor")) return 1;
if (a.includes("major") || a.includes("minor")) return -1;
if (b.includes("major") || b.includes("minor")) return 1;
return a.localeCompare(b);
};

export default Audit;
9 changes: 9 additions & 0 deletions src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,27 @@ import { Box, Flex, HStack, Stack } from "styled-system/jsx";

import { SquareCheckIcon } from "lucide-solid";
import About from "~/components/About";
import Audit from "~/components/Audit";
import Settings from "~/components/Settings";
import ThemeToggler from "~/components/ThemeToggler";
import { Icon } from "~/components/ui/icon";
import { Link } from "~/components/ui/link";
import { Text } from "~/components/ui/text";

import type {
CourseRequirementsWithKey,
Reqs,
SimplifiedSelectedSubjects,
} from "~/context/types";

const Sidebar: Component<{
changeYear: (year: number) => void;
reqList: CourseRequirementsWithKey[];
}> = (props) => {
return (
<Stack>
<SidebarButtons changeYear={props.changeYear} />
<Audit reqList={props.reqList} />
</Stack>
);
};
Expand Down
2 changes: 2 additions & 0 deletions src/components/ui/combobox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { createListCollection } from "@ark-ui/solid/combobox";
export * as Combobox from "./styled/combobox";
Loading

0 comments on commit e8fcea3

Please sign in to comment.