@@ -219,54 +219,348 @@ const DraftTrackerContent = () => {
219219 } , [ players , currentDraftPick , numTeams , rosterSettings , autoDraftSettings , teamVariability ,
220220 teamNames , draftStyle , isKeeperMode ] ) ;
221221
222- // CSV parsing - now creates unified player objects with stable IDs
223- const parseCSV = ( csvText ) => {
222+ // Team mapping utilities for CSVs without team information
223+ const normalizePlayerName = ( name ) => {
224+ return name
225+ . toLowerCase ( )
226+ . replace ( / [ ^ a - z 0 - 9 \s ] / g, '' ) // Remove special characters
227+ . replace ( / \s + / g, ' ' ) // Normalize whitespace
228+ . trim ( )
229+ . replace ( / \s / g, '' ) ; // Remove all spaces for exact matching
230+ } ;
231+
232+ const fuzzyMatchPlayerName = ( targetName , candidateName , threshold = 0.8 ) => {
233+ const target = normalizePlayerName ( targetName ) ;
234+ const candidate = normalizePlayerName ( candidateName ) ;
235+
236+ // Exact match
237+ if ( target === candidate ) return 1.0 ;
238+
239+ // Check if one contains the other
240+ if ( target . includes ( candidate ) || candidate . includes ( target ) ) {
241+ return 0.9 ;
242+ }
243+
244+ // Simple similarity check
245+ const longer = target . length > candidate . length ? target : candidate ;
246+ const shorter = target . length > candidate . length ? candidate : target ;
247+
248+ if ( longer . length === 0 ) return 1.0 ;
249+
250+ let matches = 0 ;
251+ for ( let i = 0 ; i < shorter . length ; i ++ ) {
252+ if ( longer . includes ( shorter [ i ] ) ) matches ++ ;
253+ }
254+
255+ const similarity = matches / longer . length ;
256+ return similarity >= threshold ? similarity : 0 ;
257+ } ;
258+
259+ const createTeamMappingFromPreloadedCSVs = async ( ) => {
260+ const csvFiles = [
261+ 'FantasyPros 2025 PPR.csv' ,
262+ '4for4 Underdog ADP.csv' ,
263+ 'BB10s ADP.csv' ,
264+ 'CBS ADP.csv' ,
265+ 'ESPN ADP.csv' ,
266+ 'FFPC ADP.csv' ,
267+ 'Y! ADP.csv' ,
268+ 'FantasyPros .5 PPR.csv' ,
269+ 'FantasyPros 2025 Top 10 Accurate Overall PPR.csv' ,
270+ 'FantasyNow+ PPR.csv' ,
271+ 'The Fantasy Headliners PPR.csv'
272+ ] ;
273+
274+ const teamMapping = new Map ( ) ;
275+ let filesProcessed = 0 ;
276+
277+ console . log ( '🔍 Building team mapping from preloaded CSV files...' ) ;
278+
279+ // Column patterns for reference files
280+ const COLUMN_PATTERNS = {
281+ name : [
282+ 'name' , 'player' , 'playername' , 'player_name' , 'player name' , 'full_name' , 'fullname' ,
283+ 'full name' , 'lastname' , 'last_name' , 'first_name' , 'firstname'
284+ ] ,
285+ team : [
286+ 'team' , 'tm' , 'nfl_team' , 'nfl team' , 'franchise' , 'club' , 'organization' ,
287+ 'team_abbr' , 'team abbr' , 'team_abbreviation' , 'team abbreviation' , 'teamname' , 'team_name'
288+ ]
289+ } ;
290+
291+ const findColumnIndex = ( headers , patterns ) => {
292+ const normalizedHeaders = headers . map ( h => h . trim ( ) . toLowerCase ( ) ) ;
293+
294+ for ( const pattern of patterns ) {
295+ const index = normalizedHeaders . findIndex ( h =>
296+ h === pattern . toLowerCase ( ) ||
297+ h . includes ( pattern . toLowerCase ( ) ) ||
298+ pattern . toLowerCase ( ) . includes ( h )
299+ ) ;
300+ if ( index !== - 1 ) {
301+ return index ;
302+ }
303+ }
304+ return - 1 ;
305+ } ;
306+
307+ for ( const filename of csvFiles ) {
308+ try {
309+ const response = await fetch ( `/${ filename } ` ) ;
310+ if ( ! response . ok ) continue ;
311+
312+ const csvText = await response . text ( ) ;
313+ const lines = csvText . trim ( ) . split ( '\n' ) ;
314+ const headers = lines [ 0 ] . split ( ',' ) . map ( h => h . trim ( ) ) ;
315+
316+ const nameIndex = findColumnIndex ( headers , COLUMN_PATTERNS . name ) ;
317+ const teamIndex = findColumnIndex ( headers , COLUMN_PATTERNS . team ) ;
318+
319+ if ( nameIndex === - 1 || teamIndex === - 1 ) {
320+ console . log ( `⚠️ Skipping ${ filename } : missing name or team columns` ) ;
321+ continue ;
322+ }
323+
324+ // Process each player in this reference file
325+ lines . slice ( 1 ) . forEach ( line => {
326+ const values = line . split ( ',' ) . map ( v => v . trim ( ) ) ;
327+ const playerName = values [ nameIndex ] ;
328+ const playerTeam = values [ teamIndex ] ;
329+
330+ if ( playerName && playerTeam ) {
331+ const normalizedName = normalizePlayerName ( playerName ) ;
332+ if ( ! teamMapping . has ( normalizedName ) ) {
333+ teamMapping . set ( normalizedName , playerTeam . toUpperCase ( ) ) ;
334+ }
335+ }
336+ } ) ;
337+
338+ filesProcessed ++ ;
339+ console . log ( `✅ Processed ${ filename } for team mapping` ) ;
340+
341+ } catch ( error ) {
342+ console . log ( `❌ Failed to process ${ filename } :` , error ) ;
343+ }
344+ }
345+
346+ console . log ( `📊 Team mapping complete: ${ teamMapping . size } players from ${ filesProcessed } files` ) ;
347+ return teamMapping ;
348+ } ;
349+
350+ const applyTeamMapping = async ( playersWithoutTeams , sourceFilename = '' ) => {
351+ if ( playersWithoutTeams . length === 0 ) {
352+ console . log ( '✅ No players need team mapping' ) ;
353+ return { mappedCount : 0 , unmappedPlayers : [ ] } ;
354+ }
355+
356+ console . log ( `🔄 Attempting to map teams for ${ playersWithoutTeams . length } players...` ) ;
357+
358+ const teamMapping = await createTeamMappingFromPreloadedCSVs ( ) ;
359+
360+ if ( teamMapping . size === 0 ) {
361+ console . log ( '❌ No team mapping data available from preloaded CSVs' ) ;
362+ return { mappedCount : 0 , unmappedPlayers : playersWithoutTeams } ;
363+ }
364+
365+ let mappedCount = 0 ;
366+ const unmappedPlayers = [ ] ;
367+
368+ for ( const player of playersWithoutTeams ) {
369+ let bestMatch = null ;
370+ let bestScore = 0 ;
371+
372+ // Try to find the best match in our team mapping
373+ for ( const [ mappedName , team ] of teamMapping . entries ( ) ) {
374+ const score = fuzzyMatchPlayerName ( player . name , mappedName ) ;
375+ if ( score > bestScore && score >= 0.8 ) {
376+ bestScore = score ;
377+ bestMatch = team ;
378+ }
379+ }
380+
381+ if ( bestMatch ) {
382+ player . team = bestMatch ;
383+ mappedCount ++ ;
384+ console . log ( `✅ Mapped ${ player . name } → ${ bestMatch } (confidence: ${ ( bestScore * 100 ) . toFixed ( 1 ) } %)` ) ;
385+ } else {
386+ unmappedPlayers . push ( player ) ;
387+ console . log ( `❌ No team found for ${ player . name } ` ) ;
388+ }
389+ }
390+
391+ console . log ( `📊 Team mapping results: ${ mappedCount } mapped, ${ unmappedPlayers . length } unmapped` ) ;
392+
393+ return { mappedCount, unmappedPlayers } ;
394+ } ;
395+
396+ // Enhanced CSV parsing with flexible column detection and team mapping
397+ const parseCSV = async ( csvText , filename = '' ) => {
224398 const lines = csvText . trim ( ) . split ( '\n' ) ;
225- const headers = lines [ 0 ] . split ( ',' ) . map ( h => h . trim ( ) . toLowerCase ( ) ) ;
399+ const headers = lines [ 0 ] . split ( ',' ) . map ( h => h . trim ( ) ) ;
400+
401+ console . log ( '🔍 Parsing CSV with enhanced detection...' ) ;
402+ console . log ( '📊 Headers found:' , headers ) ;
403+
404+ // Column patterns for flexible matching (case insensitive)
405+ const COLUMN_PATTERNS = {
406+ name : [
407+ 'name' , 'player' , 'playername' , 'player_name' , 'player name' , 'full_name' , 'fullname' ,
408+ 'full name' , 'lastname' , 'last_name' , 'first_name' , 'firstname'
409+ ] ,
410+ position : [
411+ 'position' , 'pos' , 'positions' , 'player_position' , 'player position' , 'eligibility' ,
412+ 'eligible_positions' , 'eligible positions' , 'fantasy_position' , 'fantasy position'
413+ ] ,
414+ team : [
415+ 'team' , 'tm' , 'nfl_team' , 'nfl team' , 'franchise' , 'club' , 'organization' ,
416+ 'team_abbr' , 'team abbr' , 'team_abbreviation' , 'team abbreviation' , 'teamname' , 'team_name'
417+ ] ,
418+ rank : [
419+ 'rank' , 'ranking' , 'overall' , 'overall_rank' , 'overall rank' , 'player_rank' , 'player rank' ,
420+ 'draft_rank' , 'draft rank' , 'fantasy_rank' , 'fantasy rank' , 'ecr' , 'consensus_rank' ,
421+ 'consensus rank' , 'expert_consensus_rank' , 'expert consensus rank' , 'avg_rank' , 'avg rank' ,
422+ 'average_rank' , 'average rank' , 'rk' , 'rnk' , 'position_rank' , 'positional_rank'
423+ ] ,
424+ tier : [
425+ 'tier' , 'tiers' , 'draft_tier' , 'draft tier' , 'fantasy_tier' , 'fantasy tier' ,
426+ 'tier_rank' , 'tier rank' , 'player_tier' , 'player tier'
427+ ]
428+ } ;
429+
430+ // Find column index using flexible patterns
431+ const findColumnIndex = ( headers , patterns ) => {
432+ const normalizedHeaders = headers . map ( h => h . trim ( ) . toLowerCase ( ) ) ;
433+
434+ for ( const pattern of patterns ) {
435+ const index = normalizedHeaders . findIndex ( h =>
436+ h === pattern . toLowerCase ( ) ||
437+ h . includes ( pattern . toLowerCase ( ) ) ||
438+ pattern . toLowerCase ( ) . includes ( h )
439+ ) ;
440+ if ( index !== - 1 ) {
441+ console . log ( `✅ Found ${ pattern } column at index ${ index } : "${ headers [ index ] } "` ) ;
442+ return index ;
443+ }
444+ }
445+ return - 1 ;
446+ } ;
226447
227- const nameIndex = headers . findIndex ( h => h . includes ( 'name' ) ) ;
228- const positionIndex = headers . findIndex ( h => h . includes ( 'position' ) || h . includes ( 'pos' ) ) ;
229- const teamIndex = headers . findIndex ( h => h . includes ( 'team' ) ) ;
230- const rankIndex = headers . findIndex ( h => h . includes ( 'rank' ) ) ;
231- const tierIndex = headers . findIndex ( h => h . includes ( 'tier' ) ) ;
448+ const nameIndex = findColumnIndex ( headers , COLUMN_PATTERNS . name ) ;
449+ const positionIndex = findColumnIndex ( headers , COLUMN_PATTERNS . position ) ;
450+ const teamIndex = findColumnIndex ( headers , COLUMN_PATTERNS . team ) ;
451+ const rankIndex = findColumnIndex ( headers , COLUMN_PATTERNS . rank ) ;
452+ const tierIndex = findColumnIndex ( headers , COLUMN_PATTERNS . tier ) ;
453+
454+ console . log ( '📍 Column mapping results:' , {
455+ name : nameIndex >= 0 ? `"${ headers [ nameIndex ] } "` : 'NOT FOUND' ,
456+ position : positionIndex >= 0 ? `"${ headers [ positionIndex ] } "` : 'NOT FOUND' ,
457+ team : teamIndex >= 0 ? `"${ headers [ teamIndex ] } "` : 'NOT FOUND' ,
458+ rank : rankIndex >= 0 ? `"${ headers [ rankIndex ] } "` : 'NOT FOUND' ,
459+ tier : tierIndex >= 0 ? `"${ headers [ tierIndex ] } "` : 'NOT FOUND'
460+ } ) ;
232461
233- if ( nameIndex === - 1 || positionIndex === - 1 || rankIndex === - 1 ) {
234- throw new Error ( 'CSV must contain name, position, and rank columns' ) ;
462+ if ( nameIndex === - 1 ) {
463+ throw new Error ( 'CSV must contain a player name column. Expected headers like: name, player, playername, player_name, full_name, etc.' ) ;
464+ }
465+ if ( positionIndex === - 1 ) {
466+ throw new Error ( 'CSV must contain a position column. Expected headers like: position, pos, eligibility, fantasy_position, etc.' ) ;
467+ }
468+ if ( rankIndex === - 1 ) {
469+ throw new Error ( 'CSV must contain a rank column. Expected headers like: rank, overall, player_rank, draft_rank, ecr, etc.' ) ;
235470 }
236471
472+ const hasTeamInfo = teamIndex !== - 1 ;
473+ console . log ( hasTeamInfo ? '✅ Team information found in CSV' : '⚠️ No team information found - will attempt automatic mapping' ) ;
474+
237475 const playersObj = { } ;
476+ const playersWithoutTeams = [ ] ;
477+
238478 lines . slice ( 1 ) . forEach ( ( line , index ) => {
239479 const values = line . split ( ',' ) . map ( v => v . trim ( ) ) ;
240480 const playerName = values [ nameIndex ] || '' ;
241481 const playerPosition = values [ positionIndex ] || '' ;
242- const playerTeam = teamIndex !== - 1 ? values [ teamIndex ] || '' : '' ;
482+ const playerTeam = hasTeamInfo ? ( values [ teamIndex ] || '' ) : '' ;
483+ const playerRank = parseInt ( values [ rankIndex ] ) || index + 1 ;
484+ const playerTier = tierIndex !== - 1 ? ( parseInt ( values [ tierIndex ] ) || null ) : null ;
243485
244- // Create stable ID based on name, position, and team to avoid shifting
486+ if ( ! playerName || ! playerPosition ) {
487+ console . warn ( `⚠️ Skipping row ${ index + 2 } : missing name or position` ) ;
488+ return ;
489+ }
490+
491+ // Create initial stable ID
245492 const playerId = `${ playerName . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 ] / g, '' ) } _${ playerPosition . toLowerCase ( ) } _${ playerTeam . toLowerCase ( ) } ` . substring ( 0 , 50 ) ;
246493
247- playersObj [ playerId ] = {
494+ const playerObj = {
248495 id : playerId ,
249496 name : playerName ,
250- position : playerPosition ,
251- team : playerTeam ,
252- rank : parseInt ( values [ rankIndex ] ) || index + 1 ,
253- tier : tierIndex !== - 1 ? parseInt ( values [ tierIndex ] ) || null : null ,
254- status : 'available' , // 'available', 'drafted', 'keeper'
255- draftInfo : null , // { teamId, pickNumber, round, isKeeper }
256- watchStatus : null // 'watched', 'avoided', null
497+ position : playerPosition . toUpperCase ( ) ,
498+ team : playerTeam . toUpperCase ( ) ,
499+ rank : playerRank ,
500+ tier : playerTier ,
501+ status : 'available' ,
502+ draftInfo : null ,
503+ watchStatus : null
257504 } ;
505+
506+ playersObj [ playerId ] = playerObj ;
507+
508+ // Track players without teams for mapping
509+ if ( ! hasTeamInfo || ! playerTeam ) {
510+ playersWithoutTeams . push ( playerObj ) ;
511+ }
258512 } ) ;
259513
514+ // If we have players without teams, try to map them
515+ if ( playersWithoutTeams . length > 0 ) {
516+ console . log ( `🔄 Attempting to map teams for ${ playersWithoutTeams . length } players...` ) ;
517+
518+ try {
519+ const mappingResult = await applyTeamMapping ( playersWithoutTeams , filename ) ;
520+
521+ if ( mappingResult . mappedCount > 0 ) {
522+ console . log ( `✅ Successfully mapped ${ mappingResult . mappedCount } players to teams` ) ;
523+
524+ // Update the players object with mapped teams and new IDs
525+ playersWithoutTeams . forEach ( player => {
526+ // Remove old ID
527+ delete playersObj [ player . id ] ;
528+
529+ // Create new ID with team info
530+ const newId = `${ player . name . toLowerCase ( ) . replace ( / [ ^ a - z 0 - 9 ] / g, '' ) } _${ player . position . toLowerCase ( ) } _${ player . team . toLowerCase ( ) } ` . substring ( 0 , 50 ) ;
531+ player . id = newId ;
532+ playersObj [ newId ] = player ;
533+ } ) ;
534+
535+ // Show user-friendly notification about team mapping
536+ const mappedPercentage = Math . round ( ( mappingResult . mappedCount / playersWithoutTeams . length ) * 100 ) ;
537+ console . log ( `🎯 Team mapping summary: ${ mappingResult . mappedCount } /${ playersWithoutTeams . length } players (${ mappedPercentage } %) successfully mapped to teams` ) ;
538+ }
539+
540+ if ( mappingResult . unmappedPlayers . length > 0 ) {
541+ console . log ( `⚠️ ${ mappingResult . unmappedPlayers . length } players still without teams - they will show as blank teams` ) ;
542+ }
543+ } catch ( error ) {
544+ console . error ( '❌ Team mapping failed:' , error ) ;
545+ console . log ( '📄 Continuing with original data (players without teams will have blank team fields)' ) ;
546+ }
547+ }
548+
549+ console . log ( `📊 CSV parsing complete: ${ Object . keys ( playersObj ) . length } players loaded from "${ filename } "` ) ;
260550 return playersObj ;
261551 } ;
262552
263- // File upload handlers
553+ // File upload handlers - FIXED to properly handle async parseCSV
264554 const handleFileUpload = ( file , isSwitch = false ) => {
265555 if ( file ?. type === 'text/csv' ) {
266556 const reader = new FileReader ( ) ;
267- reader . onload = ( e ) => {
557+ reader . onload = async ( e ) => {
268558 try {
269- const parsedPlayers = parseCSV ( e . target . result ) ;
559+ console . log ( '📁 Processing uploaded CSV file:' , file . name ) ;
560+ const parsedPlayers = await parseCSV ( e . target . result , file . name ) ;
561+ console . log ( '✅ CSV parsed successfully, players object:' , parsedPlayers ) ;
562+ console . log ( '🔢 Number of players parsed:' , Object . keys ( parsedPlayers ) . length ) ;
563+
270564 setPlayers ( parsedPlayers ) ;
271565 setCurrentCSVSource ( file . name ) ;
272566
@@ -277,7 +571,10 @@ const DraftTrackerContent = () => {
277571 setIsKeeperMode ( false ) ;
278572 clearDraftState ( ) ;
279573 }
574+
575+ console . log ( '🎯 Players state updated, UI should now show draft interface' ) ;
280576 } catch ( error ) {
577+ console . error ( '❌ CSV parsing error:' , error ) ;
281578 alert ( 'Error parsing CSV: ' + error . message ) ;
282579 }
283580 } ;
0 commit comments