@@ -2,6 +2,7 @@ import React, { useState, useMemo } from 'react';
22import { X , ChevronRight , ChevronLeft , Check , AlertTriangle } from 'lucide-react' ;
33import { MetricDef } from '../types' ;
44import { useTranslation } from 'react-i18next' ;
5+ import { AHP_SURVEY_URL } from '../constants' ;
56
67interface AHPModalProps {
78 metrics : MetricDef [ ] ;
@@ -36,6 +37,13 @@ export const AHPModal: React.FC<AHPModalProps> = ({ metrics, isOpen, onClose, on
3637 const [ currentStep , setCurrentStep ] = useState ( 0 ) ;
3738 const [ forceConsistency , setForceConsistency ] = useState ( true ) ;
3839
40+ // Research Submission state
41+ const [ role , setRole ] = useState < string > ( '' ) ;
42+ const [ otherRole , setOtherRole ] = useState < string > ( '' ) ;
43+ const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
44+ const [ submitSuccess , setSubmitSuccess ] = useState ( false ) ;
45+ const [ submitError , setSubmitError ] = useState ( false ) ;
46+
3947 // Calculate AHP Weights & Consistency
4048 const results = useMemo ( ( ) => {
4149 if ( currentStep !== pairs . length ) return null ;
@@ -115,6 +123,44 @@ export const AHPModal: React.FC<AHPModalProps> = ({ metrics, isOpen, onClose, on
115123 }
116124 } ;
117125
126+ const GOOGLE_SCRIPT_URL = AHP_SURVEY_URL ;
127+
128+ const handleSubmitResearch = async ( ) => {
129+ if ( ! results ) return ;
130+ setIsSubmitting ( true ) ;
131+ setSubmitError ( false ) ;
132+
133+ const finalRole = role === 'other' ? otherRole : role ;
134+
135+ // Prepare data object based on results
136+ const data : Record < string , any > = {
137+ role : finalRole ,
138+ CR : results . CR ,
139+ timestamp : new Date ( ) . toISOString ( )
140+ } ;
141+
142+ metrics . forEach ( ( m , idx ) => {
143+ data [ `weight_${ m . id } ` ] = results . weights [ idx ] ;
144+ } ) ;
145+
146+ try {
147+ await fetch ( GOOGLE_SCRIPT_URL , {
148+ method : 'POST' ,
149+ mode : 'no-cors' ,
150+ headers : {
151+ 'Content-Type' : 'text/plain;charset=utf-8' ,
152+ } ,
153+ body : JSON . stringify ( data )
154+ } ) ;
155+ setSubmitSuccess ( true ) ;
156+ } catch ( error ) {
157+ console . error ( "Error submitting AHP results:" , error ) ;
158+ setSubmitError ( true ) ;
159+ } finally {
160+ setIsSubmitting ( false ) ;
161+ }
162+ } ;
163+
118164 const handleSliderChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
119165 const newVal = parseInt ( e . target . value ) ;
120166 const newSelections = [ ...selections ] ;
@@ -140,7 +186,7 @@ export const AHPModal: React.FC<AHPModalProps> = ({ metrics, isOpen, onClose, on
140186 lg:border ${ isDarkMode ? 'lg:border-neutral-800' : 'lg:border-neutral-200' }
141187 relative w-full h-[100dvh] lg:h-auto lg:max-h-[90vh] lg:max-w-2xl flex flex-col overflow-hidden lg:rounded-3xl shadow-2xl transition-all
142188 ` } onClick = { e => e . stopPropagation ( ) } >
143-
189+
144190 { /* Header */ }
145191 < div className = { `flex items-center justify-between p-5 lg:p-6 border-b shrink-0 ${ isDarkMode ? 'border-neutral-800' : 'border-neutral-100' } ` } >
146192 < div className = "flex items-center gap-4" >
@@ -266,6 +312,64 @@ export const AHPModal: React.FC<AHPModalProps> = ({ metrics, isOpen, onClose, on
266312 </ div >
267313 </ div >
268314 ) }
315+
316+ { results && results . CR <= 0.1 && (
317+ < div className = { `flex flex-col gap-4 p-5 rounded-xl border ${ isDarkMode ? 'bg-neutral-800/30 border-neutral-800' : 'bg-neutral-50 border-neutral-100' } ` } >
318+ < div className = "space-y-1" >
319+ < h4 className = "text-sm lg:text-base font-bold uppercase tracking-wider" > { t ( 'ahp.submit_research' ) } </ h4 >
320+ < p className = "text-[10px] lg:text-[12px] opacity-70" > { t ( 'ahp.submit_research_desc' ) } </ p >
321+ </ div >
322+
323+ { submitSuccess ? (
324+ < div className = "flex items-center gap-2 text-green-500 font-bold text-[10px] lg:text-[12px] uppercase tracking-wider p-2" >
325+ < Check className = "w-4 h-4" /> { t ( 'ahp.submit_success' ) }
326+ </ div >
327+ ) : (
328+ < div className = "space-y-4" >
329+ < div className = "space-y-2" >
330+ < label className = "text-[10px] lg:text-[12px] font-bold uppercase tracking-widest opacity-70" > { t ( 'ahp.role_label' ) } </ label >
331+ < select
332+ value = { role }
333+ onChange = { ( e ) => setRole ( e . target . value ) }
334+ disabled = { isSubmitting }
335+ className = { `w-full p-2.5 rounded-lg text-sm outline-none border transition-colors focus:border-sky-800 ${ isDarkMode ? 'bg-neutral-900 border-neutral-800' : 'bg-white border-neutral-200' } ` }
336+ >
337+ < option value = "" > -- { t ( 'ahp.role_label' ) } --</ option >
338+ < option value = "student" > { t ( 'ahp.role_student' ) } </ option >
339+ < option value = "researcher" > { t ( 'ahp.role_researcher' ) } </ option >
340+ < option value = "professional" > { t ( 'ahp.role_professional' ) } </ option >
341+ < option value = "citizen" > { t ( 'ahp.role_citizen' ) } </ option >
342+ < option value = "other" > { t ( 'ahp.role_other' ) } </ option >
343+ </ select >
344+ </ div >
345+ { role === 'other' && (
346+ < div className = "space-y-2 animate-in slide-in-from-top-2 duration-200" >
347+ < input
348+ type = "text"
349+ value = { otherRole }
350+ onChange = { ( e ) => setOtherRole ( e . target . value ) }
351+ disabled = { isSubmitting }
352+ placeholder = { t ( 'ahp.role_other_placeholder' ) }
353+ className = { `w-full p-2.5 rounded-lg text-sm outline-none border transition-colors focus:border-sky-800 ${ isDarkMode ? 'bg-neutral-900 border-neutral-800' : 'bg-white border-neutral-200' } ` }
354+ />
355+ </ div >
356+ ) }
357+ { submitError && (
358+ < div className = "text-red-500 text-[10px] lg:text-[12px] font-bold uppercase tracking-wider p-1" >
359+ { t ( 'ahp.submit_error' ) }
360+ </ div >
361+ ) }
362+ < button
363+ onClick = { handleSubmitResearch }
364+ disabled = { isSubmitting }
365+ className = { `w-full py-2.5 rounded-xl text-[10px] lg:text-[12px] font-bold uppercase tracking-widest transition-all ${ isSubmitting ? 'opacity-50 cursor-not-allowed bg-sky-900 text-white' : 'bg-sky-900 hover:bg-sky-800 text-white shadow-lg shadow-sky-800/30' } ` }
366+ >
367+ { isSubmitting ? t ( 'ahp.submitting' ) : t ( 'ahp.submit_button' ) }
368+ </ button >
369+ </ div >
370+ ) }
371+ </ div >
372+ ) }
269373 </ div >
270374 ) }
271375 </ div >
@@ -292,7 +396,11 @@ export const AHPModal: React.FC<AHPModalProps> = ({ metrics, isOpen, onClose, on
292396 ) : (
293397 < >
294398 < button
295- onClick = { ( ) => { setCurrentStep ( 0 ) ; } }
399+ onClick = { ( ) => {
400+ setCurrentStep ( 0 ) ;
401+ setSubmitSuccess ( false ) ;
402+ setSubmitError ( false ) ;
403+ } }
296404 className = { `px-4 py-2 rounded-xl text-[10px] lg:text-[12px] font-bold uppercase tracking-widest transition-all ${ isDarkMode ? 'hover:bg-neutral-800' : 'hover:bg-neutral-200' } ` }
297405 >
298406 { results && results . CR > 0.1 && forceConsistency ? t ( 'ahp.review_comparisons' ) : t ( 'ahp.retake_survey' ) }
0 commit comments