Skip to content
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
40 changes: 40 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@mui/material": "^6.1.1",
"@mui/styled-engine-sc": "^6.1.1",
"axios": "^1.7.9",
"framer-motion": "^11.15.0",
"hugeicons-react": "^0.3.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down
4 changes: 4 additions & 0 deletions src/assets/data/revenueData.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"totalRevenue": 100000,
"comparedToLastMonth": "+ 4.12 %"
}
5 changes: 5 additions & 0 deletions src/assets/data/userData.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"totalUsers": 10000,
"activeUsers": 5000,
"unsubscribedUsers": 500
}
2 changes: 1 addition & 1 deletion src/components/uiMainContentTitle/uiMainContentTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const UiMainContentTitle: FunctionComponent<UiMainContentTitleProps> = (

return (
<Stack className={ styles["uiMainContentTitleContainer"] } spacing={ 0.5 }>
<Typography typography="h5">{ title }</Typography>
<Typography typography="h5" fontSize="24px" letterSpacing="0px">{ title }</Typography>
<Typography typography="subtitle1">{ subTitle }</Typography>
</Stack>
);
Expand Down
74 changes: 74 additions & 0 deletions src/features/overview/components/TopArtists.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import CircularProgress from "@mui/material/CircularProgress";
import { SubSectionCard } from "../../../pages/app/components/SubSectionCard";
import { SubSectionLayout } from "../../../pages/app/components/SubSectionLayout";
import { SubSectionListCard } from "../../../pages/app/components/SubSectionListCard";
import useBillboardData from "../../../states/billboardData/hooks/useBillboardData";
import { Artist } from "../../../states/billboardData/models/Artist";

/**
* Component to display the top artists section
*/
export default function TopArtists() {
const {
isTopArtistsError,
isTopArtistsLoading,
rankingDate,
topArtists
} = useBillboardData();

// Show loading state
if (isTopArtistsLoading) {
return <CircularProgress />;
}

// Show error state
if (isTopArtistsError ) {
return <div>Error loading top artists data</div>;
}

// Ensure we have enough artists before rendering
const hasEnoughArtists = topArtists?.length >= 5;

if (!hasEnoughArtists) {
return <div>Insufficient artist data</div>;
}

return (
<SubSectionLayout
title="Top Artists"
subtitle={ rankingDate && `Top artists as of ${rankingDate}` }
displayItems={ [
<SubSectionCard
key="top-artist"
title="Billboard Top Artist"
content= { topArtists[0]?.name || "N/A" }
imageUrl={ topArtists[0]?.image || "" }
/>,
<SubSectionListCard
key="artist-list"
contentList={
topArtists
.slice(1, 5)
.map((artist: Artist, index: number) => ({
imageUrl: artist?.image || "",
number: `${index + 2}`,
title: artist?.name || "N/A"
}))
}
/>,
<SubSectionListCard
key="artist-list"
contentList={
topArtists
.slice(6, 10)
.map((artist: Artist, index: number) => ({
imageUrl: artist?.image || "",
number: `${index + 6}`,
title: artist?.name || "N/A"
}))
}
/>
] }
/>
);
}
81 changes: 81 additions & 0 deletions src/features/overview/components/TopSongs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import CircularProgress from "@mui/material/CircularProgress";
import { Fragment } from "react/jsx-runtime";
import { SubSectionCard } from "../../../pages/app/components/SubSectionCard";
import { SubSectionLayout } from "../../../pages/app/components/SubSectionLayout";
import { SubSectionListCard } from "../../../pages/app/components/SubSectionListCard";
import useBillboardData from "../../../states/billboardData/hooks/useBillboardData";
import { Song } from "../../../states/billboardData/models/Song";

/**
* Component to display the top songs section
*/
export default function TopSongs() {
const {
isTopSongsLoading,
isTopSongsError,
topSongs,
songsRankingDate
} = useBillboardData();

// Show loading state
if (isTopSongsLoading) {
return <CircularProgress />;
}

// Show error state
if (isTopSongsError) {
return <div>Error loading top songs data</div>;
}

// Ensure we have enough artists before rendering
const hasEnoughSongs = topSongs?.length >= 5;

if (!hasEnoughSongs) {
return <div>Insufficient songs data</div>;
}

return (
<Fragment>
<SubSectionLayout
title="Top Songs"
subtitle={ songsRankingDate && `Top songs as of ${songsRankingDate}` }
displayItems={ [
<SubSectionCard
key="top-songs"
title="Billboard Top Songs"
content={ topSongs[0]?.name || "N/A" }
imageUrl={ topSongs[0]?.image || "" }
caption={ topSongs[0]?.artist || "N/A" }
/>,
<SubSectionListCard
key="songs-list"
contentList={
topSongs
.slice(1, 5)
.map((song: Song, index: number) => ({
imageUrl: song?.image || "",
number: `${index + 2}`,
subtitle: song?.artist || "N/A",
title: song?.name || "N/A"
}))
}
/>,
<SubSectionListCard
key="songs-list"
contentList={
topSongs
.slice(6, 10)
.map((song: Song, index: number) => ({
imageUrl: song?.image || "",
number: `${index + 6}`,
subtitle: song?.artist || "N/A",
title: song?.name || "N/A"
}))
}
/>

] }
/>
</Fragment>
);
}
45 changes: 45 additions & 0 deletions src/features/overview/components/UsersDataAnalysis.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Fragment } from "react/jsx-runtime";
import { SubSectionCard } from "../../../pages/app/components/SubSectionCard";
import { SubSectionLayout } from "../../../pages/app/components/SubSectionLayout";
import useUsersData from "../../../states/usersData/hooks/useUsersData";

/**
* Component to display the users data analysis section
*/
export default function UsersDataAnalyst() {
const {
isUsersDataError,
usersData
} = useUsersData();

// Show error state
if (isUsersDataError ) {
return <div>Error loading users data</div>;
}

return (
<Fragment>
<SubSectionLayout
title="Users Data Overview"
displayItems={ [
<SubSectionCard
key="total-users"
title="Total Users"
content= { usersData?.totalUsers.toLocaleString() || "N/A" }
/>,
<SubSectionCard
key="active-users"
title="Active Users"
content= { usersData?.activeUsers.toLocaleString() || "N/A" }
/>,
<SubSectionCard
key="unsubscribed-users"
title="Unsubscribed Users"
content= { `- ${usersData?.unsubscribedUsers.toLocaleString()}` || "N/A" }
isError={ true }
/>
] }
/>
</Fragment>
);
}
41 changes: 41 additions & 0 deletions src/features/overview/components/revenueDataAnalysis.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Fragment } from "react/jsx-runtime";
import { SubSectionCard } from "../../../pages/app/components/SubSectionCard";
import { SubSectionLayout } from "../../../pages/app/components/SubSectionLayout";
import useRevenueData from "../../../states/revenueData/hooks/useRevenueData";

/**
* Component to display the revenue data analysis section
*/
export default function RevenueDataAnalysis() {
const {
isRevenueDataError,
revenueData
} = useRevenueData();

// Show error state
if (isRevenueDataError) {
return <div>Error loading revenue data</div>;
}

return (
<Fragment>
<SubSectionLayout
title="Revenue Data Overview"
displayItems={ [
<SubSectionCard
key="total-revenue"
title="Total Revenue"
content= { revenueData?.totalRevenue.toLocaleString() || "N/A" }
/>,
<SubSectionCard
key="compared-to-last-month"
title="Compared to Last Month"
content= { revenueData?.comparedToLastMonth || "N/A" }
isError={ revenueData?.comparedToLastMonth.includes("-") }
isPositive={ revenueData?.comparedToLastMonth.includes("+") }
/>
] }
/>
</Fragment>
);
}
17 changes: 17 additions & 0 deletions src/features/overview/layout/OverviewLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Stack from "@mui/material/Stack";
import RevenueDataAnalysis from "../components/revenueDataAnalysis";
import TopArtists from "../components/TopArtists";
import TopSongs from "../components/TopSongs";
import UsersDataAnalyst from "../components/UsersDataAnalysis";

export default function OverviewLayout() {

return (
<Stack spacing={ 4 }>
<UsersDataAnalyst />
<TopArtists />
<TopSongs />
<RevenueDataAnalysis />
</Stack>
);
}
13 changes: 12 additions & 1 deletion src/features/overview/pages/OverviewPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import BillboardDataProvider from "../../../states/billboardData/providers/billboardDataProvider";
import RevenueDataProvider from "../../../states/revenueData/providers/revenueDataProvider";
import UsersDataProvider from "../../../states/usersData/providers/usersDataProvider";
import OverviewLayout from "../layout/OverviewLayout";

export default function OverviewPage() {
return (
<div>Under Construction</div>
<BillboardDataProvider>
<UsersDataProvider>
<RevenueDataProvider>
<OverviewLayout />
</RevenueDataProvider>
</UsersDataProvider>
</BillboardDataProvider>
);
}
25 changes: 25 additions & 0 deletions src/layouts/AppContentLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Container from "@mui/material/Container";
import { FunctionComponent, ReactElement, ReactNode } from "react";
import styles from "./styles/AppContentLayout.module.css";

interface AppContentLayoutProps {
/**
* Children of the layout
*/
children: ReactNode;
}

export const AppContentLayout: FunctionComponent<AppContentLayoutProps> = (
props: AppContentLayoutProps): ReactElement => {
const {
children
} = props;

return (
<Container
className={ styles.appContentLayout }
>
{ children }
</Container>
);
};
Loading