diff --git a/package-lock.json b/package-lock.json index f18b9f1..4644da6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "CommerceQuest-website", + "name": "app", "version": "0.0.1", "lockfileVersion": 3, "requires": true, @@ -19,11 +19,13 @@ "@astrojs/tailwind": "^5.1.2", "@keystatic/astro": "^5.0.3", "@keystatic/core": "^0.5.41", - "@preline/accordion": "^2.4.1", - "@preline/collapse": "^2.4.1", - "@preline/dropdown": "^2.4.1", - "@preline/overlay": "^2.4.1", - "@preline/tabs": "^2.4.1", + "@preline/accordion": "^2.6.0", + "@preline/collapse": "^2.6.0", + "@preline/dropdown": "^2.6.0", + "@preline/overlay": "^2.6.0", + "@preline/select": "^2.6.0", + "@preline/tabs": "^2.6.0", + "@tanstack/table-core": "^8.20.5", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "astro": "^4.16.14", @@ -2547,33 +2549,39 @@ } }, "node_modules/@preline/accordion": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@preline/accordion/-/accordion-2.5.0.tgz", - "integrity": "sha512-vQnRTJ0W1FNHsgwgQ/i6OXXDkRuT1oaR9WNjgTX8jeldQfnU40NYCJdjyhdPX5kCkNH2Q8AW6ihG/8XI2DNGJA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@preline/accordion/-/accordion-2.6.0.tgz", + "integrity": "sha512-MWZrV1RC0urfG9KuaKTd316gXK8yc8vIq3XtABCVbEf43hNRYUqLFbRPD3JTbdHtYiD/xT53NZkFC0CIvGj1yg==", "license": "Licensed under MIT and Preline UI Fair Use License" }, "node_modules/@preline/collapse": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@preline/collapse/-/collapse-2.5.0.tgz", - "integrity": "sha512-FFVB+T7FXn66EUmwgR8c1OtxXDlL74pRvBXtntXwwQ+9fqCZML1xPTOXfdeRL4e46kroAtigVcp/Y28lEpkXTQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@preline/collapse/-/collapse-2.6.0.tgz", + "integrity": "sha512-0w/Gd5g2N81atuMhEikAyW/88yIk/Gt7wnrIMB5NZQry5CWj8SAf0kksb2wtFF5uakA/tnbSuXzeCcabbZe0uw==", "license": "Licensed under MIT and Preline UI Fair Use License" }, "node_modules/@preline/dropdown": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@preline/dropdown/-/dropdown-2.5.0.tgz", - "integrity": "sha512-ydKGBx02eLWzmCn8gkaaTe/gXXqvkZKZCgCYCWETRdy31qhaam43hvJMcpsGOROWSjlPNBgVyGrwYdAgJUO68g==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@preline/dropdown/-/dropdown-2.6.0.tgz", + "integrity": "sha512-Ryjeo0tOnsxzT9dI1S4gybUwTcvQPszVTqxmxry1yxIaKWzpom2As/AFoYETZ/8EbdQxr7G6CQj4d0Fw+9u+mA==", "license": "Licensed under MIT and Preline UI Fair Use License" }, "node_modules/@preline/overlay": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@preline/overlay/-/overlay-2.5.0.tgz", - "integrity": "sha512-5eWzP2N3sTOKhgoefXdrrxUNQh8F678von6ehN1cD79ujVfShYdWowvjtzMlUiTePbF0pgM/wG6D6mRzicOMzw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@preline/overlay/-/overlay-2.6.0.tgz", + "integrity": "sha512-UHwvQ/urc9JXkepINlFUXYYZg6Z4FMKyN2YO/2Kn7mCbpY+QbXtoRBvq+8j9aC2MFOvV6HQhm1yLbCibt1qwFA==", + "license": "Licensed under MIT and Preline UI Fair Use License" + }, + "node_modules/@preline/select": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@preline/select/-/select-2.6.0.tgz", + "integrity": "sha512-plQ2DDmMx8qr6TaObutbbC9H+Pf3HxJe0jV8vcOiFXch+ze+sCuZV2UmQLno2frSaBzqoNDOdO2W1ywpAzPJfA==", "license": "Licensed under MIT and Preline UI Fair Use License" }, "node_modules/@preline/tabs": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@preline/tabs/-/tabs-2.5.0.tgz", - "integrity": "sha512-JdMwRCtbJ0G9goqU+6214/ncfJX8zthEeLl1i9YYyeFLqTJvXPTIW5MWKyXpR4Gi1RUjr2NnT86Abtjp97bESg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@preline/tabs/-/tabs-2.6.0.tgz", + "integrity": "sha512-7zL1TAwRHjYyhMAX7HrJ56Xa8wgTnWBXl+5PsGKJsAeHrv09k5DbRKLYvAMbAROXg/ZRz8Z5Dko96/AUhuNecg==", "license": "Licensed under MIT and Preline UI Fair Use License" }, "node_modules/@react-aria/actiongroup": { @@ -4556,6 +4564,19 @@ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20" } }, + "node_modules/@tanstack/table-core": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@toeverything/y-indexeddb": { "version": "0.10.0-canary.9", "resolved": "https://registry.npmjs.org/@toeverything/y-indexeddb/-/y-indexeddb-0.10.0-canary.9.tgz", diff --git a/package.json b/package.json index d0793c1..413714c 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,13 @@ "@astrojs/tailwind": "^5.1.2", "@keystatic/astro": "^5.0.3", "@keystatic/core": "^0.5.41", - "@preline/accordion": "^2.4.1", - "@preline/collapse": "^2.4.1", - "@preline/dropdown": "^2.4.1", - "@preline/overlay": "^2.4.1", - "@preline/tabs": "^2.4.1", + "@preline/accordion": "^2.6.0", + "@preline/collapse": "^2.6.0", + "@preline/dropdown": "^2.6.0", + "@preline/overlay": "^2.6.0", + "@preline/select": "^2.6.0", + "@preline/tabs": "^2.6.0", + "@tanstack/table-core": "^8.20.5", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "astro": "^4.16.14", diff --git a/src/components/ui/tables/CommunityToolsTable.astro b/src/components/ui/tables/CommunityToolsTable.astro index 6dfbb48..48fd903 100644 --- a/src/components/ui/tables/CommunityToolsTable.astro +++ b/src/components/ui/tables/CommunityToolsTable.astro @@ -1,89 +1,401 @@ --- -import type { CommunityToolCollection } from "../../../types"; +import type { CommunityToolCollection } from "@/types"; +import TableSelectFilter from "@components/ui/tables/TableSelectFilter.astro"; interface Props { tools: CommunityToolCollection; } + +function getUniqueAttributes(tools: CommunityToolCollection) { + const creatorNames = new Set(); + const tags = new Set(); + const licenses = new Set(); + + for (const tool of tools) { + tool.creators.forEach(creator => creatorNames.add(creator.name)); + (tool.tags || []).forEach(tag => tags.add(tag)); + licenses.add(tool.license); + } + + return { + creatorNames: Array.from(creatorNames), + tags: Array.from(tags), + licenses: Array.from(licenses), + }; +} + +const { tools } = Astro.props; +const { creatorNames, tags, licenses } = getUniqueAttributes(tools); ---
-
- - - - - - - - - - - - - - - - - - { - Astro.props.tools.map((toolRow) => ( - - - - - - - - - - - - )) - } +
+ +
+ + +
+ +
+
+ +
+
+ {tags.length > 0 && } + {licenses.length > 0 && } + {creatorNames.length > 0 && } + +
+
-
-
- - - About - LicenseCreatorLinks
-
-

{toolRow.title}

-

{toolRow.subtitle}

-
-
-
- -
-
-
- {toolRow.license} -
-
-
- { - toolRow.creators.map((creator) => ( - {creator.name} - )) - } -
-
-
- { - toolRow.links.map((link) => ( - {link.label} - )) - } -
-
+
+
+ + \ No newline at end of file diff --git a/src/components/ui/tables/TableSelectFilter.astro b/src/components/ui/tables/TableSelectFilter.astro new file mode 100644 index 0000000..8993e6e --- /dev/null +++ b/src/components/ui/tables/TableSelectFilter.astro @@ -0,0 +1,42 @@ +--- + +interface Props { + selectId: string, + placeholder: string, + options: string[], +} + +const { selectId, placeholder, options } = Astro.props; + +--- +
+ +
+ + + \ No newline at end of file diff --git a/src/components/ui/tables/tanstackTableExtender.ts b/src/components/ui/tables/tanstackTableExtender.ts new file mode 100644 index 0000000..8f6a01e --- /dev/null +++ b/src/components/ui/tables/tanstackTableExtender.ts @@ -0,0 +1,47 @@ +import type { + RowData, + Table, + TableOptions, + TableOptionsResolved +} from "@tanstack/table-core"; +import { createTable } from "@tanstack/table-core"; +import '@tanstack/table-core' + +declare module '@tanstack/table-core' { + interface ColumnMeta { + thClassList?: string[], + tdClassList?: string[] + } +} + +export function flexRender(comp: any, props: TProps) { + if (typeof comp === 'function') { + return comp(props) + } + return comp +} + +export function createVanillaTable( + options: TableOptions +): Table { + + // Compose in the generic options to the user options + const resolvedOptions: TableOptionsResolved = { + state: {}, // Dummy state + renderFallbackValue: null, + ...options, + onStateChange: (updater) => { + if (typeof updater === 'function') { + table.options.state = updater(table.getState()) + } else { + table.options.state = updater + } + options.onStateChange?.(updater) + }, + } + + // Create the table + const table = createTable(resolvedOptions) + + return table +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index f6f4274..e47d867 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,45 @@ +export interface NavSubmenuEntry { + label: string; + href?: string; + submenu?: NavSubmenuEntry[]; +} + +export interface NavEntry { + label?: string; + href?: string; + isExternal?: boolean; + submenu?: NavEntry[] | undefined; +} + +export type CommunityToolCreator = { + name: string; + url?: string; +} + +export type CommunityToolContributor = { + name: string; + url?: string; +} + +export type CommunityToolLink = { + label: string; + url: string; +} + +export type CommunityTool = { + title: string; + subtitle: string | undefined; + description: string; + tags?: string[]; + license: string; + creators: CommunityToolCreator[]; + contributors: CommunityToolContributor[]; + links: CommunityToolLink[]; +} + +export type CommunityToolCollection = CommunityTool[]; + +// Define the Spryker certifications interface export interface SprykerCertifications { backEndDeveloper: boolean; solutionArchitect: boolean; @@ -32,39 +74,4 @@ export interface KeystaticFreelancer { export interface Freelancer extends KeystaticFreelancer { id: string; -} - -export interface NavSubmenuEntry { - label: string; - href?: string; - submenu?: NavSubmenuEntry[]; -} - -export interface NavEntry { - label: string; - href?: string; - submenu?: NavSubmenuEntry[]; -} - -export interface CommunityToolCreator { - name: string; - url: string; -} - -export interface CommunityToolLink { - url: string; - label: string; -} - -export interface CommunityTool { - title: string; - subtitle: string; - description: string; - license: string; - creators: CommunityToolCreator[]; - links: CommunityToolLink[]; - tags?: string[]; - contributors?: string[]; -} - -export type CommunityToolCollection = CommunityTool[]; +} \ No newline at end of file diff --git a/src/utils/sorting.ts b/src/utils/sorting.ts new file mode 100644 index 0000000..7033b73 --- /dev/null +++ b/src/utils/sorting.ts @@ -0,0 +1,52 @@ +const reSplitAlphaNumeric = /([0-9]+)/gm + +// Mixed sorting is slow, but very inclusive of many edge cases. +// It handles numbers, mixed alphanumeric combinations, and even +// null, undefined, and Infinity +function compareAlphanumeric(aStr: string, bStr: string) { + // Split on number groups, but keep the delimiter + // Then remove falsey split values + const a = aStr.split(reSplitAlphaNumeric).filter(Boolean) + const b = bStr.split(reSplitAlphaNumeric).filter(Boolean) + + // While + while (a.length && b.length) { + const aa = a.shift()! + const bb = b.shift()! + + const an = parseInt(aa, 10) + const bn = parseInt(bb, 10) + + const combo = [an, bn].sort() + + // Both are string + if (isNaN(combo[0]!)) { + if (aa > bb) { + return 1 + } + if (bb > aa) { + return -1 + } + continue + } + + // One is a string, one is a number + if (isNaN(combo[1]!)) { + return isNaN(an) ? -1 : 1 + } + + // Both are numbers + if (an > bn) { + return 1 + } + if (bn > an) { + return -1 + } + } + + return a.length - b.length +} + +export { + compareAlphanumeric, +}; \ No newline at end of file