Skip to content

Commit

Permalink
Merge pull request #7 from bonz88/feat/timeline-chart
Browse files Browse the repository at this point in the history
feat/timeline-chart
  • Loading branch information
bonz88 committed Mar 14, 2024
2 parents c2dbe4f + 082ef7b commit c90c45e
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 19 deletions.
2 changes: 1 addition & 1 deletion src/app/components/CarouselCoins.tsx
Expand Up @@ -47,7 +47,7 @@ export default function CarouselCoins() {
className="w-full"
>
<CarouselContent>
{data.map((coin) => (
{data?.map((coin) => (
<CarouselItem
key={coin.id}
className="sm:basis-1/2 md:basis-1/3 lg:basis-1/4 xl:basis-1/5"
Expand Down
64 changes: 47 additions & 17 deletions src/app/components/CoinChart.tsx
Expand Up @@ -13,6 +13,7 @@ import {
import { AppDispatch, useAppSelector } from "../../redux/store";
import { getCoinDataGraph } from "../../redux/features/selectedCoinSlice";
import formatNumber from "../utils/formatNumber";
import formatDateGraphs from "../utils/formatDateGraph";

type Payload = {
date: string;
Expand All @@ -35,14 +36,19 @@ export default function CoinChart({
const { selectedCoins, loading, hasError } = useAppSelector(
(state) => state.selectedCoin
);
const days = useAppSelector((state) => state.timeline.currentTimeline.days);
const [currentValue, setCurrentValue] = useState("");
const [currentDate, setCurrentDate] = useState("");

useEffect(() => {
dispatch(
getCoinDataGraph({ currency: currencyCode, days: "3", coinId: "bitcoin" })
getCoinDataGraph({
currency: currencyCode,
days: days,
coinId: "bitcoin",
})
);
}, [dispatch, currencyCode]);
}, [dispatch, currencyCode, days]);

useEffect(() => {
if (selectedCoins.length > 0) {
Expand All @@ -53,17 +59,20 @@ export default function CoinChart({
selectedCoins[0].total_volumes.length - 1
];
const lastValue = lastDataPoint[1];
const lastDate = new Date(lastDataPoint[0]).toLocaleDateString();
const lastDate = new Date(lastDataPoint[0]).toLocaleDateString(
undefined,
{
month: "long",
day: "numeric",
year: "numeric",
}
);

setCurrentValue(`${lastValue.toFixed(2)}`);
setCurrentDate(lastDate);
}
}, [selectedCoins, chartType, currencyCode]);

if (loading === "pending") {
return <div>Loading...</div>;
}

if (hasError) {
return <div>Error fetching data</div>;
}
Expand All @@ -73,11 +82,11 @@ export default function CoinChart({
const formattedData = coin
? chartType === "price"
? coin.prices.map(([time, value]) => ({
date: new Date(time).toLocaleDateString(),
date: new Date(time),
value,
}))
: coin.total_volumes.map(([time, volume]) => ({
date: new Date(time).toLocaleDateString(),
date: new Date(time),
volume,
}))
: [];
Expand All @@ -87,13 +96,24 @@ export default function CoinChart({
const { payload } = e.activePayload[0];
const dataKey = chartType === "price" ? "value" : "volume";
const value = payload[dataKey]?.toFixed(2) ?? "0";
const date = new Date(payload.date);
setCurrentValue(value);
setCurrentDate(payload.date);
setCurrentDate(
date.toLocaleDateString(undefined, {
month: "long",
day: "numeric",
year: "numeric",
})
);
}
};

return (
<div className="w-full h-[404px] flex flex-col dark:bg-[#1E1932] bg-white rounded-xl p-6">
<div
className={`w-full h-[404px] flex flex-col ${
chartType === "volume" ? "dark:bg-[#1E1932]" : "dark:bg-[#191932]"
} bg-white rounded-xl p-6`}
>
<div className="flex flex-col">
<span className="text-xl font-normal dark:text-[#D1D1D1] text-[#191932] leading-6">
{chartType === "volume"
Expand All @@ -113,19 +133,24 @@ export default function CoinChart({
{chartType === "volume" ? (
<BarChart data={formattedData} onMouseMove={handleMouseMove}>
<defs>
<linearGradient id="color" x1="0" y1="0" x2="0" y2="1">
<linearGradient id="colorBarChart" x1="0" y1="0" x2="0" y2="1">
<stop offset="1%" stopColor="#B374F2" />
<stop offset="100%" stopColor="#9D62D9" />
</linearGradient>
</defs>
<XAxis dataKey="date" />
<XAxis
dataKey="date"
axisLine={false}
tickLine={false}
tickFormatter={(tick) => formatDateGraphs(tick, days)}
/>
<Tooltip content={<></>} />
<Bar dataKey="volume" stroke="" fill="url(#color)" />
<Bar dataKey="volume" stroke="" fill="url(#colorBarChart)" />
</BarChart>
) : (
<AreaChart data={formattedData} onMouseMove={handleMouseMove}>
<defs>
<linearGradient id="color" x1="0" y1="0" x2="0" y2="1">
<linearGradient id="colorAreaChart" x1="0" y1="0" x2="0" y2="1">
<stop offset="1%" stopColor="#7474F2" stopOpacity={0.6} />
<stop offset="60%" stopColor="#7474F2" stopOpacity={0.1} />
</linearGradient>
Expand All @@ -134,10 +159,15 @@ export default function CoinChart({
dataKey="value"
stroke="#7878FA"
strokeWidth="3"
fill="url(#color)"
fill="url(#colorAreaChart)"
/>

<XAxis dataKey="date" axisLine={false} tickLine={false} />
<XAxis
dataKey="date"
axisLine={false}
tickLine={false}
tickFormatter={(tick) => formatDateGraphs(tick, days)}
/>

<Tooltip content={<></>} />
</AreaChart>
Expand Down
14 changes: 14 additions & 0 deletions src/app/components/Timeline.tsx
@@ -0,0 +1,14 @@
"use client";
import { useAppSelector } from "../../redux/store";
import TimelineDays from "./TimelineDays";
export default function Timeline() {
const timeline = useAppSelector((state) => state.timeline.timeline);

return (
<div className="w-[463px] h-[42px] dark:bg-[#232336] bg-[#CCCCFA] bg-opacity-40 rounded-md flex gap-2 p-1">
{timeline.map((t) => (
<TimelineDays timeline={t} key={t.id} />
))}
</div>
);
}
28 changes: 28 additions & 0 deletions src/app/components/TimelineDays.tsx
@@ -0,0 +1,28 @@
import { useDispatch } from "react-redux";
import { AppDispatch } from "../../redux/store";
import { activeTimeline } from "../../redux/features/timelineSlice";
import { TimelineItem } from "../../redux/features/timelineSlice";

type TimelineDaysProps = {
timeline: TimelineItem;
};

export default function TimelineDays({ timeline }: TimelineDaysProps) {
const dispatch: AppDispatch = useDispatch();

return (
<button
className={`w-14 p-1 ${
timeline.active
? "bg-[rgb(120,120,250,0.7)] border border-[#7878FA] shadow-md rounded-md"
: ""
}`}
onClick={() => dispatch(activeTimeline(timeline.id))}
key={timeline.id}
>
<span className="dark:text-[#E4E4F0] text-[#181825] text-sm">
{timeline.display}
</span>
</button>
);
}
6 changes: 5 additions & 1 deletion src/app/page.tsx
@@ -1,17 +1,21 @@
import NavHome from "./components/NavHome";
import CarouselCoins from "./components/CarouselCoins";
import GraphCoins from "./components/GraphCoins";
import Timeline from "./components/Timeline";

export default function Home() {
return (
<main>
<main className="pb-10">
<NavHome />
<div className="mt-[70px] max-w-[1440px] mx-auto xl:px-[72px] lg:px-[36px] md:px-[24px]">
<CarouselCoins />
</div>
<div className="mt-10 max-w-[1440px] mx-auto xl:px-[72px] lg:px-[36px] md:px-[24px]">
<GraphCoins />
</div>
<div className="mt-10 max-w-[1440px] mx-auto xl:px-[72px] lg:px-[36px] md:px-[24px]">
<Timeline />
</div>
</main>
);
}
29 changes: 29 additions & 0 deletions src/app/utils/formatDateGraph.ts
@@ -0,0 +1,29 @@
const formatDateGraphs = (tick: string, days: string): string => {
const date = new Date(tick);
let formattedDate: string;

if (days === "1") {
formattedDate = date.toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
hour12: false,
});
} else if (["7", "14", "30"].includes(days)) {
formattedDate = date.toLocaleDateString(undefined, {
day: "numeric",
month: "short",
});
} else if (["90", "180", "365", "max"].includes(days)) {
formattedDate = date.toLocaleDateString(undefined, {
day: "numeric",
month: "short",
year: "numeric",
});
} else {
formattedDate = date.toLocaleDateString();
}

return formattedDate;
};

export default formatDateGraphs;
93 changes: 93 additions & 0 deletions src/redux/features/timelineSlice.ts
@@ -0,0 +1,93 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";

export type TimelineItem = {
id: number;
days: string;
display: string;
active: boolean;
};

type TimelineState = {
currentTimeline: TimelineItem;
timeline: TimelineItem[];
};

const initialState: TimelineState = {
currentTimeline: {
id: 1,
days: "1",
display: "1D",
active: true,
},
timeline: [
{
id: 1,
days: "1",
display: "1D",
active: true,
},
{ id: 2, days: "7", display: "7D", active: false },
{
id: 3,
days: "14",
display: "14D",
active: false,
},
{
id: 4,
days: "30",
display: "1M",
active: false,
},
{
id: 5,
days: "90",
display: "3M",
active: false,
},
{
id: 6,
days: "180",
display: "6M",
active: false,
},
{
id: 7,
days: "365",
display: "1Y",
active: false,
},
{
id: 8,
days: "max",
display: "MAX",
active: false,
},
],
};

export const timelineSlice = createSlice({
name: "timeline",
initialState,
reducers: {
activeTimeline: (state, action: PayloadAction<number>) => {
const currentActiveIndex = state.timeline.findIndex(
(item) => item.active
);
if (currentActiveIndex !== -1) {
state.timeline[currentActiveIndex].active = false;
}

const newActiveIndex = state.timeline.findIndex(
(item) => item.id === action.payload
);
if (newActiveIndex !== -1) {
state.timeline[newActiveIndex].active = true;
state.currentTimeline = { ...state.timeline[newActiveIndex] };
}
},
},
});

export const { activeTimeline } = timelineSlice.actions;
export default timelineSlice.reducer;
2 changes: 2 additions & 0 deletions src/redux/store.ts
Expand Up @@ -5,13 +5,15 @@ import globalReducer from "./features/globalSlice";
import currencyReducer from "./features/currencySlice";
import coinReducer from "./features/coinInfoSlice";
import selectedCoinReducer from "./features/selectedCoinSlice";
import timelineReducer from "./features/timelineSlice";

export const store = configureStore({
reducer: {
globalData: globalReducer,
currency: currencyReducer,
coinData: coinReducer,
selectedCoin: selectedCoinReducer,
timeline: timelineReducer,
},
});

Expand Down

0 comments on commit c90c45e

Please sign in to comment.