11import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" ;
22import { faPencilAlt } from '@fortawesome/free-solid-svg-icons' ;
33import { useNavigate } from "react-router-dom" ;
4+ import type { PriceHistory } from "../types/priceHistory" ;
5+ import 'chartjs-adapter-date-fns' ;
6+ import { Line } from 'react-chartjs-2' ;
7+ import {
8+ Chart as ChartJS ,
9+ LineElement ,
10+ PointElement ,
11+ CategoryScale ,
12+ LinearScale ,
13+ Tooltip ,
14+ Legend ,
15+ TimeScale ,
16+ type ChartOptions ,
17+ } from 'chart.js' ;
18+
19+ ChartJS . register ( LineElement , PointElement , CategoryScale , LinearScale , Tooltip , Legend , TimeScale ) ;
20+
421
522export interface ItemCardProps {
623 id : string ;
@@ -13,6 +30,7 @@ export interface ItemCardProps {
1330 isDiscount : boolean ;
1431 itemPrice : number ;
1532 discountedPrice ?: number ;
33+ priceHistory ?: PriceHistory [ ] ;
1634}
1735
1836const ItemCard : React . FC < ItemCardProps > = ( {
@@ -26,13 +44,106 @@ const ItemCard: React.FC<ItemCardProps> = ({
2644 isDiscount,
2745 itemPrice,
2846 discountedPrice,
47+ priceHistory
2948} ) => {
3049 const navigate = useNavigate ( ) ;
3150
3251 const handleEditClick = ( ) => {
3352 navigate ( `/edit-item/${ id } ` ) ;
3453 } ;
3554
55+ const formatPrice = ( price : number ) => {
56+ return price % 1 === 0 ? `${ price } ` : `${ price . toFixed ( 2 ) } `
57+ }
58+
59+ let sortedHistory = Array . isArray ( priceHistory )
60+ ? [ ...priceHistory ]
61+ . filter ( h => h && h . changedAt && h . price !== undefined )
62+ . sort ( ( a , b ) => new Date ( a . changedAt ) . getTime ( ) - new Date ( b . changedAt ) . getTime ( ) )
63+ : [ ] ;
64+
65+ if ( sortedHistory . length > 0 ) {
66+ const last = sortedHistory [ sortedHistory . length - 1 ] ;
67+ const lastDate = new Date ( last . changedAt ) ;
68+ const today = new Date ( ) ;
69+ lastDate . setHours ( 0 , 0 , 0 , 0 ) ;
70+ today . setHours ( 0 , 0 , 0 , 0 ) ;
71+
72+ if ( lastDate . getTime ( ) < today . getTime ( ) ) {
73+ sortedHistory = [
74+ ...sortedHistory ,
75+ {
76+ ...last ,
77+ id : last . id + '-virtual' ,
78+ changedAt : today . toISOString ( ) ,
79+ }
80+ ] ;
81+ }
82+ }
83+
84+ const chartData = {
85+ datasets : [
86+ {
87+ label : 'Price' ,
88+ data : sortedHistory . map ( h => ( {
89+ x : h . changedAt ,
90+ y : h . discountedPrice !== undefined && h . discountedPrice !== null
91+ ? h . discountedPrice
92+ : h . price
93+ } ) ) ,
94+ fill : false ,
95+ borderColor : '#c8ff00' ,
96+ backgroundColor : '#c8ff00' ,
97+ tension : 0.2 ,
98+ stepped : 'before' as const ,
99+ } ,
100+ ] ,
101+ } ;
102+
103+ const chartOptions : ChartOptions < 'line' > = {
104+ responsive : true ,
105+ maintainAspectRatio : false ,
106+ plugins : {
107+ legend : { display : false } ,
108+ tooltip : {
109+ enabled : true ,
110+ callbacks : {
111+ label : function ( context ) {
112+ const yValue = ( context . raw as { x : any ; y : number } ) . y ;
113+ return `$${ formatPrice ( yValue ) } ` ;
114+ } ,
115+ } ,
116+ } ,
117+ } ,
118+ scales : {
119+ x : {
120+ type : 'time' ,
121+ time : {
122+ unit : 'day' ,
123+ tooltipFormat : 'MM/dd/yy hh:mm:ss a' ,
124+ displayFormats : {
125+ day : 'MM/dd/yy' ,
126+ } ,
127+ } ,
128+ title : { display : false } ,
129+ grid : { display : false } ,
130+ border : { display : false } ,
131+ ticks : { color : "#FFF" , maxTicksLimit : 4 }
132+ } ,
133+ y : {
134+ title : { display : true , text : 'Price' , color : "#FFF" } ,
135+ grid : { display : false } ,
136+ border : { display : false } ,
137+ ticks : {
138+ callback : function ( value ) {
139+ return `${ formatPrice ( Number ( value ) ) } ` ;
140+ } ,
141+ color : "#FFF"
142+ }
143+ } ,
144+ } ,
145+ } ;
146+
36147 return (
37148 < div className = "item-card" >
38149 < img src = { itemImageUrl } alt = { itemName } />
@@ -42,18 +153,60 @@ const ItemCard: React.FC<ItemCardProps> = ({
42153 < div className = "item-category" > { category } </ div >
43154 < div className = "item-store" > { storeName } </ div >
44155 < div className = "item-price" >
45- { isDiscount && discountedPrice !== undefined ? (
46- < div >
47- < p > ${ discountedPrice } </ p >
48- < s > ${ itemPrice } </ s >
49- </ div >
50- ) : (
51- < p > ${ itemPrice } </ p >
52- ) }
156+ { isDiscount && discountedPrice !== undefined ? (
157+ < div >
158+ < p > ${ formatPrice ( discountedPrice ) } </ p >
159+ < s > ${ formatPrice ( itemPrice ) } </ s >
160+ </ div >
161+ ) : (
162+ < p > ${ formatPrice ( itemPrice ) } </ p >
163+ ) }
53164 </ div >
54165 < button className = "button edit-button" onClick = { handleEditClick } >
55166 < FontAwesomeIcon icon = { faPencilAlt } /> Edit
56167 </ button >
168+
169+ { Array . isArray ( priceHistory ) && priceHistory . length > 0 && (
170+ < div className = "price-history" >
171+ < h4 > Price History</ h4 >
172+ { sortedHistory . length > 0 && (
173+ < div className = "price-history-graph" >
174+ < Line data = { chartData } options = { chartOptions } />
175+ </ div >
176+ ) }
177+ < table className = "price-history-table" >
178+ < thead >
179+ < tr >
180+ < th > Date</ th >
181+ < th > Price</ th >
182+ </ tr >
183+ </ thead >
184+ < tbody >
185+ { [ ...priceHistory ]
186+ . sort ( ( a , b ) => new Date ( a . changedAt ) . getTime ( ) - new Date ( b . changedAt ) . getTime ( ) )
187+ . map ( ( h ) => (
188+ < tr key = { h . id } >
189+ < td >
190+ { new Date ( h . changedAt ) . toLocaleDateString ( undefined , {
191+ year : '2-digit' ,
192+ month : '2-digit' ,
193+ day : '2-digit'
194+ } ) }
195+ </ td >
196+ < td >
197+ { h . discountedPrice !== undefined && h . discountedPrice !== null
198+ ? (
199+ < span > ${ formatPrice ( h . discountedPrice ) } </ span >
200+ ) : (
201+ < span > ${ formatPrice ( h . price ) } </ span >
202+ ) }
203+ </ td >
204+ </ tr >
205+ ) ) }
206+ </ tbody >
207+ </ table >
208+ </ div >
209+ ) }
57210 </ div >
58211 )
59212}
0 commit comments