mirror of
https://github.com/apricote/Listory.git
synced 2026-01-13 21:21:02 +00:00
refactor: use TimeOptions for Listens report
This commit is contained in:
parent
5bc76f23d0
commit
67ea28aec7
7 changed files with 51 additions and 59 deletions
|
|
@ -79,13 +79,17 @@ export const getRecentListens = async (
|
||||||
export const getListensReport = async (
|
export const getListensReport = async (
|
||||||
options: ListenReportOptions
|
options: ListenReportOptions
|
||||||
): Promise<ListenReportItem[]> => {
|
): Promise<ListenReportItem[]> => {
|
||||||
const { timeFrame, timeStart, timeEnd } = options;
|
const {
|
||||||
|
timeFrame,
|
||||||
|
time: { timePreset, customTimeStart, customTimeEnd },
|
||||||
|
} = options;
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`/api/v1/reports/listens?${qs({
|
`/api/v1/reports/listens?${qs({
|
||||||
timeFrame,
|
timeFrame,
|
||||||
timeStart: formatISO(timeStart),
|
timePreset,
|
||||||
timeEnd: formatISO(timeEnd),
|
customTimeStart: formatISO(customTimeStart),
|
||||||
|
customTimeEnd: formatISO(customTimeEnd),
|
||||||
})}`,
|
})}`,
|
||||||
{
|
{
|
||||||
headers: getDefaultHeaders(),
|
headers: getDefaultHeaders(),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
|
import { TimeOptions } from "./time-options";
|
||||||
|
|
||||||
export interface ListenReportOptions {
|
export interface ListenReportOptions {
|
||||||
timeFrame: "day" | "week" | "month" | "year";
|
timeFrame: "day" | "week" | "month" | "year";
|
||||||
timeStart: Date;
|
time: TimeOptions;
|
||||||
timeEnd: Date;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { format, getTime } from "date-fns";
|
import { format, getTime } from "date-fns";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { Redirect } from "react-router-dom";
|
import { Redirect } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
Area,
|
Area,
|
||||||
|
|
@ -14,21 +14,29 @@ import {
|
||||||
import { getListensReport } from "../api/api";
|
import { getListensReport } from "../api/api";
|
||||||
import { ListenReportItem } from "../api/entities/listen-report-item";
|
import { ListenReportItem } from "../api/entities/listen-report-item";
|
||||||
import { ListenReportOptions } from "../api/entities/listen-report-options";
|
import { ListenReportOptions } from "../api/entities/listen-report-options";
|
||||||
|
import { TimeOptions } from "../api/entities/time-options";
|
||||||
|
import { TimePreset } from "../api/entities/time-preset.enum";
|
||||||
import { useAsync } from "../hooks/use-async";
|
import { useAsync } from "../hooks/use-async";
|
||||||
import { useAuth } from "../hooks/use-auth";
|
import { useAuth } from "../hooks/use-auth";
|
||||||
|
import { ReportTimeOptions } from "./ReportTimeOptions";
|
||||||
|
|
||||||
export const ReportListens: React.FC = () => {
|
export const ReportListens: React.FC = () => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
const [reportOptions, setReportOptions] = useState<ListenReportOptions>({
|
const [timeFrame, setTimeFrame] = useState<"day" | "week" | "month" | "year">(
|
||||||
timeFrame: "day",
|
"day"
|
||||||
timeStart: new Date("2020-05-01"),
|
);
|
||||||
timeEnd: new Date(),
|
|
||||||
|
const [timeOptions, setTimeOptions] = useState<TimeOptions>({
|
||||||
|
timePreset: TimePreset.LAST_7_DAYS,
|
||||||
|
customTimeStart: new Date(0),
|
||||||
|
customTimeEnd: new Date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchData = useMemo(() => () => getListensReport(reportOptions), [
|
const fetchData = useMemo(
|
||||||
reportOptions,
|
() => () => getListensReport({ timeFrame, time: timeOptions }),
|
||||||
]);
|
[timeFrame, timeOptions]
|
||||||
|
);
|
||||||
|
|
||||||
const { value: report, pending: isLoading } = useAsync(fetchData, []);
|
const { value: report, pending: isLoading } = useAsync(fetchData, []);
|
||||||
|
|
||||||
|
|
@ -51,14 +59,9 @@ export const ReportListens: React.FC = () => {
|
||||||
<select
|
<select
|
||||||
className="block appearance-none min-w-full md:win-w-0 md:w-1/4 bg-white border border-gray-400 hover:border-gray-500 p-2 rounded shadow leading-tight focus:outline-none focus:shadow-outline"
|
className="block appearance-none min-w-full md:win-w-0 md:w-1/4 bg-white border border-gray-400 hover:border-gray-500 p-2 rounded shadow leading-tight focus:outline-none focus:shadow-outline"
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setReportOptions({
|
setTimeFrame(
|
||||||
...reportOptions,
|
e.target.value as "day" | "week" | "month" | "year"
|
||||||
timeFrame: e.target.value as
|
)
|
||||||
| "day"
|
|
||||||
| "week"
|
|
||||||
| "month"
|
|
||||||
| "year",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<option value="day">Daily</option>
|
<option value="day">Daily</option>
|
||||||
|
|
@ -67,6 +70,10 @@ export const ReportListens: React.FC = () => {
|
||||||
<option value="year">Yearly</option>
|
<option value="year">Yearly</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<ReportTimeOptions
|
||||||
|
timeOptions={timeOptions}
|
||||||
|
setTimeOptions={setTimeOptions}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -80,7 +87,7 @@ export const ReportListens: React.FC = () => {
|
||||||
)}
|
)}
|
||||||
{reportHasItems && (
|
{reportHasItems && (
|
||||||
<div className="w-full text-gray-700 mt-5">
|
<div className="w-full text-gray-700 mt-5">
|
||||||
<ReportGraph options={reportOptions} data={report} />
|
<ReportGraph timeFrame={timeFrame} data={report} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -90,9 +97,9 @@ export const ReportListens: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const ReportGraph: React.FC<{
|
const ReportGraph: React.FC<{
|
||||||
options: ListenReportOptions;
|
timeFrame: ListenReportOptions["timeFrame"];
|
||||||
data: ListenReportItem[];
|
data: ListenReportItem[];
|
||||||
}> = ({ options, data }) => {
|
}> = ({ timeFrame, data }) => {
|
||||||
const dataLocal = data.map(({ date, ...other }) => ({
|
const dataLocal = data.map(({ date, ...other }) => ({
|
||||||
...other,
|
...other,
|
||||||
date: getTime(date),
|
date: getTime(date),
|
||||||
|
|
@ -108,10 +115,7 @@ const ReportGraph: React.FC<{
|
||||||
}
|
}
|
||||||
|
|
||||||
const [{ value: listens }] = payload;
|
const [{ value: listens }] = payload;
|
||||||
const date = format(
|
const date = format(label as number, dateFormatFromTimeFrame(timeFrame));
|
||||||
label as number,
|
|
||||||
dateFormatFromTimeFrame(options.timeFrame)
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-gray-100 shadow-xl p-2 rounded text-sm font-light">
|
<div className="bg-gray-100 shadow-xl p-2 rounded text-sm font-light">
|
||||||
|
|
@ -145,7 +149,7 @@ const ReportGraph: React.FC<{
|
||||||
domain={["auto", "auto"]}
|
domain={["auto", "auto"]}
|
||||||
dataKey="date"
|
dataKey="date"
|
||||||
tickFormatter={(date) =>
|
tickFormatter={(date) =>
|
||||||
format(date, shortDateFormatFromTimeFrame(options.timeFrame))
|
format(date, shortDateFormatFromTimeFrame(timeFrame))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<YAxis />
|
<YAxis />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { IsEnum, IsISO8601 } from "class-validator";
|
import { IsEnum, ValidateNested } from "class-validator";
|
||||||
import { User } from "../../users/user.entity";
|
import { User } from "../../users/user.entity";
|
||||||
import { Timeframe } from "../timeframe.enum";
|
import { Timeframe } from "../timeframe.enum";
|
||||||
|
import { ReportTimeDto } from "./report-time.dto";
|
||||||
|
|
||||||
export class GetListenReportDto {
|
export class GetListenReportDto {
|
||||||
user: User;
|
user: User;
|
||||||
|
|
@ -8,9 +9,6 @@ export class GetListenReportDto {
|
||||||
@IsEnum(Timeframe)
|
@IsEnum(Timeframe)
|
||||||
timeFrame: Timeframe;
|
timeFrame: Timeframe;
|
||||||
|
|
||||||
@IsISO8601()
|
@ValidateNested()
|
||||||
timeStart: string;
|
time: ReportTimeDto;
|
||||||
|
|
||||||
@IsISO8601()
|
|
||||||
timeEnd: string;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,4 @@ export class ListenReportDto {
|
||||||
date: string;
|
date: string;
|
||||||
count: number;
|
count: number;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
timeFrame: Timeframe;
|
|
||||||
|
|
||||||
timeStart: string;
|
|
||||||
|
|
||||||
timeEnd: string;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ import { Controller, Get, Query } from "@nestjs/common";
|
||||||
import { Auth } from "src/auth/decorators/auth.decorator";
|
import { Auth } from "src/auth/decorators/auth.decorator";
|
||||||
import { ReqUser } from "../auth/decorators/req-user.decorator";
|
import { ReqUser } from "../auth/decorators/req-user.decorator";
|
||||||
import { User } from "../users/user.entity";
|
import { User } from "../users/user.entity";
|
||||||
import { GetListenReportDto } from "./dto/get-listen-report.dto";
|
|
||||||
import { GetTopArtistsReportDto } from "./dto/get-top-artists-report.dto";
|
|
||||||
import { ListenReportDto } from "./dto/listen-report.dto";
|
import { ListenReportDto } from "./dto/listen-report.dto";
|
||||||
|
import { ReportTimeDto } from "./dto/report-time.dto";
|
||||||
import { TopArtistsReportDto } from "./dto/top-artists-report.dto";
|
import { TopArtistsReportDto } from "./dto/top-artists-report.dto";
|
||||||
import { ReportsService } from "./reports.service";
|
import { ReportsService } from "./reports.service";
|
||||||
|
import { Timeframe } from "./timeframe.enum";
|
||||||
|
|
||||||
@Controller("api/v1/reports")
|
@Controller("api/v1/reports")
|
||||||
export class ReportsController {
|
export class ReportsController {
|
||||||
|
|
@ -15,10 +15,11 @@ export class ReportsController {
|
||||||
@Get("listens")
|
@Get("listens")
|
||||||
@Auth()
|
@Auth()
|
||||||
async getListens(
|
async getListens(
|
||||||
@Query() options: GetListenReportDto,
|
@Query() time: ReportTimeDto,
|
||||||
|
@Query("timeFrame") timeFrame: Timeframe,
|
||||||
@ReqUser() user: User
|
@ReqUser() user: User
|
||||||
): Promise<ListenReportDto> {
|
): Promise<ListenReportDto> {
|
||||||
return this.reportsService.getListens({ ...options, user });
|
return this.reportsService.getListens({ user, timeFrame, time });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("top-artists")
|
@Get("top-artists")
|
||||||
|
|
|
||||||
|
|
@ -65,14 +65,9 @@ export class ReportsService {
|
||||||
constructor(private readonly listensService: ListensService) {}
|
constructor(private readonly listensService: ListensService) {}
|
||||||
|
|
||||||
async getListens(options: GetListenReportDto): Promise<ListenReportDto> {
|
async getListens(options: GetListenReportDto): Promise<ListenReportDto> {
|
||||||
const { user, timeFrame, timeStart, timeEnd } = options;
|
const { user, timeFrame, time: timePreset } = options;
|
||||||
|
|
||||||
// Function should eventually be rewritten to accept a timepreset
|
const interval = this.getIntervalFromPreset(timePreset);
|
||||||
const interval = this.getIntervalFromPreset({
|
|
||||||
timePreset: TimePreset.CUSTOM,
|
|
||||||
customTimeStart: timeStart,
|
|
||||||
customTimeEnd: timeEnd,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { items: listens } = await this.listensService.getListens({
|
const { items: listens } = await this.listensService.getListens({
|
||||||
user,
|
user,
|
||||||
|
|
@ -81,20 +76,15 @@ export class ReportsService {
|
||||||
limit: PAGINATION_LIMIT_UNLIMITED,
|
limit: PAGINATION_LIMIT_UNLIMITED,
|
||||||
});
|
});
|
||||||
|
|
||||||
const reportInterval: Interval = {
|
|
||||||
start: parseISO(timeStart),
|
|
||||||
end: parseISO(timeEnd),
|
|
||||||
};
|
|
||||||
|
|
||||||
const { eachOfInterval, isSame } = timeframeToDateFns[timeFrame];
|
const { eachOfInterval, isSame } = timeframeToDateFns[timeFrame];
|
||||||
|
|
||||||
const reportItems = eachOfInterval(reportInterval).map((date) => {
|
const reportItems = eachOfInterval(interval).map((date) => {
|
||||||
const count = listens.filter((listen) => isSame(date, listen.playedAt))
|
const count = listens.filter((listen) => isSame(date, listen.playedAt))
|
||||||
.length;
|
.length;
|
||||||
return { date: formatISO(date), count };
|
return { date: formatISO(date), count };
|
||||||
});
|
});
|
||||||
|
|
||||||
return { items: reportItems, timeStart, timeEnd, timeFrame };
|
return { items: reportItems };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTopArtists(
|
async getTopArtists(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue