diff --git a/README.md b/README.md index 91822b4..9976677 100644 --- a/README.md +++ b/README.md @@ -262,6 +262,12 @@ MusicCatalogue.LookupTool --lookup "John Coltrane" "Blue Train" catalogue - Click on the "Search" button to view the results - Records are included from 00:00 on the start date up to and including 23:59 on the end date +#### Monthly Spend Report + +- To view a report on spending by year and month, click on the "Reports > Monthly Spend" menu bar item: + +Monthly Spend Report + [^ top](#musiccatalogue) ## Web Service diff --git a/diagrams/reports-monthly-spend.png b/diagrams/reports-monthly-spend.png new file mode 100644 index 0000000..0a93f30 Binary files /dev/null and b/diagrams/reports-monthly-spend.png differ diff --git a/docker/api/Dockerfile b/docker/api/Dockerfile index 4794fd7..e5b8d27 100644 --- a/docker/api/Dockerfile +++ b/docker/api/Dockerfile @@ -1,4 +1,4 @@ FROM mcr.microsoft.com/dotnet/core/aspnet:latest -COPY musiccatalogue.api-1.15.0.0 /opt/musiccatalogue.api-1.15.0.0 -WORKDIR /opt/musiccatalogue.api-1.15.0.0/bin +COPY musiccatalogue.api-1.16.0.0 /opt/musiccatalogue.api-1.16.0.0 +WORKDIR /opt/musiccatalogue.api-1.16.0.0/bin ENTRYPOINT [ "./MusicCatalogue.Api" ] diff --git a/docker/ui/Dockerfile b/docker/ui/Dockerfile index 2694576..1b1911e 100644 --- a/docker/ui/Dockerfile +++ b/docker/ui/Dockerfile @@ -1,6 +1,6 @@ FROM node:20-alpine -COPY musiccatalogue.ui-1.15.0.0 /opt/musiccatalogue.ui-1.15.0.0 -WORKDIR /opt/musiccatalogue.ui-1.15.0.0 +COPY musiccatalogue.ui-1.16.0.0 /opt/musiccatalogue.ui-1.16.0.0 +WORKDIR /opt/musiccatalogue.ui-1.16.0.0 RUN npm install RUN npm run build ENTRYPOINT [ "npm", "start" ] diff --git a/src/MusicCatalogue.Api/Controllers/ReportsController.cs b/src/MusicCatalogue.Api/Controllers/ReportsController.cs index 36a31a9..5bb0af1 100644 --- a/src/MusicCatalogue.Api/Controllers/ReportsController.cs +++ b/src/MusicCatalogue.Api/Controllers/ReportsController.cs @@ -94,5 +94,27 @@ public async Task>> GetArtistStatisticsRepor // Convert to a list and return the results return results.ToList(); } + + /// + /// Generate the monthly spending report + /// + /// + /// + /// + [HttpGet] + [Route("spend/{wishlist}")] + public async Task>> GetMonthlySpendingReportAsync(bool wishlist) + { + // Get the report content + var results = await _factory.MonthlySpend.GenerateReportAsync(wishlist, 1, int.MaxValue); + + if (!results.Any()) + { + return NoContent(); + } + + // Convert to a list and return the results + return results.ToList(); + } } } diff --git a/src/MusicCatalogue.Api/MusicCatalogue.Api.csproj b/src/MusicCatalogue.Api/MusicCatalogue.Api.csproj index 7259367..844cbd0 100644 --- a/src/MusicCatalogue.Api/MusicCatalogue.Api.csproj +++ b/src/MusicCatalogue.Api/MusicCatalogue.Api.csproj @@ -2,9 +2,9 @@ net7.0 - 1.15.0.0 - 1.15.0.0 - 1.15.0 + 1.16.0.0 + 1.16.0.0 + 1.16.0 enable enable diff --git a/src/MusicCatalogue.Data/Migrations/20231115182723_MonthlySpendReport.Designer.cs b/src/MusicCatalogue.Data/Migrations/20231115182723_MonthlySpendReport.Designer.cs new file mode 100644 index 0000000..a6c74d4 --- /dev/null +++ b/src/MusicCatalogue.Data/Migrations/20231115182723_MonthlySpendReport.Designer.cs @@ -0,0 +1,316 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MusicCatalogue.Data; + +#nullable disable + +namespace MusicCatalogue.Data.Migrations +{ + [DbContext(typeof(MusicCatalogueDbContext))] + [Migration("20231115182723_MonthlySpendReport")] + partial class MonthlySpendReport + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.11"); + + modelBuilder.Entity("MusicCatalogue.Entities.Database.Album", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("Id"); + + b.Property("ArtistId") + .HasColumnType("INTEGER") + .HasColumnName("ArtistId"); + + b.Property("CoverUrl") + .HasColumnType("TEXT") + .HasColumnName("CoverUrl"); + + b.Property("GenreId") + .HasColumnType("INTEGER") + .HasColumnName("GenreId"); + + b.Property("IsWishListItem") + .HasColumnType("INTEGER"); + + b.Property("Price") + .HasColumnType("TEXT") + .HasColumnName("Price"); + + b.Property("Purchased") + .HasColumnType("TEXT") + .HasColumnName("Purchased"); + + b.Property("Released") + .HasColumnType("INTEGER") + .HasColumnName("Released"); + + b.Property("RetailerId") + .HasColumnType("INTEGER") + .HasColumnName("RetailerId"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("Title"); + + b.HasKey("Id"); + + b.HasIndex("ArtistId"); + + b.HasIndex("GenreId"); + + b.HasIndex("RetailerId"); + + b.ToTable("ALBUMS", (string)null); + }); + + modelBuilder.Entity("MusicCatalogue.Entities.Database.Artist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("Id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("Name"); + + b.Property("SearchableName") + .HasColumnType("TEXT") + .HasColumnName("SearchableName"); + + b.HasKey("Id"); + + b.ToTable("ARTISTS", (string)null); + }); + + modelBuilder.Entity("MusicCatalogue.Entities.Database.Genre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("Id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("Name"); + + b.HasKey("Id"); + + b.ToTable("GENRES", (string)null); + }); + + modelBuilder.Entity("MusicCatalogue.Entities.Database.JobStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("End") + .HasColumnType("DATETIME") + .HasColumnName("end"); + + b.Property("Error") + .HasColumnType("TEXT") + .HasColumnName("error"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("name"); + + b.Property("Parameters") + .HasColumnType("TEXT") + .HasColumnName("parameters"); + + b.Property("Start") + .HasColumnType("DATETIME") + .HasColumnName("start"); + + b.HasKey("Id"); + + b.ToTable("JOB_STATUS", (string)null); + }); + + modelBuilder.Entity("MusicCatalogue.Entities.Database.Retailer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("Id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("Name"); + + b.HasKey("Id"); + + b.ToTable("RETAILERS", (string)null); + }); + + modelBuilder.Entity("MusicCatalogue.Entities.Database.Track", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("Id"); + + b.Property("AlbumId") + .HasColumnType("INTEGER") + .HasColumnName("AlbumId"); + + b.Property("Duration") + .HasColumnType("INTEGER") + .HasColumnName("Duration"); + + b.Property("Number") + .HasColumnType("INTEGER") + .HasColumnName("Number"); + + b.Property("Purchased") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("Title"); + + b.HasKey("Id"); + + b.HasIndex("AlbumId"); + + b.ToTable("TRACKS", (string)null); + }); + + modelBuilder.Entity("MusicCatalogue.Entities.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasColumnName("id"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("Password"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT") + .HasColumnName("UserName"); + + b.HasKey("Id"); + + b.ToTable("USER", (string)null); + }); + + modelBuilder.Entity("MusicCatalogue.Entities.Reporting.ArtistStatistics", b => + { + b.Property("Albums") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Spend") + .HasColumnType("TEXT"); + + b.Property("Tracks") + .HasColumnType("INTEGER"); + + b.ToTable("ArtistStatistics"); + }); + + modelBuilder.Entity("MusicCatalogue.Entities.Reporting.GenreStatistics", b => + { + b.Property("Albums") + .HasColumnType("INTEGER"); + + b.Property("Artists") + .HasColumnType("INTEGER"); + + b.Property("Genre") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Spend") + .HasColumnType("TEXT"); + + b.Property("Tracks") + .HasColumnType("INTEGER"); + + b.ToTable("GenreStatistics"); + }); + + modelBuilder.Entity("MusicCatalogue.Entities.Reporting.MonthlySpend", b => + { + b.Property("Month") + .HasColumnType("INTEGER"); + + b.Property("Spend") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.ToTable("MonthlySpend"); + }); + + modelBuilder.Entity("MusicCatalogue.Entities.Database.Album", b => + { + b.HasOne("MusicCatalogue.Entities.Database.Artist", null) + .WithMany("Albums") + .HasForeignKey("ArtistId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MusicCatalogue.Entities.Database.Genre", "Genre") + .WithMany() + .HasForeignKey("GenreId"); + + b.HasOne("MusicCatalogue.Entities.Database.Retailer", "Retailer") + .WithMany() + .HasForeignKey("RetailerId"); + + b.Navigation("Genre"); + + b.Navigation("Retailer"); + }); + + modelBuilder.Entity("MusicCatalogue.Entities.Database.Track", b => + { + b.HasOne("MusicCatalogue.Entities.Database.Album", null) + .WithMany("Tracks") + .HasForeignKey("AlbumId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("MusicCatalogue.Entities.Database.Album", b => + { + b.Navigation("Tracks"); + }); + + modelBuilder.Entity("MusicCatalogue.Entities.Database.Artist", b => + { + b.Navigation("Albums"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/MusicCatalogue.Data/Migrations/20231115182723_MonthlySpendReport.cs b/src/MusicCatalogue.Data/Migrations/20231115182723_MonthlySpendReport.cs new file mode 100644 index 0000000..0457e69 --- /dev/null +++ b/src/MusicCatalogue.Data/Migrations/20231115182723_MonthlySpendReport.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System.Diagnostics.CodeAnalysis; + +#nullable disable + +namespace MusicCatalogue.Data.Migrations +{ + [ExcludeFromCodeCoverage] + /// + public partial class MonthlySpendReport : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "MonthlySpend", + columns: table => new + { + Year = table.Column(type: "INTEGER", nullable: true), + Month = table.Column(type: "INTEGER", nullable: true), + Spend = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "MonthlySpend"); + } + } +} diff --git a/src/MusicCatalogue.Data/Migrations/MusicCatalogueDbContextModelSnapshot.cs b/src/MusicCatalogue.Data/Migrations/MusicCatalogueDbContextModelSnapshot.cs index 3cef278..cbeab9b 100644 --- a/src/MusicCatalogue.Data/Migrations/MusicCatalogueDbContextModelSnapshot.cs +++ b/src/MusicCatalogue.Data/Migrations/MusicCatalogueDbContextModelSnapshot.cs @@ -256,6 +256,20 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("GenreStatistics"); }); + modelBuilder.Entity("MusicCatalogue.Entities.Reporting.MonthlySpend", b => + { + b.Property("Month") + .HasColumnType("INTEGER"); + + b.Property("Spend") + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.ToTable("MonthlySpend"); + }); + modelBuilder.Entity("MusicCatalogue.Entities.Database.Album", b => { b.HasOne("MusicCatalogue.Entities.Database.Artist", null) @@ -265,7 +279,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); b.HasOne("MusicCatalogue.Entities.Database.Genre", "Genre") - .WithMany("Albums") + .WithMany() .HasForeignKey("GenreId"); b.HasOne("MusicCatalogue.Entities.Database.Retailer", "Retailer") @@ -295,11 +309,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Navigation("Albums"); }); - - modelBuilder.Entity("MusicCatalogue.Entities.Database.Genre", b => - { - b.Navigation("Albums"); - }); #pragma warning restore 612, 618 } } diff --git a/src/MusicCatalogue.Data/MusicCatalogue.Data.csproj b/src/MusicCatalogue.Data/MusicCatalogue.Data.csproj index 15b0d6c..0c457cc 100644 --- a/src/MusicCatalogue.Data/MusicCatalogue.Data.csproj +++ b/src/MusicCatalogue.Data/MusicCatalogue.Data.csproj @@ -5,7 +5,7 @@ enable enable MusicCatalogue.Data - 1.14.0.0 + 1.15.0.0 Dave Walker Copyright (c) Dave Walker 2023 Dave Walker @@ -17,7 +17,7 @@ https://github.com/davewalker5/MusicCatalogue MIT false - 1.14.0.0 + 1.15.0.0 diff --git a/src/MusicCatalogue.Data/MusicCatalogueDbContext.cs b/src/MusicCatalogue.Data/MusicCatalogueDbContext.cs index a9628e6..56f22ef 100644 --- a/src/MusicCatalogue.Data/MusicCatalogueDbContext.cs +++ b/src/MusicCatalogue.Data/MusicCatalogueDbContext.cs @@ -17,6 +17,7 @@ public class MusicCatalogueDbContext : DbContext public virtual DbSet JobStatuses { get; set; } public virtual DbSet GenreStatistics { get; set; } public virtual DbSet ArtistStatistics { get; set; } + public virtual DbSet MonthlySpend { get; set; } public MusicCatalogueDbContext(DbContextOptions options) : base(options) { diff --git a/src/MusicCatalogue.Entities/Interfaces/IMusicCatalogueFactory.cs b/src/MusicCatalogue.Entities/Interfaces/IMusicCatalogueFactory.cs index 76269bd..52ce966 100644 --- a/src/MusicCatalogue.Entities/Interfaces/IMusicCatalogueFactory.cs +++ b/src/MusicCatalogue.Entities/Interfaces/IMusicCatalogueFactory.cs @@ -18,5 +18,6 @@ public interface IMusicCatalogueFactory IJobStatusManager JobStatuses { get; } IWishListBasedReport GenreStatistics { get; } IWishListBasedReport ArtistStatistics { get; } + IWishListBasedReport MonthlySpend { get; } } } \ No newline at end of file diff --git a/src/MusicCatalogue.Entities/MusicCatalogue.Entities.csproj b/src/MusicCatalogue.Entities/MusicCatalogue.Entities.csproj index b153947..8f009c8 100644 --- a/src/MusicCatalogue.Entities/MusicCatalogue.Entities.csproj +++ b/src/MusicCatalogue.Entities/MusicCatalogue.Entities.csproj @@ -5,7 +5,7 @@ enable enable MusicCatalogue.Entities - 1.14.0.0 + 1.15.0.0 Dave Walker Copyright (c) Dave Walker 2023 Dave Walker @@ -17,7 +17,7 @@ https://github.com/davewalker5/MusicCatalogue MIT false - 1.14.0.0 + 1.15.0.0 diff --git a/src/MusicCatalogue.Entities/Reporting/MonthlySpend.cs b/src/MusicCatalogue.Entities/Reporting/MonthlySpend.cs new file mode 100644 index 0000000..a615860 --- /dev/null +++ b/src/MusicCatalogue.Entities/Reporting/MonthlySpend.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; +using MusicCatalogue.Entities.Attributes; +using System.Diagnostics.CodeAnalysis; + +namespace MusicCatalogue.Entities.Reporting +{ + [Keyless] + [ExcludeFromCodeCoverage] + public class MonthlySpend + { + [Export("Year", 1)] + public int? Year { get; set; } + + [Export("Month", 2)] + public int? Month { get; set; } + + [Export("Spend", 2)] + public decimal Spend { get; set; } + } +} diff --git a/src/MusicCatalogue.Logic/Factory/MusicCatalogueFactory.cs b/src/MusicCatalogue.Logic/Factory/MusicCatalogueFactory.cs index 0d7d50f..9ef1c90 100644 --- a/src/MusicCatalogue.Logic/Factory/MusicCatalogueFactory.cs +++ b/src/MusicCatalogue.Logic/Factory/MusicCatalogueFactory.cs @@ -23,6 +23,7 @@ public class MusicCatalogueFactory : IMusicCatalogueFactory private readonly Lazy _jobStatuses; private readonly Lazy> _genreStatistics; private readonly Lazy> _artistStatistics; + private readonly Lazy> _monthlySpend; public DbContext Context { get; private set; } public IGenreManager Genres { get { return _genres.Value; } } @@ -42,6 +43,9 @@ public class MusicCatalogueFactory : IMusicCatalogueFactory [ExcludeFromCodeCoverage] public IWishListBasedReport ArtistStatistics { get { return _artistStatistics.Value; } } + [ExcludeFromCodeCoverage] + public IWishListBasedReport MonthlySpend { get { return _monthlySpend.Value; } } + public MusicCatalogueFactory(MusicCatalogueDbContext context) { Context = context; @@ -57,6 +61,7 @@ public MusicCatalogueFactory(MusicCatalogueDbContext context) _xlsxExporter = new Lazy(() => new XlsxExporter(this)); _genreStatistics = new Lazy>(() => new WishListBasedReport(context)); _artistStatistics = new Lazy>(() => new WishListBasedReport(context)); + _monthlySpend = new Lazy>(() => new WishListBasedReport(context)); } } } \ No newline at end of file diff --git a/src/MusicCatalogue.Logic/MusicCatalogue.Logic.csproj b/src/MusicCatalogue.Logic/MusicCatalogue.Logic.csproj index 96ab279..4f330a9 100644 --- a/src/MusicCatalogue.Logic/MusicCatalogue.Logic.csproj +++ b/src/MusicCatalogue.Logic/MusicCatalogue.Logic.csproj @@ -5,7 +5,7 @@ enable enable MusicCatalogue.Logic - 1.14.0.0 + 1.15.0.0 Dave Walker Copyright (c) Dave Walker 2023 Dave Walker @@ -17,17 +17,19 @@ https://github.com/davewalker5/MusicCatalogue MIT false - 1.14.0.0 + 1.15.0.0 + + diff --git a/src/MusicCatalogue.Logic/Sql/MonthlySpend.sql b/src/MusicCatalogue.Logic/Sql/MonthlySpend.sql new file mode 100644 index 0000000..fd4297a --- /dev/null +++ b/src/MusicCatalogue.Logic/Sql/MonthlySpend.sql @@ -0,0 +1,8 @@ +SELECT STRFTIME( '%Y', al.Purchased ) AS "Year", + STRFTIME( '%m', al.Purchased ) AS "Month", + SUM( al.Price ) AS "Spend" +FROM ALBUMS al +WHERE IFNULL( al.IsWishListItem, 0 ) = $wishlist +AND al.Price IS NOT NULL +GROUP BY STRFTIME( '%Y', al.Purchased ), + STRFTIME( '%m', al.Purchased ); diff --git a/src/MusicCatalogue.LookupTool/MusicCatalogue.LookupTool.csproj b/src/MusicCatalogue.LookupTool/MusicCatalogue.LookupTool.csproj index 8b24100..bad4846 100644 --- a/src/MusicCatalogue.LookupTool/MusicCatalogue.LookupTool.csproj +++ b/src/MusicCatalogue.LookupTool/MusicCatalogue.LookupTool.csproj @@ -3,9 +3,9 @@ Exe net7.0 - 1.14.0.0 - 1.14.0.0 - 1.14.0 + 1.15.0.0 + 1.15.0.0 + 1.15.0 enable enable false diff --git a/src/music-catalogue-ui/components/componentPicker.js b/src/music-catalogue-ui/components/componentPicker.js index 45ec5ce..d9b6a74 100644 --- a/src/music-catalogue-ui/components/componentPicker.js +++ b/src/music-catalogue-ui/components/componentPicker.js @@ -8,6 +8,7 @@ import GenreStatusReport from "./genreStatisticsReport"; import JobStatusReport from "./jobStatusReport"; import AlbumPurchaseDetails from "./albumPurchaseDetails"; import ArtistStatisticsReport from "./artistStatisticsReport"; +import MonthlySpendReport from "./monthlySpendReport"; /** * Component using the current page name to render the components required @@ -57,6 +58,8 @@ const ComponentPicker = ({ context, navigate, logout }) => { return ; case pages.jobStatusReport: return ; + case pages.monthlySpendReport: + return ; case pages.albumPurchaseDetails: return ( { > Job Status + + navigate(pages.monthlySpendReport, null, null, false) + } + > + Monthly Spend + navigate(pages.export, null, null, false)}>Export diff --git a/src/music-catalogue-ui/components/monthlySpendReport.js b/src/music-catalogue-ui/components/monthlySpendReport.js new file mode 100644 index 0000000..2796e46 --- /dev/null +++ b/src/music-catalogue-ui/components/monthlySpendReport.js @@ -0,0 +1,37 @@ +import useMonthlySpend from "@/hooks/useMonthlySpend"; +import MonthlySpendReportRow from "./monthlySpendReportRow"; + +/** + * Component to display the monthly spending report page and its results + * @param {*} logout + * @returns + */ +const MonthlySpendReport = ({ logout }) => { + const { records, setRecords } = useMonthlySpend(logout); + + return ( + <> +
+
Monthly Spending Report
+
+ + + + + + + + + {records != null && ( + + {records.map((r) => ( + + ))} + + )} +
YearMonthTotal Spend
+ + ); +}; + +export default MonthlySpendReport; diff --git a/src/music-catalogue-ui/components/monthlySpendReportRow.js b/src/music-catalogue-ui/components/monthlySpendReportRow.js new file mode 100644 index 0000000..3b606f8 --- /dev/null +++ b/src/music-catalogue-ui/components/monthlySpendReportRow.js @@ -0,0 +1,20 @@ +import CurrencyFormatter from "./currencyFormatter"; + +/** + * Component to render a row containing the details for a single monthly spending record + * @param {*} record + * @returns + */ +const MonthlySpendReportRow = ({ record }) => { + return ( + + {record.year} + {record.month} + + + + + ); +}; + +export default MonthlySpendReportRow; diff --git a/src/music-catalogue-ui/helpers/apiReports.js b/src/music-catalogue-ui/helpers/apiReports.js index 1701b38..3e9c14e 100644 --- a/src/music-catalogue-ui/helpers/apiReports.js +++ b/src/music-catalogue-ui/helpers/apiReports.js @@ -86,8 +86,28 @@ const apiArtistStatisticsReport = async (wishlist, logout) => { return records; }; +/** + * Call the API to retrieve the monthly spending report + * @param {*} logout + * @returns + */ +const apiMonthlySpendReport = async (logout) => { + // Construct the route + const url = `${config.api.baseUrl}/reports/spend/false`; + + // Call the API to get content for the report + const response = await fetch(url, { + method: "GET", + headers: apiGetHeaders(), + }); + + const records = await apiReadResponseData(response, logout); + return records; +}; + export { apiJobStatusReport, apiGenreStatisticsReport, apiArtistStatisticsReport, + apiMonthlySpendReport, }; diff --git a/src/music-catalogue-ui/helpers/navigation.js b/src/music-catalogue-ui/helpers/navigation.js index e2dae51..9dfc460 100644 --- a/src/music-catalogue-ui/helpers/navigation.js +++ b/src/music-catalogue-ui/helpers/navigation.js @@ -9,6 +9,7 @@ const pages = { artistStatisticsReport: "ArtistStatisticsReport", genreStatisticsReport: "GenreStatisticsReport", jobStatusReport: "JobStatusReport", + monthlySpendReport: "MonthlySpendReport", albumPurchaseDetails: "AlbumPurchaseDetails", }; diff --git a/src/music-catalogue-ui/hooks/useMonthlySpend.js b/src/music-catalogue-ui/hooks/useMonthlySpend.js new file mode 100644 index 0000000..72f6b25 --- /dev/null +++ b/src/music-catalogue-ui/hooks/useMonthlySpend.js @@ -0,0 +1,29 @@ +import { useState, useEffect } from "react"; +import { apiMonthlySpendReport } from "@/helpers/apiReports"; + +/** + * Hook that uses the API helpers to retrieve a list of artists from the + * Music Catalogue REST API + * @param {*} logout + * @returns + */ +const useMonthlySpend = (logout) => { + // Current list of artists and the method to change it + const [records, setRecords] = useState([]); + + useEffect(() => { + const fetchReport = async () => { + try { + // Get a list of records via the service, store it in state + var fetchedRecords = await apiMonthlySpendReport(logout); + setRecords(fetchedRecords); + } catch {} + }; + + fetchReport(); + }, [logout]); + + return { records, setRecords }; +}; + +export default useMonthlySpend;