11import { useEffect , useState } from "react" ;
2- import { getMember , patchMemberStatus } from "../api/member" ;
2+ import { getMember , patchMemberStatus , getMemberAuthFile } from "../api/member" ;
33import AdminLayout from "../components/layout/adminLayout" ;
44import Table from "../components/common/table" ;
55import Pagination from "../components/common/pagination" ;
@@ -16,6 +16,8 @@ export default function Members() {
1616 const [ showModal , setShowModal ] = useState ( false ) ;
1717 const [ selectedStatus , setSelectedStatus ] = useState ( null ) ;
1818 const [ rejectReason , setRejectReason ] = useState ( "" ) ;
19+ const [ authFile , setAuthFile ] = useState ( null ) ;
20+ const [ loadingAuthFile , setLoadingAuthFile ] = useState ( false ) ;
1921 const pageSize = 10 ;
2022 const columns = [
2123 { key : "타입" , value : "타입" } ,
@@ -26,6 +28,8 @@ export default function Members() {
2628 { key : "처리상태" , value : "처리 상태" } ,
2729] ;
2830
31+ const VITE_S3_BUCKET_URL = import . meta. env . VITE_S3_BUCKET_URL ;
32+
2933const getMemberData = async ( ) => {
3034 try {
3135 const params = {
@@ -122,19 +126,45 @@ useEffect(() => {
122126 setCurrentPage ( 0 ) ;
123127 } ;
124128
125- const handleRowClick = ( member , originalData ) => {
129+ const handleRowClick = async ( member , originalData ) => {
126130 // originalData가 있으면 사용, 없으면 member 자체 사용
127- setSelectedMember ( originalData || member ) ;
131+ const memberData = originalData || member ;
132+ setSelectedMember ( member ) ;
128133 setShowModal ( true ) ;
129134 setSelectedStatus ( null ) ;
130135 setRejectReason ( "" ) ;
136+ setAuthFile ( null ) ;
137+
138+ // 인증 파일 조회 - memberId는 mapped data나 originalData에서 가져올 수 있음
139+ const memberId = member . memberId || ( originalData && originalData . memberId ) ;
140+ if ( memberId ) {
141+ setLoadingAuthFile ( true ) ;
142+ try {
143+ const response = await getMemberAuthFile ( { memberId : memberId } ) ;
144+ console . log ( "인증 파일 응답:" , response ) ;
145+ // API 응답 구조에 따라 수정 필요
146+ if ( response && response . result ) {
147+ setAuthFile ( response . result ) ;
148+ } else if ( response && response . data ) {
149+ setAuthFile ( response . data ) ;
150+ } else {
151+ setAuthFile ( response ) ;
152+ }
153+ } catch ( error ) {
154+ console . error ( "인증 파일 조회 실패:" , error ) ;
155+ setAuthFile ( null ) ;
156+ } finally {
157+ setLoadingAuthFile ( false ) ;
158+ }
159+ }
131160 } ;
132161
133162 const handleCloseModal = ( ) => {
134163 setShowModal ( false ) ;
135164 setSelectedMember ( null ) ;
136165 setSelectedStatus ( null ) ;
137166 setRejectReason ( "" ) ;
167+ setAuthFile ( null ) ;
138168 } ;
139169
140170 const handleStatusChange = ( newStatus ) => {
@@ -235,7 +265,7 @@ useEffect(() => {
235265 { showModal && selectedMember && (
236266 < div className = "fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center" onClick = { handleCloseModal } >
237267 < div
238- className = "bg-white rounded-lg shadow-xl w-full max-w-md p-6 max-h-[80vh ] overflow-y-auto"
268+ className = "bg-white rounded-lg shadow-xl w-full max-w-5xl p-6 max-h-[90vh ] overflow-y-auto"
239269 onClick = { ( e ) => e . stopPropagation ( ) }
240270 >
241271 < div className = "flex justify-between items-center mb-6" >
@@ -248,7 +278,9 @@ useEffect(() => {
248278 </ button >
249279 </ div >
250280
251- < div className = "space-y-4" >
281+ < div className = "flex gap-6" >
282+ { /* 왼쪽: 회원 정보 */ }
283+ < div className = "flex-1 space-y-4" >
252284 < div >
253285 < label className = "block text-sm font-medium text-gray-700 mb-2" > 이름</ label >
254286 < div className = "p-3 bg-gray-50 rounded border" > { selectedMember . 이름 } </ div >
@@ -345,6 +377,149 @@ useEffect(() => {
345377 </ button >
346378 </ div >
347379 ) }
380+ </ div >
381+
382+ { /* 오른쪽: 인증 파일 및 상세 정보 */ }
383+ < div className = "flex-1 border-l pl-6" >
384+ < h3 className = "text-lg font-semibold text-gray-800 mb-4" > 인증 정보</ h3 >
385+ { loadingAuthFile ? (
386+ < div className = "flex items-center justify-center py-8" >
387+ < div className = "text-gray-500" > 로딩 중...</ div >
388+ </ div >
389+ ) : authFile ? (
390+ < div className = "space-y-6" >
391+ { /* 인증 파일 */ }
392+ < div >
393+ < label className = "block text-sm font-medium text-gray-700 mb-2" > 인증 파일</ label >
394+ { authFile . authenticationFileUrl ? ( ( ) => {
395+ const fileUrl = VITE_S3_BUCKET_URL + authFile . authenticationFileUrl ;
396+ const fileExtension = authFile . authenticationFileUrl . split ( '.' ) . pop ( ) ?. toLowerCase ( ) ;
397+ const isImage = [ 'jpg' , 'jpeg' , 'png' , 'gif' , 'webp' , 'svg' , 'bmp' ] . includes ( fileExtension ) ;
398+ const isPdf = fileExtension === 'pdf' ;
399+
400+ return (
401+ < div className = "space-y-2" >
402+ { isImage ? (
403+ < img
404+ src = { fileUrl }
405+ alt = "인증 파일"
406+ className = "w-full rounded border max-h-[500px] object-contain"
407+ onError = { ( e ) => {
408+ e . target . style . display = 'none' ;
409+ e . target . nextSibling . style . display = 'block' ;
410+ } }
411+ />
412+ ) : isPdf ? (
413+ < iframe
414+ src = { fileUrl }
415+ className = "w-full rounded border"
416+ style = { { height : '500px' } }
417+ title = "인증 파일 PDF"
418+ />
419+ ) : (
420+ < div className = "p-4 bg-gray-50 rounded border text-gray-500 text-center" >
421+ 미리보기를 지원하지 않는 파일 형식입니다.
422+ </ div >
423+ ) }
424+ < div style = { { display : 'none' } } className = "text-gray-500 text-center py-4" >
425+ 파일을 불러올 수 없습니다.
426+ </ div >
427+ < a
428+ href = { fileUrl }
429+ target = "_blank"
430+ rel = "noopener noreferrer"
431+ className = "text-blue-main hover:text-blue-point underline text-sm"
432+ >
433+ 원본 파일 보기
434+ </ a >
435+ </ div >
436+ ) ;
437+ } ) ( ) : (
438+ < div className = "p-3 bg-gray-50 rounded border text-gray-500 text-sm" >
439+ 인증 파일이 없습니다.
440+ </ div >
441+ ) }
442+ </ div >
443+
444+ { /* 전화번호 */ }
445+ { authFile . resDto ?. phoneNumber && (
446+ < div >
447+ < label className = "block text-sm font-medium text-gray-700 mb-2" > 전화번호</ label >
448+ < div className = "p-3 bg-gray-50 rounded border" > { authFile . resDto . phoneNumber } </ div >
449+ </ div >
450+ ) }
451+
452+ { /* 상세 정보 (detail이 있는 경우에만 표시) */ }
453+ { authFile . resDto ?. detail && (
454+ < div >
455+ < label className = "block text-sm font-medium text-gray-700 mb-2" > 사업자 정보</ label >
456+ < div className = "space-y-3 p-4 bg-gray-50 rounded border" >
457+ { authFile . resDto . detail . companyName && (
458+ < div >
459+ < span className = "text-xs text-gray-500" > 회사명</ span >
460+ < div className = "text-sm font-medium text-gray-800 mt-1" >
461+ { authFile . resDto . detail . companyName }
462+ </ div >
463+ </ div >
464+ ) }
465+ { authFile . resDto . detail . businessClassification && (
466+ < div >
467+ < span className = "text-xs text-gray-500" > 사업자 분류</ span >
468+ < div className = "text-sm font-medium text-gray-800 mt-1" >
469+ { authFile . resDto . detail . businessClassification }
470+ </ div >
471+ </ div >
472+ ) }
473+ { authFile . resDto . detail . businessRegistrationNumber && (
474+ < div >
475+ < span className = "text-xs text-gray-500" > 사업자 등록번호</ span >
476+ < div className = "text-sm font-medium text-gray-800 mt-1" >
477+ { authFile . resDto . detail . businessRegistrationNumber }
478+ </ div >
479+ </ div >
480+ ) }
481+ { authFile . resDto . detail . businessStatus && (
482+ < div >
483+ < span className = "text-xs text-gray-500" > 업태</ span >
484+ < div className = "text-sm font-medium text-gray-800 mt-1" >
485+ { authFile . resDto . detail . businessStatus }
486+ </ div >
487+ </ div >
488+ ) }
489+ { authFile . resDto . detail . zipCode && (
490+ < div >
491+ < span className = "text-xs text-gray-500" > 우편번호</ span >
492+ < div className = "text-sm font-medium text-gray-800 mt-1" >
493+ { authFile . resDto . detail . zipCode }
494+ </ div >
495+ </ div >
496+ ) }
497+ { authFile . resDto . detail . roadNameAddress && (
498+ < div >
499+ < span className = "text-xs text-gray-500" > 도로명 주소</ span >
500+ < div className = "text-sm font-medium text-gray-800 mt-1" >
501+ { authFile . resDto . detail . roadNameAddress }
502+ </ div >
503+ </ div >
504+ ) }
505+ { authFile . resDto . detail . detailedAddress && (
506+ < div >
507+ < span className = "text-xs text-gray-500" > 상세 주소</ span >
508+ < div className = "text-sm font-medium text-gray-800 mt-1" >
509+ { authFile . resDto . detail . detailedAddress }
510+ </ div >
511+ </ div >
512+ ) }
513+ </ div >
514+ </ div >
515+ ) }
516+ </ div >
517+ ) : (
518+ < div className = "text-gray-500 py-8 text-center" >
519+ 인증 정보를 불러올 수 없습니다.
520+ </ div >
521+ ) }
522+ </ div >
348523 </ div >
349524 </ div >
350525 </ div >
0 commit comments