1+ import React from 'react' ;
2+ import { Box , Heading , Button , VStack , HStack , Text } from '@chakra-ui/react' ;
3+ import { DragIcon } from '@/components/ui' ;
4+ import { COLORS } from '@/constants/form' ;
5+
6+
7+
8+ interface VolunteerRankingFormProps {
9+ rankedPreferences : string [ ] ;
10+ onMoveItem : ( fromIndex : number , toIndex : number ) => void ;
11+ onSubmit : ( ) => void ;
12+ }
13+
14+ export function VolunteerRankingForm ( {
15+ rankedPreferences,
16+ onMoveItem,
17+ onSubmit
18+ } : VolunteerRankingFormProps ) {
19+ const [ draggedIndex , setDraggedIndex ] = React . useState < number | null > ( null ) ;
20+ const [ dropTargetIndex , setDropTargetIndex ] = React . useState < number | null > ( null ) ;
21+
22+ const renderStatementWithBold = ( statement : string ) => {
23+ const boldPhrases = [
24+ 'the same age as me' ,
25+ 'the same diagnosis as me' ,
26+ 'the same marital status as me' ,
27+ 'the same ethnic or cultural group as me' ,
28+ 'the same parental status as me'
29+ ] ;
30+
31+ const phraseToBold = boldPhrases . find ( phrase => statement . includes ( phrase ) ) ;
32+
33+ if ( ! phraseToBold ) {
34+ return statement ;
35+ }
36+
37+ const parts = statement . split ( phraseToBold ) ;
38+
39+ return (
40+ < >
41+ { parts [ 0 ] }
42+ < Text as = "span" fontWeight = { 700 } >
43+ { phraseToBold }
44+ </ Text >
45+ { parts [ 1 ] }
46+ </ >
47+ ) ;
48+ } ;
49+
50+ const handleDragStart = ( e : React . DragEvent , index : number ) => {
51+ setDraggedIndex ( index ) ;
52+ e . dataTransfer . effectAllowed = 'move' ;
53+ e . dataTransfer . setData ( 'text/html' , index . toString ( ) ) ;
54+ } ;
55+
56+ const handleDragOver = ( e : React . DragEvent , index : number ) => {
57+ e . preventDefault ( ) ;
58+ e . dataTransfer . dropEffect = 'move' ;
59+ setDropTargetIndex ( index ) ;
60+ } ;
61+
62+ const handleDragLeave = ( ) => {
63+ setDropTargetIndex ( null ) ;
64+ } ;
65+
66+ const handleDrop = ( e : React . DragEvent , dropIndex : number ) => {
67+ e . preventDefault ( ) ;
68+ if ( draggedIndex !== null && draggedIndex !== dropIndex ) {
69+ onMoveItem ( draggedIndex , dropIndex ) ;
70+ }
71+ setDraggedIndex ( null ) ;
72+ setDropTargetIndex ( null ) ;
73+ } ;
74+
75+ const handleDragEnd = ( ) => {
76+ setDraggedIndex ( null ) ;
77+ setDropTargetIndex ( null ) ;
78+ } ;
79+ return (
80+ < Box >
81+ < Heading
82+ as = "h1"
83+ fontFamily = "system-ui, -apple-system, sans-serif"
84+ fontWeight = { 600 }
85+ color = { COLORS . veniceBlue }
86+ fontSize = "28px"
87+ mb = { 8 }
88+ >
89+ Volunteer Matching Preferences
90+ </ Heading >
91+
92+ < Box mb = { 10 } >
93+ < HStack gap = { 3 } >
94+ < Box flex = "1" >
95+ < Box h = "3px" bg = { COLORS . progressGray } borderRadius = "full" />
96+ </ Box >
97+ < Box flex = "1" >
98+ < Box h = "3px" bg = { COLORS . teal } borderRadius = "full" />
99+ </ Box >
100+ </ HStack >
101+ </ Box >
102+
103+ < Box mb = { 10 } >
104+ < Heading
105+ as = "h2"
106+ fontFamily = "system-ui, -apple-system, sans-serif"
107+ fontWeight = { 600 }
108+ color = { COLORS . veniceBlue }
109+ fontSize = "20px"
110+ mb = { 3 }
111+ >
112+ Ranking Match Preferences
113+ </ Heading >
114+ < Text
115+ color = { COLORS . fieldGray }
116+ fontFamily = "system-ui, -apple-system, sans-serif"
117+ fontSize = "15px"
118+ mb = { 2 }
119+ >
120+ This information will be used to match you with a suitable volunteer.
121+ </ Text >
122+ < Text
123+ color = { COLORS . veniceBlue }
124+ fontFamily = "system-ui, -apple-system, sans-serif"
125+ fontSize = "15px"
126+ fontWeight = { 600 }
127+ mb = { 8 }
128+ >
129+ Note that your volunteer is guaranteed to speak your language and have the same availability.
130+ </ Text >
131+
132+ < VStack gap = { 5 } >
133+ < Box w = "full" >
134+ < Text
135+ color = { COLORS . fieldGray }
136+ fontFamily = "system-ui, -apple-system, sans-serif"
137+ fontSize = "14px"
138+ mb = { 2 }
139+ >
140+ Rank the following statements in the order that you agree with them:
141+ </ Text >
142+ < Text
143+ color = { COLORS . fieldGray }
144+ fontFamily = "system-ui, -apple-system, sans-serif"
145+ fontSize = "12px"
146+ mb = { 6 }
147+ >
148+ 1 is most agreed, 5 is least agreed.
149+ </ Text >
150+
151+ < VStack gap = { 3 } align = "start" >
152+ { rankedPreferences . map ( ( statement , index ) => {
153+ const isDragging = draggedIndex === index ;
154+ const isDropTarget = dropTargetIndex === index ;
155+
156+ return (
157+ < HStack
158+ key = { `ranking-item-${ index } -${ statement . slice ( 0 , 20 ) } ` }
159+ w = "full"
160+ gap = { 4 }
161+ align = "center"
162+ >
163+ < Text
164+ fontFamily = "system-ui, -apple-system, sans-serif"
165+ fontSize = "16px"
166+ fontWeight = { 600 }
167+ color = { COLORS . veniceBlue }
168+ minW = "20px"
169+ >
170+ { index + 1 } .
171+ </ Text >
172+
173+ < HStack
174+ flex = "1"
175+ p = { 4 }
176+ bg = { isDragging ? "#e5e7eb" : isDropTarget ? "#dbeafe" : "#f9fafb" }
177+ border = { `1px solid ${ isDropTarget ? COLORS . teal : "#e5e7eb" } ` }
178+ borderRadius = "6px"
179+ cursor = { isDragging ? 'grabbing' : 'grab' }
180+ gap = { 3 }
181+ opacity = { isDragging ? 0.5 : 1 }
182+ transition = "all 0.2s ease"
183+ draggable
184+ onDragStart = { ( e ) => handleDragStart ( e , index ) }
185+ onDragOver = { ( e ) => handleDragOver ( e , index ) }
186+ onDragLeave = { handleDragLeave }
187+ onDrop = { ( e ) => handleDrop ( e , index ) }
188+ onDragEnd = { handleDragEnd }
189+ _hover = { {
190+ borderColor : COLORS . teal ,
191+ boxShadow : `0 0 0 1px ${ COLORS . teal } 20` ,
192+ bg : isDragging ? "#e5e7eb" : "#f3f4f6"
193+ } }
194+ >
195+ < Box
196+ cursor = { isDragging ? 'grabbing' : 'grab' }
197+ p = { 1 }
198+ _hover = { { opacity : 0.7 } }
199+ >
200+ < DragIcon />
201+ </ Box >
202+
203+ < Text
204+ fontFamily = "system-ui, -apple-system, sans-serif"
205+ fontSize = "14px"
206+ color = { COLORS . veniceBlue }
207+ flex = "1"
208+ userSelect = "none"
209+ >
210+ { renderStatementWithBold ( statement ) }
211+ </ Text >
212+ </ HStack >
213+ </ HStack >
214+ ) ;
215+ } ) }
216+ </ VStack >
217+ </ Box >
218+ </ VStack >
219+ </ Box >
220+
221+ < Box w = "full" display = "flex" justifyContent = "flex-end" >
222+ < Button
223+ bg = { COLORS . teal }
224+ color = "white"
225+ _hover = { { bg : COLORS . teal } }
226+ _active = { { bg : COLORS . teal } }
227+ onClick = { onSubmit }
228+ w = "auto"
229+ h = "40px"
230+ fontSize = "14px"
231+ fontWeight = { 500 }
232+ px = { 6 }
233+ >
234+ Submit Preferences →
235+ </ Button >
236+ </ Box >
237+ </ Box >
238+ ) ;
239+ }
0 commit comments