1
1
'use client' ;
2
2
3
3
import { Line } from 'react-chartjs-2' ;
4
-
5
4
import {
6
5
Chart as ChartJS ,
7
6
CategoryScale ,
@@ -13,12 +12,12 @@ import {
13
12
Legend ,
14
13
} from 'chart.js' ;
15
14
import { useQuery } from '@tanstack/react-query' ;
16
- import { useState } from 'react' ;
15
+ import { useEffect , useState } from 'react' ;
17
16
import { COLORS , PATHS , SCREENS } from '@/constants' ;
18
17
import { Dropdown , Input } from '@/components' ;
18
+ import { PostDetailValue } from '@/types' ;
19
19
import { useResponsive } from '@/hooks' ;
20
20
import { postDetail } from '@/apis' ;
21
- import { PostDetailValue } from '@/types' ;
22
21
23
22
ChartJS . register (
24
23
CategoryScale ,
@@ -45,60 +44,94 @@ interface IProp {
45
44
releasedAt : string ;
46
45
}
47
46
47
+ type ModeType = 'none' | 'weekly' | 'monthly' | 'custom' ;
48
+
48
49
export const Graph = ( { id, releasedAt } : IProp ) => {
49
50
const width = useResponsive ( ) ;
50
51
51
52
const isMBI = width < SCREENS . MBI ;
52
53
53
- const [ type , setType ] = useState ( {
54
- start : '' ,
55
- end : '' ,
56
- type : 'View' ,
57
- } ) ;
54
+ const [ type , setType ] = useState ( { start : '' , end : '' , type : 'View' } ) ;
55
+ const [ mode , setMode ] = useState < ModeType > ( 'none' ) ;
58
56
59
57
const { data : datas } = useQuery ( {
60
58
queryKey : [ PATHS . DETAIL , type ] ,
61
59
queryFn : async ( ) => await postDetail ( id , type . start , type . end ) ,
62
- select : ( { post } ) => ( {
63
- labels : post . map ( ( i ) => i . date . split ( 'T' ) [ 0 ] ) ,
64
- datasets : [
65
- {
66
- label : type . type ,
67
- data : post . map (
68
- ( i ) => i [ `daily${ type . type } Count` as keyof PostDetailValue ] ,
69
- ) ,
70
- ...datasets ,
71
- } ,
72
- ] ,
73
- } ) ,
60
+ select : ( { post } ) => {
61
+ post = post . sort (
62
+ ( a , b ) => new Date ( a . date ) . getTime ( ) - new Date ( b . date ) . getTime ( ) ,
63
+ ) ;
64
+ return {
65
+ labels : post . map ( ( i ) => i . date . split ( 'T' ) [ 0 ] ) ,
66
+ datasets : [
67
+ {
68
+ label : type . type ,
69
+ data : post . map (
70
+ ( i ) => i [ `daily${ type . type } Count` as keyof PostDetailValue ] ,
71
+ ) ,
72
+ ...datasets ,
73
+ } ,
74
+ ] ,
75
+ } ;
76
+ } ,
74
77
enabled : ! ! type . start && ! ! type . end ,
75
78
} ) ;
76
79
80
+ useEffect ( ( ) => {
81
+ if ( mode === 'none' || mode === 'custom' ) {
82
+ setType ( ( prev ) => ( { ...prev , start : '' , end : '' } ) ) ;
83
+ } else {
84
+ const start = new Date ( ) ;
85
+ if ( mode === 'monthly' ) start . setMonth ( start . getMonth ( ) - 1 ) ;
86
+ else start . setDate ( start . getDate ( ) - 7 ) ;
87
+
88
+ setType ( ( prev ) => ( {
89
+ ...prev ,
90
+ start : start . toISOString ( ) . split ( 'T' ) [ 0 ] ,
91
+ end : new Date ( ) . toISOString ( ) . split ( 'T' ) [ 0 ] ,
92
+ } ) ) ;
93
+ }
94
+ } , [ mode ] ) ;
95
+
77
96
return (
78
97
< div className = "w-full bg-BG-SUB flex flex-col items-center px-[25px] pb-[30px] gap-[30px] max-MBI:px-5 max-MBI:pb-10" >
79
98
< div className = "flex items-center gap-[20px] flex-wrap justify-center max-TBL:gap-[10px]" >
80
- < Input
81
- size = { isMBI ? 'SMALL' : 'MEDIUM' }
82
- form = "SMALL"
83
- value = { type . start }
84
- min = { releasedAt . split ( 'T' ) [ 0 ] }
85
- onChange = { ( e ) => setType ( { ...type , start : e . target . value } ) }
86
- placeholder = "시작 날짜"
87
- type = "date"
88
- />
89
- < span className = "text-ST4 max-TBL:text-T5 text-TEXT-MAIN" > ~</ span >
90
- < Input
91
- size = { isMBI ? 'SMALL' : 'MEDIUM' }
92
- form = "SMALL"
93
- value = { type . end }
94
- min = { type . start ? type . start : releasedAt . split ( 'T' ) [ 0 ] }
95
- onChange = { ( e ) => setType ( { ...type , end : e . target . value } ) }
96
- placeholder = "종료 날짜"
97
- type = "date"
99
+ { mode === 'custom' && (
100
+ < >
101
+ < Input
102
+ size = { isMBI ? 'SMALL' : 'MEDIUM' }
103
+ form = "SMALL"
104
+ value = { type . start }
105
+ min = { releasedAt . split ( 'T' ) [ 0 ] }
106
+ onChange = { ( e ) => setType ( { ...type , start : e . target . value } ) }
107
+ placeholder = "시작 날짜"
108
+ type = "date"
109
+ />
110
+ < span className = "text-ST4 max-TBL:text-T5 text-TEXT-MAIN" > ~</ span >
111
+ < Input
112
+ size = { isMBI ? 'SMALL' : 'MEDIUM' }
113
+ form = "SMALL"
114
+ value = { type . end }
115
+ min = { type . start ? type . start : releasedAt . split ( 'T' ) [ 0 ] }
116
+ onChange = { ( e ) => setType ( { ...type , end : e . target . value } ) }
117
+ placeholder = "종료 날짜"
118
+ type = "date"
119
+ />
120
+ </ >
121
+ ) }
122
+ < Dropdown
123
+ onChange = { ( e ) => setMode ( e as ModeType ) }
124
+ defaultValue = "미선택"
125
+ options = { [
126
+ [ '미선택' , 'none' ] ,
127
+ [ '지난 7일' , 'weekly' ] ,
128
+ [ '지난 30일' , 'monthly' ] ,
129
+ [ '직접선택' , 'custom' ] ,
130
+ ] }
98
131
/>
99
132
< Dropdown
100
133
onChange = { ( e ) => setType ( { ...type , type : e as string } ) }
101
- defaultValue = { ' 조회수' }
134
+ defaultValue = " 조회수"
102
135
options = { [
103
136
[ '조회수' , 'View' ] ,
104
137
[ '좋아요' , 'Like' ] ,
@@ -120,24 +153,10 @@ export const Graph = ({ id, releasedAt }: IProp) => {
120
153
maintainAspectRatio : false ,
121
154
animation : false ,
122
155
interaction : { mode : 'nearest' , intersect : false } ,
123
- plugins : {
124
- legend : {
125
- display : false ,
126
- } ,
127
- } ,
156
+ plugins : { legend : { display : false } } ,
128
157
scales : {
129
- x : {
130
- axis : 'x' ,
131
- grid : {
132
- color : COLORS . BORDER . SUB ,
133
- } ,
134
- } ,
135
- y : {
136
- axis : 'y' ,
137
- grid : {
138
- color : COLORS . BORDER . SUB ,
139
- } ,
140
- } ,
158
+ x : { axis : 'x' , grid : { color : COLORS . BORDER . SUB } } ,
159
+ y : { axis : 'y' , grid : { color : COLORS . BORDER . SUB } } ,
141
160
} ,
142
161
} }
143
162
className = "w-[100%_!important] h-[auto_!important] max-h-[300px]"
0 commit comments