Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented album picker #58

Merged
merged 1 commit into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/core/aspnet:latest
COPY musiccatalogue.api-1.32.0.0 /opt/musiccatalogue.api
COPY musiccatalogue.api-1.33.0.0 /opt/musiccatalogue.api
WORKDIR /opt/musiccatalogue.api/bin
ENTRYPOINT [ "./MusicCatalogue.Api" ]
2 changes: 1 addition & 1 deletion docker/ui/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
FROM node:20-alpine
COPY musiccatalogue.ui-1.32.0.0 /opt/musiccatalogue.ui
COPY musiccatalogue.ui-1.33.0.0 /opt/musiccatalogue.ui
WORKDIR /opt/musiccatalogue.ui
RUN npm install
RUN npm run build
Expand Down
16 changes: 16 additions & 0 deletions src/MusicCatalogue.Api/Controllers/AlbumsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ public async Task<ActionResult<Album>> GetAlbumByIdAsync(int id)
return album;
}

[HttpGet]
[Route("random")]
public async Task<ActionResult<Album?>> GetRandomAlbum()
{
var album = await _factory.Albums.GetRandomAsync(x => !(x.IsWishListItem ?? false));
return album;
}

[HttpGet]
[Route("random/{genreId}")]
public async Task<ActionResult<Album?>> GetRandomAlbum(int genreId)
{
var album = await _factory.Albums.GetRandomAsync(x => !(x.IsWishListItem ?? false) && (x.GenreId == genreId));
return album;
}

/// <summary>
/// Return a list of albums for the specified artist, filtering for items that are on/not on
/// the wishlist based on the arguments
Expand Down
6 changes: 3 additions & 3 deletions src/MusicCatalogue.Api/MusicCatalogue.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ReleaseVersion>1.32.0.0</ReleaseVersion>
<FileVersion>1.32.0.0</FileVersion>
<ProductVersion>1.32.0</ProductVersion>
<ReleaseVersion>1.33.0.0</ReleaseVersion>
<FileVersion>1.33.0.0</FileVersion>
<ProductVersion>1.33.0</ProductVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
Expand Down
1 change: 1 addition & 0 deletions src/MusicCatalogue.Entities/Interfaces/IAlbumManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Task<Album> AddAsync(
int? retailerId);

Task<Album> GetAsync(Expression<Func<Album, bool>> predicate);
Task<Album?> GetRandomAsync(Expression<Func<Album, bool>> predicate);
Task<List<Album>> ListAsync(Expression<Func<Album, bool>> predicate);
Task DeleteAsync(int albumId);
}
Expand Down
23 changes: 23 additions & 0 deletions src/MusicCatalogue.Logic/Database/AlbumManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,29 @@ public async Task<List<Album>> ListAsync(Expression<Func<Album, bool>> predicate
.Include(x => x.Tracks)
.ToListAsync();

/// <summary>
/// Return a randomly selected album from those matching the predicate
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public async Task<Album?> GetRandomAsync(Expression<Func<Album, bool>> predicate)
{
Album? album = null;

// Get a list of albums matching the predicate
var albums = await ListAsync(predicate);

// If there are any, pick one at random
var numberOfAlbums = albums.Count;
if (numberOfAlbums > 0)
{
var index = new Random().Next(0, numberOfAlbums);
album = albums[index];
}

return album;
}

/// <summary>
/// Add an album, if it doesn't already exist
/// </summary>
Expand Down
104 changes: 104 additions & 0 deletions src/music-catalogue-ui/components/albums/albumPicker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React, { useCallback, useState } from "react";
import styles from "./albumPicker.module.css";
import { apiFetchRandomAlbum } from "@/helpers/api/apiAlbums";
import AlbumPickerAlbumRow from "./albumPickerAlbumRow";
import GenreSelector from "../genres/genreSelector";
import { apiFetchArtistById } from "@/helpers/api/apiArtists";

/**
* Component to pick a random album, optionally for a specified genre
* @param {*} logout
* @returns
*/
const AlbumPicker = ({ logout }) => {
const [genre, setGenre] = useState(null);
const [details, setDetails] = useState({ album: null, artist: null });

// Callback to request a random album from the API
const pickAlbumCallback = useCallback(
async (e) => {
// Prevent the default action associated with the click event
e.preventDefault();

// Request a random album, optionally filtering by the selected genre, and
// retrieve the artist details
const genreId = genre != null ? genre.id : null;
const fetchedAlbum = await apiFetchRandomAlbum(genreId, logout);
if (fetchedAlbum != null) {
const fetchedArtist = await apiFetchArtistById(
fetchedAlbum.artistId,
logout
);
setDetails({ album: fetchedAlbum, artist: fetchedArtist });
} else {
setDetails({ album: null, artist: null });
}
},
[genre, logout]
);

return (
<>
<div className="row mb-2 pageTitle">
<h5 className="themeFontColor text-center">Album Picker</h5>
</div>
<div className={styles.albumPickerFormContainer}>
<form className={styles.albumPickerForm}>
<div className="row" align="center">
<div className="mt-3">
<div className="d-inline-flex align-items-center">
<div className="col">
<label className={styles.albumPickerLabel}>
Genre to pick from:
</label>
</div>
<div className="col">
<div className={styles.alunmPickerGenreSelector}>
<GenreSelector
initialGenre={genre}
genreChangedCallback={setGenre}
/>
</div>
</div>
<div className="col">
<button
className="btn btn-primary"
onClick={(e) => pickAlbumCallback(e)}
>
Pick
</button>
</div>
</div>
</div>
</div>
</form>
</div>
<table className="table table-hover">
<thead>
<tr>
<th>Artist</th>
<th>Title</th>
<th>Genre</th>
<th>Released</th>
<th>Purchased</th>
<th>Price</th>
<th>Retailer</th>
</tr>
</thead>
{details.album != null && (
<tbody>
{
<AlbumPickerAlbumRow
key={details.album.id}
album={details.album}
artist={details.artist}
/>
}
</tbody>
)}
</table>
</>
);
};

export default AlbumPicker;
22 changes: 22 additions & 0 deletions src/music-catalogue-ui/components/albums/albumPicker.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.albumPickerFormContainer {
display: flex;
justify-content: center;
align-items: center;
}

.albumPickerForm {
width: 100vw;
padding-top: 20px;
padding-bottom: 20px;
}

.albumPickerLabel {
margin-right: 20px;
font-size: 14px;
font-weight: 600;
color: rgb(34, 34, 34);
}

.alunmPickerGenreSelector {
width: 200px;
}
39 changes: 39 additions & 0 deletions src/music-catalogue-ui/components/albums/albumPickerAlbumRow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import CurrencyFormatter from "../common/currencyFormatter";
import DateFormatter from "../common/dateFormatter";

/**
* Component to render a row containing the details of a single album in a
* genre
* @param {*} album
* @param {*} artist
* @returns
*/
const AlbumPickerAlbumRow = ({ album, artist }) => {
const purchaseDate = new Date(album.purchased);

return (
<tr>
<td>{artist.name}</td>
<td>{album.title}</td>
<td>{album.genre.name}</td>
{album.released > 0 ? <td>{album.released}</td> : <td />}
{purchaseDate > 1900 ? (
<td>
<DateFormatter value={album.purchased} />
</td>
) : (
<td />
)}
{album.price > 0 ? (
<td>
<CurrencyFormatter value={album.price} />
</td>
) : (
<td />
)}
{album.retailer != null ? <td>{album.retailer.name}</td> : <td />}
</tr>
);
};

export default AlbumPickerAlbumRow;
3 changes: 3 additions & 0 deletions src/music-catalogue-ui/components/componentPicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import ManufacturerEditor from "./manufacturers/manufacturerEditor";
import EquipmentList from "./equipment/equipmentList";
import EquipmentPurchaseDetails from "./equipment/equpimentPurchaseDetails";
import EquipmentEditor from "./equipment/equipmentEditor";
import AlbumPicker from "./albums/albumPicker";

/**
* Component using the current context to select and render the current page
Expand Down Expand Up @@ -84,6 +85,8 @@ const ComponentPicker = ({ context, navigate, logout }) => {
logout={logout}
/>
);
case pages.albumPicker:
return <AlbumPicker navigate={navigate} logout={logout} />;
case pages.tracks:
return (
<TrackList
Expand Down
3 changes: 3 additions & 0 deletions src/music-catalogue-ui/components/menuBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ const MenuBar = ({ navigate, logout }) => {
</div>
</button>
<div className={styles.dropdownContent}>
<a onClick={() => navigate({ page: pages.albumPicker })}>
Album Picker
</a>
<a onClick={() => navigate({ page: pages.artists, filter: "A" })}>
Artists
</a>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useCallback, useState } from "react";
import styles from "./reports.module.css";
import "react-datepicker/dist/react-datepicker.css";
// import "react-datepicker/dist/react-datepicker.css";
import { apiGenreAlbumsReport } from "@/helpers/api/apiReports";
import GenreAlbumRow from "./genreAlbumRow";
import ReportExportControls from "./reportExportControls";
Expand Down Expand Up @@ -91,7 +91,7 @@ const GenreAlbumsReport = ({ logout }) => {
<label className={styles.reportFormLabel}>Report For:</label>
</div>
<div className="col">
<div className={styles.genreSelector}>
<div className={styles.reportGenreSelector}>
<GenreSelector
initialGenre={genre}
genreChangedCallback={setGenre}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,6 @@
text-align: center;
}

.genreSelector {
.reportGenreSelector {
width: 200px;
}
21 changes: 21 additions & 0 deletions src/music-catalogue-ui/helpers/api/apiAlbums.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,26 @@ const apiSetAlbumPurchaseDetails = async (
return response;
};

/**
* Return the album details for a randomly selected album from the
* Music Catalogue REST API
* @param {*} albumId
* @param {*} logout
* @returns
*/
const apiFetchRandomAlbum = async (genreId, logout) => {
// Call the API to get the details for a randomly selected album
const baseUrl = `${config.api.baseUrl}/albums/random`;
const url = genreId != null ? `${baseUrl}/${genreId}` : baseUrl;
const response = await fetch(url, {
method: "GET",
headers: apiGetHeaders(),
});

const album = await apiReadResponseData(response, logout);
return album;
};

export {
apiCreateAlbum,
apiUpdateAlbum,
Expand All @@ -274,4 +294,5 @@ export {
apiLookupAlbum,
apiSetAlbumWishListFlag,
apiSetAlbumPurchaseDetails,
apiFetchRandomAlbum,
};
1 change: 1 addition & 0 deletions src/music-catalogue-ui/helpers/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const pages = {
albums: "Albums",
albumEditor: "AlbumEditor",
albumPurchaseDetails: "AlbumPurchaseDetails",
albumPicker: "AlbumPicker",
wishlistAlbums: "WishlistAlbums",
tracks: "Tracks",
trackEditor: "TrackEditor",
Expand Down
Loading