1- import { useState , useEffect , useRef , useCallback } from 'react' ;
1+ import { useState , useEffect , useRef } from 'react' ;
22import { getClubMembers , deleteClubMember , addAsset , getAssets , updateAsset , deleteAsset , getMyClubs , uploadExcelAssets , getAssetStatistics , getAssetPictures , addAssetPicture , setMainPicture , deleteAssetPicture , getPictureUrl , getSchedules , type ClubMember , type Asset , type AssetStatistics , type AssetPicture , type Schedule } from '@/api/client' ;
33import '@/styles/App.css' ;
44import '@/styles/AdminDashboard.css' ;
@@ -11,8 +11,6 @@ type TabType = 'assets' | 'rentals' | 'members';
1111interface LazyAssetCardProps {
1212 asset : Asset ;
1313 isExpanded : boolean ;
14- mainPictureId : number | null | undefined ;
15- onLoadPicture : ( assetId : number ) => void ;
1614 onClick : ( ) => void ;
1715 editingAsset : { name : string ; description : string ; quantity : number ; location : string ; max_rental_days : number | null } | null ;
1816 statsLoading : boolean ;
@@ -21,52 +19,17 @@ interface LazyAssetCardProps {
2119 onEditClick : ( ) => void ;
2220}
2321
24- function LazyAssetCard ( { asset, isExpanded, mainPictureId, onLoadPicture, onClick, editingAsset, statsLoading, statsError, assetStats, onEditClick } : LazyAssetCardProps ) {
25- const cardRef = useRef < HTMLDivElement > ( null ) ;
26- const [ hasLoaded , setHasLoaded ] = useState ( false ) ;
27-
28- useEffect ( ( ) => {
29- const node = cardRef . current ;
30- if ( ! node ) return ;
31-
32- if ( ! node ) return ;
33- const observer = new IntersectionObserver (
34- ( entries ) => {
35- entries . forEach ( ( entry ) => {
36- if ( entry . isIntersecting && ! hasLoaded ) {
37- setHasLoaded ( true ) ;
38- onLoadPicture ( asset . id ) ;
39- }
40- } ) ;
41- } ,
42- { rootMargin : '100px' } // 100px 전에 미리 로드
43- ) ;
44-
45- if ( cardRef . current ) {
46- observer . observe ( cardRef . current ) ;
47- }
48-
49- return ( ) => {
50- if ( node ) {
51- observer . unobserve ( node ) ;
52- }
53- observer . disconnect ( ) ;
54- } ;
55- } , [ asset . id , hasLoaded , onLoadPicture ] ) ;
56-
22+ function LazyAssetCard ( { asset, isExpanded, onClick, editingAsset, statsLoading, statsError, assetStats, onEditClick } : LazyAssetCardProps ) {
5723 return (
58- < div
59- ref = { cardRef }
60- className = { `asset-card ${ isExpanded ? 'expanded' : '' } ` }
61- onClick = { onClick }
62- >
24+ < div className = { `asset-card ${ isExpanded ? 'expanded' : '' } ` } onClick = { onClick } >
6325 < div className = "asset-card-header" >
6426 < div className = "asset-image" >
65- { mainPictureId ? (
27+ { asset . main_picture ? (
6628 < img
67- src = { getPictureUrl ( mainPictureId ) }
29+ src = { getPictureUrl ( asset . main_picture ) }
6830 alt = { asset . name }
6931 className = "asset-main-picture"
32+ loading = "lazy"
7033 />
7134 ) : (
7235 < div className = "asset-image-placeholder" > 📦</ div >
@@ -202,9 +165,6 @@ export function AdminDashboardPage() {
202165 const [ uploadingPicture , setUploadingPicture ] = useState ( false ) ;
203166 const pictureInputRef = useRef < HTMLInputElement > ( null ) ;
204167
205- // 각 자산의 대표 사진 ID 저장 (assetId -> pictureId)
206- const [ assetMainPictures , setAssetMainPictures ] = useState < Record < number , number | null > > ( { } ) ;
207-
208168 // 동아리 멤버 상태
209169 const [ clubMembers , setClubMembers ] = useState < ClubMember [ ] > ( [ ] ) ;
210170 const [ membersLoading , setMembersLoading ] = useState ( true ) ;
@@ -233,30 +193,6 @@ export function AdminDashboardPage() {
233193 setAssetsLoading ( false ) ;
234194 } ;
235195
236- // 개별 자산의 대표 사진 로드 (Intersection Observer용)
237- const loadAssetMainPicture = useCallback ( async ( assetId : number ) => {
238- let shouldFetch = false ;
239-
240- // 이미 로드했거나 로딩 중이면 스킵, 아니면 로딩 중 표시 (null로 설정)
241- setAssetMainPictures ( prev => {
242- if ( prev [ assetId ] !== undefined ) {
243- return prev ;
244- }
245- shouldFetch = true ;
246- return { ...prev , [ assetId ] : null } ;
247- } ) ;
248-
249- if ( ! shouldFetch ) {
250- return ;
251- }
252-
253- const picturesResult = await getAssetPictures ( assetId ) ;
254- if ( picturesResult . success && picturesResult . data ) {
255- const mainPic = picturesResult . data . find ( p => p . is_main ) ;
256- setAssetMainPictures ( prev => ( { ...prev , [ assetId ] : mainPic ? mainPic . id : null } ) ) ;
257- }
258- } , [ ] ) ;
259-
260196 // 대여 현황 가져오기 함수
261197 const fetchSchedules = async ( clubId : number , status ?: string ) => {
262198 setSchedulesLoading ( true ) ;
@@ -705,94 +641,69 @@ export function AdminDashboardPage() {
705641 } ) ;
706642 } ;
707643
644+ const refreshAssetsList = async ( ) => {
645+ if ( myClubId ) {
646+ const result = await getAssets ( myClubId ) ;
647+ if ( result . success && result . data ) {
648+ setAssets ( result . data ) ;
649+ }
650+ }
651+ } ;
652+
708653 // 사진 업로드 핸들러
709654 const handlePictureUpload = async ( e : React . ChangeEvent < HTMLInputElement > ) => {
710655 const file = e . target . files ?. [ 0 ] ;
711656 if ( ! file || ! expandedAssetId ) return ;
712657
713- // 이미지 파일 검증
714- if ( ! file . type . startsWith ( 'image/' ) ) {
715- setError ( '이미지 파일만 업로드 가능합니다.' ) ;
716- return ;
717- }
718-
719658 setUploadingPicture ( true ) ;
720-
721659 try {
722- // 이미지 압축 (500KB 이상인 경우에만)
723660 let uploadFile = file ;
724- if ( file . size > 500 * 1024 ) {
725- uploadFile = await compressImage ( file ) ;
726- }
661+ if ( file . size > 500 * 1024 ) uploadFile = await compressImage ( file ) ;
727662
728- const isMain = assetPictures . length === 0 ; // 첫 번째 사진은 자동으로 대표 설정
663+ const isMain = assetPictures . length === 0 ;
729664 const result = await addAssetPicture ( expandedAssetId , uploadFile , isMain ) ;
730665
731666 if ( result . success ) {
732667 // 사진 목록 새로고침
733668 const picturesResult = await getAssetPictures ( expandedAssetId ) ;
734669 if ( picturesResult . success && picturesResult . data ) {
735670 setAssetPictures ( picturesResult . data ) ;
736-
737- const newMain = picturesResult . data . find ( p => p . is_main ) || picturesResult . data [ 0 ] ;
738- setAssetMainPictures ( prev => ( {
739- ...prev ,
740- [ expandedAssetId ] : newMain ? newMain . id : null
741- } ) ) ;
742671 }
672+ // 4. 수정: assetMainPictures 업데이트 대신 전체 자산 목록 갱신
673+ refreshAssetsList ( ) ;
743674 } else {
744675 setError ( result . error || '사진 업로드에 실패했습니다.' ) ;
745676 }
746677 } catch ( err ) {
747- console . error ( 'Image compression error:' , err ) ;
678+ console . error ( 'Picture upload error:' , err ) ;
748679 setError ( '이미지 처리 중 오류가 발생했습니다.' ) ;
749680 }
750-
751681 setUploadingPicture ( false ) ;
752-
753- // input 초기화
754- if ( pictureInputRef . current ) {
755- pictureInputRef . current . value = '' ;
756- }
757682 } ;
758683
759- // 대표 사진 설정 핸들러
760684 const handleSetMainPicture = async ( pictureId : number ) => {
761685 if ( ! expandedAssetId ) return ;
762-
763686 const result = await setMainPicture ( expandedAssetId , pictureId ) ;
764687 if ( result . success ) {
765- // 사진 목록 새로고침
766688 const picturesResult = await getAssetPictures ( expandedAssetId ) ;
767689 if ( picturesResult . success && picturesResult . data ) {
768690 setAssetPictures ( picturesResult . data ) ;
769-
770- setAssetMainPictures ( prev => ( {
771- ...prev ,
772- [ expandedAssetId ] : pictureId
773- } ) ) ;
774691 }
692+ // 5. 수정: 대표 사진 설정 후 목록 갱신
693+ refreshAssetsList ( ) ;
775694 } else {
776695 setError ( result . error || '대표 사진 설정에 실패했습니다.' ) ;
777696 }
778697 } ;
779698
780- // 사진 삭제 핸들러
781699 const handleDeletePicture = async ( pictureId : number ) => {
782700 if ( ! expandedAssetId ) return ;
783701 if ( ! confirm ( '이 사진을 삭제하시겠습니까?' ) ) return ;
784-
785702 const result = await deleteAssetPicture ( expandedAssetId , pictureId ) ;
786703 if ( result . success ) {
787704 setAssetPictures ( prev => prev . filter ( p => p . id !== pictureId ) ) ;
788-
789- setAssetMainPictures ( prev => {
790- if ( prev [ expandedAssetId ] === pictureId ) {
791- return { ...prev , [ expandedAssetId ] : null } ;
792- } else {
793- return prev ;
794- }
795- } ) ;
705+ // 6. 수정: 사진 삭제 후 목록 갱신 (삭제된 사진이 대표였을 수 있으므로)
706+ refreshAssetsList ( ) ;
796707 } else {
797708 setError ( result . error || '사진 삭제에 실패했습니다.' ) ;
798709 }
@@ -1086,8 +997,6 @@ export function AdminDashboardPage() {
1086997 key = { asset . id }
1087998 asset = { asset }
1088999 isExpanded = { expandedAssetId === asset . id }
1089- mainPictureId = { assetMainPictures [ asset . id ] }
1090- onLoadPicture = { loadAssetMainPicture }
10911000 onClick = { ( ) => handleAssetClick ( asset ) }
10921001 editingAsset = { editingAsset }
10931002 statsLoading = { statsLoading }
0 commit comments