diff --git a/src/components/Select.tsx b/src/components/Select.tsx
new file mode 100644
index 0000000..5b256dc
--- /dev/null
+++ b/src/components/Select.tsx
@@ -0,0 +1,87 @@
+import { useEffect, useState } from "react";
+
+export type SelectProps = {
+ options: SelectOption[];
+ selected?: number;
+ allowEmpty?: boolean;
+ className?: string;
+ onChange?: Function;
+};
+
+export type SelectOption = {
+ name: string;
+ label: string;
+};
+
+export function selectOptionsIncludes(
+ options: SelectOption[],
+ newOption: SelectOption
+) {
+ options.map((option) => {
+ if (option.label === newOption.label) return true;
+ });
+ return false;
+}
+
+export default function Select(props: SelectProps): JSX.Element {
+ const [selected, setSelected] = useState(
+ props.selected ? props.selected : props.allowEmpty ? -1 : 0
+ );
+ const [opened, setOpened] = useState(false);
+
+ return (
+
+
+
setOpened(!opened)} className="w-full">
+ {selected === -1 ? "—" : props.options[selected].label}
+
+ {selected >= 0 && props.allowEmpty && (
+
{
+ setSelected(-1);
+ props.onChange && props.onChange("");
+ }}
+ className="material-icons !text-xs"
+ >
+ close
+
+ )}
+
setOpened(!opened)} className="material-icons">
+ {opened ? "arrow_drop_up" : "arrow_drop_down"}
+
+
+
+ {props.options.map((option, index) => (
+ <>
+ {index !== selected && (
+
{
+ setOpened(false);
+ setSelected(index);
+ props.onChange && props.onChange(props.options[index].name);
+ }}
+ >
+ {option.label}
+
+ )}
+ >
+ ))}
+
+
+ );
+}
diff --git a/src/graphql/operation.graphql b/src/graphql/operation.graphql
index 63d9963..a91da45 100644
--- a/src/graphql/operation.graphql
+++ b/src/graphql/operation.graphql
@@ -128,7 +128,6 @@ query getLibraryItemsPreview($language_code: String) {
libraryItems(
filters: { root_item: { eq: true } }
pagination: { limit: -1 }
- sort: ["title:asc", "subtitle:asc"]
) {
data {
id
diff --git a/src/pages/library/index.tsx b/src/pages/library/index.tsx
index 915a284..32cd19c 100644
--- a/src/pages/library/index.tsx
+++ b/src/pages/library/index.tsx
@@ -14,14 +14,43 @@ import {
import PanelHeader from "components/PanelComponents/PanelHeader";
import AppLayout from "components/AppLayout";
import LibraryItemsPreview from "components/Library/LibraryItemsPreview";
+import Select from "components/Select";
+import { useEffect, useState } from "react";
+import { prettyDate, prettyinlineTitle } from "queries/helpers";
type LibraryProps = {
libraryItems: GetLibraryItemsPreviewQuery;
langui: GetWebsiteInterfaceQuery;
};
+type GroupLibraryItems = Map<
+ string,
+ GetLibraryItemsPreviewQuery["libraryItems"]["data"]
+>;
+
export default function Library(props: LibraryProps): JSX.Element {
const langui = props.langui.websiteInterfaces.data[0].attributes;
+
+ const [sortedItems, setSortedItem] = useState<
+ LibraryProps["libraryItems"]["libraryItems"]["data"]
+ >(sortBy("title", props.libraryItems.libraryItems.data));
+
+ const [sortingMethod, setSortingMethod] = useState("title");
+
+ const [groups, setGroups] = useState(
+ getGroups("", sortedItems)
+ );
+
+ const [groupingMethod, setGroupingMethod] = useState("");
+
+ useEffect(() => {
+ setSortedItem(sortBy(sortingMethod, props.libraryItems.libraryItems.data));
+ }, [props.libraryItems.libraryItems.data, sortingMethod]);
+
+ useEffect(() => {
+ setGroups(getGroups(groupingMethod, sortedItems));
+ }, [groupingMethod, sortedItems]);
+
const subPanel = (
+
+
+
+
);
const contentPanel = (
-
- {props.libraryItems.libraryItems.data.map((item) => (
-
- ))}
-
+ {[...groups].map(([name, items]) => (
+ <>
+ {items.length > 0 && (
+ <>
+ {name}
+
+ {items.map((item) => (
+
+ ))}
+
+ >
+ )}
+ >
+ ))}
);
return (
@@ -67,3 +135,118 @@ export const getStaticProps: GetStaticProps = async (context) => {
return { props: {} };
}
};
+
+function getGroups(
+ groupByType: string,
+ items: LibraryProps["libraryItems"]["libraryItems"]["data"]
+): GroupLibraryItems {
+ switch (groupByType) {
+ case "category":
+ return new Map();
+
+ case "type":
+ const groupType: GroupLibraryItems = new Map();
+ groupType.set("Audio", []);
+ groupType.set("Game", []);
+ groupType.set("Textual", []);
+ groupType.set("Video", []);
+ groupType.set("Other", []);
+ groupType.set("No type", []);
+ items.map((item) => {
+ if (item.attributes.metadata.length > 0) {
+ switch (item.attributes.metadata[0].__typename) {
+ case "ComponentMetadataAudio":
+ groupType.get("Audio")?.push(item);
+ break;
+ case "ComponentMetadataGame":
+ groupType.get("Game")?.push(item);
+ break;
+ case "ComponentMetadataBooks":
+ groupType.get("Textual")?.push(item);
+ break;
+ case "ComponentMetadataVideo":
+ groupType.get("Video")?.push(item);
+ break;
+ case "ComponentMetadataOther":
+ groupType.get("Other")?.push(item);
+ break;
+ }
+ } else {
+ groupType.get("No type")?.push(item);
+ }
+ });
+ return groupType;
+
+ case "releaseYear":
+ const years: number[] = [];
+ items.map((item) => {
+ if (item.attributes.release_date) {
+ if (!years.includes(item.attributes.release_date.year))
+ years.push(item.attributes.release_date.year);
+ }
+ });
+ const groupYear: GroupLibraryItems = new Map();
+ years.sort();
+ years.map((year) => {
+ groupYear.set(year.toString(), []);
+ });
+ groupYear.set("No year", []);
+ items.map((item) => {
+ if (item.attributes.release_date) {
+ groupYear
+ .get(item.attributes.release_date.year.toString())
+ ?.push(item);
+ } else {
+ groupYear.get("No year")?.push(item);
+ }
+ });
+
+ return groupYear;
+
+ default:
+ const groupDefault: GroupLibraryItems = new Map();
+ groupDefault.set("", items);
+ return groupDefault;
+ }
+}
+
+function sortBy(
+ orderByType: string,
+ items: LibraryProps["libraryItems"]["libraryItems"]["data"]
+): LibraryProps["libraryItems"]["libraryItems"]["data"] {
+ switch (orderByType) {
+ case "title":
+ return [...items].sort((a, b) => {
+ const titleA = prettyinlineTitle(
+ "",
+ a.attributes.title,
+ a.attributes.subtitle
+ );
+ const titleB = prettyinlineTitle(
+ "",
+ b.attributes.title,
+ b.attributes.subtitle
+ );
+ return titleA.localeCompare(titleB);
+ });
+ case "price":
+ return [...items].sort((a, b) => {
+ const priceA = a.attributes.price ? a.attributes.price.amount : 99999;
+ const priceB = b.attributes.price ? b.attributes.price.amount : 99999;
+ return priceA - priceB;
+ });
+ case "releaseDate":
+ return [...items].sort((a, b) => {
+ const dateA =
+ a.attributes.release_date !== null
+ ? prettyDate(a.attributes.release_date)
+ : "9999";
+ const dateB =
+ b.attributes.release_date !== null
+ ? prettyDate(b.attributes.release_date)
+ : "9999";
+ return dateA.localeCompare(dateB);
+ });
+ }
+ return items;
+}