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
feat/timeline-chart #7
Changes from 2 commits
3a4532b
d3d4267
18d8afa
5e6c2dc
9f7ef73
dcfa723
082ef7b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,14 +35,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) { | ||
|
@@ -53,16 +58,23 @@ 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") { | ||
/* if (loading === "pending") { | ||
return <div>Loading...</div>; | ||
} | ||
} */ | ||
|
||
if (hasError) { | ||
return <div>Error fetching data</div>; | ||
|
@@ -73,11 +85,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, | ||
})) | ||
: []; | ||
|
@@ -87,13 +99,52 @@ 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", | ||
}) | ||
); | ||
} | ||
}; | ||
|
||
const formatDateGraphs = (tick: 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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be moved to a utility file from where you can export formatDateGraphs function because it does not depend on a state, it is a pure JS function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good one. It makes sense to move it to utility folder as separate function. It makes code so much cleaner. Doing it right now. |
||
} | ||
|
||
return formattedDate; | ||
}; | ||
|
||
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" | ||
|
@@ -113,19 +164,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={formatDateGraphs} | ||
/> | ||
<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> | ||
|
@@ -134,10 +190,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={formatDateGraphs} | ||
/> | ||
|
||
<Tooltip content={<></>} /> | ||
</AreaChart> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
"use client"; | ||
import { useDispatch } from "react-redux"; | ||
import { AppDispatch, useAppSelector } from "../../redux/store"; | ||
import { activeTimeline } from "../../redux/features/timelineSlice"; | ||
|
||
export default function Timeline() { | ||
const dispatch: AppDispatch = useDispatch(); | ||
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) => ( | ||
<button | ||
className={`w-14 p-1 ${ | ||
t.active | ||
? "bg-[rgb(120,120,250,0.7)] border border-[#7878FA] shadow-md rounded-md" | ||
: "" | ||
}`} | ||
onClick={() => dispatch(activeTimeline(t.id))} | ||
key={t.id} | ||
> | ||
<span className="dark:text-[#E4E4F0] text-[#181825] text-sm"> | ||
{t.display} | ||
</span> | ||
</button> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be moved into it's own separate component. What will you name this component? |
||
))} | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
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 ( | ||
|
@@ -12,6 +13,10 @@ export default function Home() { | |
<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> | ||
<div className="mt-10"></div> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we have this empty div? |
||
</main> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { createSlice, PayloadAction } from "@reduxjs/toolkit"; | ||
|
||
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this commented out code?