diff --git a/server/src/graphQL/mockAlbums.ts b/server/src/graphQL/mockAlbums.ts
index b4b5d3d..a431153 100644
--- a/server/src/graphQL/mockAlbums.ts
+++ b/server/src/graphQL/mockAlbums.ts
@@ -50,4 +50,112 @@ export const MOCK_ALBUMS: Album[] = [
{ title: 'Beat It', duration: 258000 },
],
},
+ {
+ id: '4',
+ title: 'In Rainbows',
+ artist: 'Radiohead',
+ releaseYear: 2007,
+ genre: 'Rock',
+ coverUrl: null,
+ tracks: [
+ { title: '15 Step', duration: 237000 },
+ { title: 'Bodysnatchers', duration: 242000 },
+ ],
+ },
+ {
+ id: '5',
+ title: 'Number Ones',
+ artist: 'Michael Jackson',
+ releaseYear: 2002,
+ genre: 'Pop',
+ coverUrl: null,
+ tracks: [
+ { title: 'Billie Jean', duration: 294000 },
+ { title: 'Beat It', duration: 258000 },
+ ],
+ },
+ {
+ id: '6',
+ title: '9 to 5 and Odd Jobs',
+ artist: 'Dolly Parton',
+ releaseYear: 1980,
+ genre: 'Country',
+ coverUrl: null,
+ tracks: [
+ { title: '9 to 5', duration: 163000 },
+ { title: 'Hush-A-Bye Hard Times', duration: 210000 },
+ ],
+ },
+ {
+ id: '7',
+ title: 'Rumours',
+ artist: 'Fleetwood Mac',
+ releaseYear: 1977,
+ genre: 'Rock',
+ coverUrl: null,
+ tracks: [
+ { title: 'Dreams', duration: 257000 },
+ { title: 'The Chain', duration: 270000 },
+ ],
+ },
+ {
+ id: '8',
+ title: 'Back to Black',
+ artist: 'Amy Winehouse',
+ releaseYear: 2006,
+ genre: 'Soul',
+ coverUrl: null,
+ tracks: [
+ { title: 'Rehab', duration: 214000 },
+ { title: 'Back to Black', duration: 241000 },
+ ],
+ },
+ {
+ id: '9',
+ title: 'Discovery',
+ artist: 'Daft Punk',
+ releaseYear: 2001,
+ genre: 'Electronic',
+ coverUrl: null,
+ tracks: [
+ { title: 'One More Time', duration: 320000 },
+ { title: 'Digital Love', duration: 298000 },
+ ],
+ },
+ {
+ id: '10',
+ title: 'AM',
+ artist: 'Arctic Monkeys',
+ releaseYear: 2013,
+ genre: 'Rock',
+ coverUrl: null,
+ tracks: [
+ { title: 'Do I Wanna Know?', duration: 272000 },
+ { title: 'R U Mine?', duration: 201000 },
+ ],
+ },
+ {
+ id: '11',
+ title: '2',
+ artist: 'Mac DeMarco',
+ releaseYear: 2012,
+ genre: 'Pop',
+ coverUrl: null,
+ tracks: [
+ { title: 'Dreaming', duration: 147000 },
+ { title: 'Freaking Out the Neighborhood', duration: 173000 },
+ ],
+ },
+ {
+ id: '12',
+ title: 'Doggystyle',
+ artist: 'Snoop Dogg',
+ releaseYear: 1993,
+ genre: 'HipHop',
+ coverUrl: null,
+ tracks: [
+ { title: 'Bathtub', duration: 110000 },
+ { title: 'G Funk Intro', duration: 144000 },
+ ],
+ },
];
\ No newline at end of file
diff --git a/server/src/graphQL/resolvers.ts b/server/src/graphQL/resolvers.ts
index 3d26963..4dff658 100644
--- a/server/src/graphQL/resolvers.ts
+++ b/server/src/graphQL/resolvers.ts
@@ -1,8 +1,9 @@
+import { start } from 'node:repl';
import { MOCK_ALBUMS } from './mockAlbums.js';
export const resolvers = {
Query: {
- albums: (_: unknown, args: { search?: string; genre?: string; decade?: number; first?: number }) => {
+ albums: (_: unknown, args: { search?: string; genre?: string; decade?: number; first?: number, after?: string }) => {
let filtered = [...MOCK_ALBUMS];
// Søkefilter på tittel eller artist
@@ -30,14 +31,28 @@ export const resolvers = {
);
}
- const limit = args.first || 10;
- const sliced = filtered.slice(0, limit);
+ const limit = args.first ?? 8;
+
+ let startIndex = 0;
+
+ if (args.after) {
+ const cursorIndex = filtered.findIndex(
+ (album) => album.id === args.after
+ );
+
+ if (cursorIndex !== -1) {
+ startIndex = cursorIndex + 1;
+ }
+ }
+
+ const sliced = filtered.slice(startIndex, startIndex + limit);
return {
edges: sliced,
+ totalCount: filtered.length,
pageInfo: {
endCursor: sliced.length > 0 ? sliced[sliced.length - 1].id : null,
- hasNextPage: filtered.length > limit,
+ hasNextPage: startIndex + sliced.length < filtered.length,
},
};
},
diff --git a/server/src/graphQL/schema.ts b/server/src/graphQL/schema.ts
index 70734f9..ebbd282 100644
--- a/server/src/graphQL/schema.ts
+++ b/server/src/graphQL/schema.ts
@@ -11,7 +11,7 @@ export const typeDefs = `#graphql
releaseYear: Int!
genre: String!
coverUrl: String
- tracks: [Track!]
+ tracks: [Track!]!
}
type PageInfo {
@@ -22,6 +22,7 @@ export const typeDefs = `#graphql
type AlbumConnection {
edges: [Album!]!
pageInfo: PageInfo!
+ totalCount: Int!
}
type Query {
@@ -30,7 +31,7 @@ export const typeDefs = `#graphql
genre: String
decade: Int
sortBy: String
- first: Int = 10
+ first: Int = 8
after: String
): AlbumConnection!
diff --git a/src/components/AlbumCard/AlbumCard.tsx b/src/components/AlbumCard/AlbumCard.tsx
index e6a1673..07e8ad2 100644
--- a/src/components/AlbumCard/AlbumCard.tsx
+++ b/src/components/AlbumCard/AlbumCard.tsx
@@ -28,7 +28,7 @@ function AlbumCard({ album }: AlbumCardProps) {
{album.artist}
{album.releaseYear}
- {album.genre.join(' / ')}
+ {album.genre}
diff --git a/src/graphQL/queries.ts b/src/graphQL/queries.ts
index d6a8d35..682bfb0 100644
--- a/src/graphQL/queries.ts
+++ b/src/graphQL/queries.ts
@@ -1,7 +1,5 @@
import { gql } from '@apollo/client'
-// Temporary types matching the current mock graphql schema
-// Update when the backend switches to the PostgreSQL-backend typeDefs
export const GET_ALBUM = gql`
query GetAlbum($id: ID!) {
album(id: $id) {
@@ -18,4 +16,36 @@ export const GET_ALBUM = gql`
}
}
}
+`
+
+export const GET_ALBUMS = gql`
+ query GetAlbums(
+ $search: String
+ $genre: String
+ $decade: Int
+ $first: Int
+ $after: String
+ ) {
+ albums(
+ search: $search
+ genre: $genre
+ decade: $decade
+ first: $first
+ after: $after
+ ) {
+ edges {
+ id
+ title
+ artist
+ releaseYear
+ genre
+ coverUrl
+ }
+ totalCount
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ }
+ }
`
\ No newline at end of file
diff --git a/src/pages/CataloguePage.tsx b/src/pages/CataloguePage.tsx
index 1baa16a..bec3dbb 100644
--- a/src/pages/CataloguePage.tsx
+++ b/src/pages/CataloguePage.tsx
@@ -1,23 +1,59 @@
-import { useEffect, useMemo, useState } from 'react'
+import { useEffect, useState } from 'react'
import '../App.css'
-import albumData from '../mock/albums.json'
import type { Album } from '../types/album'
import AlbumList from '../components/AlbumList/AlbumList'
import SearchBar from '../components/SearchBar/SearchBar'
import FilterControls from '../components/FilterControls/FilterControls'
import Header from '../components/Header/Header'
+import { useQuery } from '@apollo/client/react'
+import { GET_ALBUMS } from '../graphQL/queries'
-function CataloguePage() {
- const albums: Album[] = albumData as Album[]
+type GetAlbumsData = {
+ albums: {
+ edges: Album[]
+ totalCount: number
+ pageInfo: {
+ endCursor: string | null
+ hasNextPage: boolean
+ }
+ }
+}
+
+type GetAlbumsVariables = {
+ search?: string
+ genre?: string
+ decade?: number
+ first?: number
+ after?: string | null
+}
+function CataloguePage() {
const [search, setSearch] = useState('')
+ const [debouncedSearch, setDebouncedSearch] = useState('')
+
+ useEffect(() => {
+ const timeout = setTimeout(() => {
+ setDebouncedSearch(search)
+ }, 400)
+
+ return () => clearTimeout(timeout)
+ }, [search])
+
const [genre, setGenre] = useState('All genres')
const [decade, setDecade] = useState('All decades')
+
const [rating, setRating] = useState(null)
+
const [filtersVisible, setFiltersVisible] = useState(true)
+
const [currentPage, setCurrentPage] = useState(1)
+ const [after, setAfter] = useState(null)
+ const [cursorHistory, setCursorHistory] = useState<(string | null)[]>([])
+
+ const selectedGenre = genre === 'All genres' ? undefined : genre
+ const selectedDecade = decade === 'All decades' ? undefined : parseInt(decade)
const genres = [
'All genres',
@@ -51,43 +87,31 @@ function CataloguePage() {
'2020s',
]
- const filteredAlbums = useMemo(() => {
- return albums.filter((album) => {
- const matchesSearch =
- `${album.title} ${album.artist}`
- .toLowerCase()
- .includes(search.toLowerCase())
-
- const matchesGenre =
- genre === 'All genres' || album.genre.includes(genre)
-
- const albumDecade = `${Math.floor(album.releaseYear / 10) * 10}s`
- const matchesDecade =
- decade === 'All decades' || albumDecade === decade
-
- // Rating er kun en visuell kontroll inntil rating-data finnes.
- const matchesRating = rating === null
-
- return (
- matchesSearch &&
- matchesGenre &&
- matchesDecade &&
- matchesRating
- )
- })
- }, [albums, search, genre, decade, rating])
-
const pageSize = filtersVisible ? 6 : 8
- const totalPages = Math.ceil(filteredAlbums.length / pageSize)
- const visibleAlbums = filteredAlbums.slice(
- (currentPage - 1) * pageSize,
- currentPage * pageSize,
- )
+ const { data, loading, error } = useQuery<
+ GetAlbumsData,
+ GetAlbumsVariables
+ >(GET_ALBUMS, {
+ variables: {
+ search: debouncedSearch || undefined,
+ genre: selectedGenre,
+ decade: selectedDecade,
+ first: pageSize,
+ after,
+ },
+ })
useEffect(() => {
setCurrentPage(1)
- }, [search, genre, decade, filtersVisible])
+ setAfter(null)
+ setCursorHistory([])
+ }, [debouncedSearch, genre, decade, filtersVisible])
+
+ const albums = data?.albums.edges ?? []
+ const totalCount = data?.albums.totalCount ?? 0
+
+ const totalPages = Math.ceil(totalCount / pageSize)
function clearFilters() {
setSearch('')
@@ -96,6 +120,26 @@ function CataloguePage() {
setRating(null)
}
+ function nextPage() {
+ const endCursor = data?.albums.pageInfo.endCursor
+
+ if (!endCursor) return
+
+ setCursorHistory((history) => [...history, after])
+ setAfter(endCursor)
+ setCurrentPage((page) => page + 1)
+ }
+
+ function previousPage() {
+ if (currentPage <= 1) return
+
+ const previousAfter = cursorHistory[cursorHistory.length - 1] ?? null
+
+ setAfter(previousAfter)
+ setCursorHistory((history) => history.slice(0, -1))
+ setCurrentPage((page) => page - 1)
+ }
+
return (
@@ -183,25 +227,28 @@ function CataloguePage() {
>
- {filteredAlbums.length}{' '}
- {filteredAlbums.length === 1 ? 'album' : 'albums'} found
+ {totalCount}{' '}
+ {totalCount === 1 ? 'album' : 'albums'} found
CURATED FOR YOU
- {filteredAlbums.length === 0 ? (
-
+ {loading ? (
+
Loading albums...
+ ) : error ? (
+
Error loading albums: {error.message}
+ ) : totalCount === 0 ? (
+
No albums found
Try another search or adjust your filters.
-
) : (
-
+
)}