AnayShukla commited on
Commit
cbb573e
·
1 Parent(s): 4c4af79
frontend/src/components/PlayerModals.jsx CHANGED
@@ -51,9 +51,9 @@ export const PlayerEditModal = ({
51
  };
52
 
53
  return (
54
- <div className="fixed inset-0 z-[150] flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
55
- <div className="bg-slate-950 border border-slate-800 w-full max-w-2xl rounded-2xl shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200 flex flex-col">
56
- <div className="bg-slate-900 p-5 flex justify-between items-center border-b border-slate-800">
57
  <div className="flex flex-col">
58
  <h3 className="font-black text-2xl text-slate-100 uppercase tracking-tight">{livePlayer.Name}</h3>
59
  <div className="flex gap-3 text-sm font-bold text-slate-500">
@@ -64,7 +64,7 @@ export const PlayerEditModal = ({
64
  </div>
65
  <button onClick={() => setSelectedPlayer(null)} className="text-slate-500 hover:text-white transition-colors bg-slate-900 p-2 rounded-full border border-slate-800">✕</button>
66
  </div>
67
- <div className="p-6 flex flex-col gap-6">
68
  <div className="flex gap-4">
69
  {[
70
  { label: `GW${activeGW} xG`, val: livePlayer[`${activeGW}_xG`] ?? livePlayer.xG ?? "-" },
@@ -206,6 +206,8 @@ export const PlayerSearchModal = ({
206
  itb,
207
  handleAddPlayer,
208
  }) => {
 
 
209
  return (
210
  <div className="fixed inset-0 z-[150] flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
211
  <div className="bg-slate-950 border border-slate-800 w-full max-w-lg rounded-2xl shadow-2xl overflow-hidden flex flex-col animate-in zoom-in-95 duration-200">
@@ -238,10 +240,11 @@ export const PlayerSearchModal = ({
238
  </button>
239
  </div>
240
 
241
- <div className="max-h-[400px] overflow-y-auto p-2">
 
242
  {globalPlayers
243
- // THE FIX: Removed the restrictive 'replacedPlayer' ban and added defensive FPL ID type-checking
244
- .filter((p) => !ownedPlayerIds.has(p.ID) && !ownedPlayerIds.has(String(p.ID)) && !ownedPlayerIds.has(Number(p.ID)) && String(p.ID) !== String(selectedPlayer.replacedPlayer?.ID) && p.Pos === selectedPlayer.Pos && p.Name.toLowerCase().includes(searchQuery.toLowerCase()))
245
  .sort((a, b) => {
246
  let valA = sortConfig.key === "ev" ? Number(a[`${activeGW}_Pts`] || 0) : getPlayerPrice(a);
247
  let valB = sortConfig.key === "ev" ? Number(b[`${activeGW}_Pts`] || 0) : getPlayerPrice(b);
@@ -251,7 +254,6 @@ export const PlayerSearchModal = ({
251
  })
252
  .slice(0, 50)
253
  .map((p) => {
254
- // THE FIX: Your true FPL purchasing power includes the money freed up by selling the outgoing player
255
  const sellingPrice = getPlayerPrice(selectedPlayer) || 0;
256
  const maxBudget = itb + sellingPrice;
257
  const cost = getPlayerPrice(p);
@@ -262,19 +264,21 @@ export const PlayerSearchModal = ({
262
  key={p.ID}
263
  disabled={!isAffordable}
264
  onClick={() => handleAddPlayer(p)}
265
- className={`w-full flex items-center justify-between p-3 border-b border-slate-800/30 transition-colors group ${isAffordable ? "hover:bg-slate-900 cursor-pointer" : "opacity-40 cursor-not-allowed"}`}
 
266
  >
267
  <div className="flex flex-col items-start text-left">
268
- <span className="font-bold text-slate-200 text-sm">{p.Name}</span>
269
- <span className="text-[10px] text-slate-500 font-bold uppercase tracking-wider">{p.Team} • {p.Pos}</span>
 
270
  </div>
271
- <div className="flex items-center gap-4 text-right">
272
  <div className="flex flex-col items-end">
273
- <span className="text-xs font-mono text-emerald-400 font-bold">EV: {Number(p[`${activeGW}_Pts`] || 0).toFixed(2)}</span>
274
- <span className="text-[10px] font-mono text-slate-400">{p[`${activeGW}_xMins`] || 0} xMins</span>
275
  </div>
276
- <span className={`text-sm font-mono font-bold ${isAffordable ? "text-slate-300" : "text-red-400"}`}>£{cost.toFixed(1)}m</span>
277
- <Plus className={`transition-colors ${isAffordable ? "text-slate-600 group-hover:text-luigi-400" : "text-slate-800"}`} size={18} />
278
  </div>
279
  </button>
280
  );
 
51
  };
52
 
53
  return (
54
+ <div className="fixed inset-0 z-[150] flex items-center justify-center bg-black/80 backdrop-blur-sm p-2 sm:p-4">
55
+ <div className="bg-slate-950 border border-slate-800 w-full max-w-2xl max-h-[90vh] sm:max-h-none overflow-y-auto sm:overflow-visible rounded-xl sm:rounded-2xl shadow-2xl animate-in zoom-in-95 duration-200 flex flex-col">
56
+ <div className="bg-slate-900 p-4 sm:p-5 flex justify-between items-center border-b border-slate-800 sticky top-0 z-20">
57
  <div className="flex flex-col">
58
  <h3 className="font-black text-2xl text-slate-100 uppercase tracking-tight">{livePlayer.Name}</h3>
59
  <div className="flex gap-3 text-sm font-bold text-slate-500">
 
64
  </div>
65
  <button onClick={() => setSelectedPlayer(null)} className="text-slate-500 hover:text-white transition-colors bg-slate-900 p-2 rounded-full border border-slate-800">✕</button>
66
  </div>
67
+ <div className="p-3 sm:p-6 flex flex-col gap-4 sm:gap-6">
68
  <div className="flex gap-4">
69
  {[
70
  { label: `GW${activeGW} xG`, val: livePlayer[`${activeGW}_xG`] ?? livePlayer.xG ?? "-" },
 
206
  itb,
207
  handleAddPlayer,
208
  }) => {
209
+ const cleanString = (str) => str ? str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase() : "";
210
+ const cleanSearch = cleanString(searchQuery);
211
  return (
212
  <div className="fixed inset-0 z-[150] flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
213
  <div className="bg-slate-950 border border-slate-800 w-full max-w-lg rounded-2xl shadow-2xl overflow-hidden flex flex-col animate-in zoom-in-95 duration-200">
 
240
  </button>
241
  </div>
242
 
243
+ {/* THE FIX: Changed max-h to 50vh on mobile so it doesn't span the whole screen */}
244
+ <div className="max-h-[50vh] sm:max-h-[400px] overflow-y-auto p-1 sm:p-2 custom-scrollbar">
245
  {globalPlayers
246
+ // THE FIX: Apply cleanString to both the player name and the search query
247
+ .filter((p) => !ownedPlayerIds.has(p.ID) && !ownedPlayerIds.has(String(p.ID)) && !ownedPlayerIds.has(Number(p.ID)) && String(p.ID) !== String(selectedPlayer.replacedPlayer?.ID) && p.Pos === selectedPlayer.Pos && cleanString(p.Name).includes(cleanSearch))
248
  .sort((a, b) => {
249
  let valA = sortConfig.key === "ev" ? Number(a[`${activeGW}_Pts`] || 0) : getPlayerPrice(a);
250
  let valB = sortConfig.key === "ev" ? Number(b[`${activeGW}_Pts`] || 0) : getPlayerPrice(b);
 
254
  })
255
  .slice(0, 50)
256
  .map((p) => {
 
257
  const sellingPrice = getPlayerPrice(selectedPlayer) || 0;
258
  const maxBudget = itb + sellingPrice;
259
  const cost = getPlayerPrice(p);
 
264
  key={p.ID}
265
  disabled={!isAffordable}
266
  onClick={() => handleAddPlayer(p)}
267
+ // THE FIX: Shrunk padding on mobile (p-2 instead of p-3)
268
+ className={`w-full flex items-center justify-between p-2 sm:p-3 border-b border-slate-800/30 transition-colors group ${isAffordable ? "hover:bg-slate-900 cursor-pointer" : "opacity-40 cursor-not-allowed"}`}
269
  >
270
  <div className="flex flex-col items-start text-left">
271
+ {/* THE FIX: Shrunk font sizes on mobile */}
272
+ <span className="font-bold text-slate-200 text-xs sm:text-sm">{p.Name}</span>
273
+ <span className="text-[9px] sm:text-[10px] text-slate-500 font-bold uppercase tracking-wider">{p.Team} • {p.Pos}</span>
274
  </div>
275
+ <div className="flex items-center gap-2 sm:gap-4 text-right">
276
  <div className="flex flex-col items-end">
277
+ <span className="text-[10px] sm:text-xs font-mono text-emerald-400 font-bold">EV: {Number(p[`${activeGW}_Pts`] || 0).toFixed(2)}</span>
278
+ <span className="text-[8px] sm:text-[10px] font-mono text-slate-400">{p[`${activeGW}_xMins`] || 0} xMins</span>
279
  </div>
280
+ <span className={`text-xs sm:text-sm font-mono font-bold ${isAffordable ? "text-slate-300" : "text-red-400"}`}>£{cost.toFixed(1)}m</span>
281
+ <Plus className={`transition-colors ${isAffordable ? "text-slate-600 group-hover:text-luigi-400" : "text-slate-800"}`} size={16} />
282
  </div>
283
  </button>
284
  );
solver_engine.py CHANGED
@@ -6,8 +6,10 @@ def _norm_id_list(raw) -> list[int]:
6
  return []
7
  out = []
8
  for x in raw:
9
- if isinstance(x, dict) and "id" in x:
10
- out.append(int(x["id"]))
 
 
11
  else:
12
  out.append(int(x))
13
  return out
@@ -110,8 +112,12 @@ def prep_solver_data(payload_data: dict):
110
  for x in raw:
111
  if isinstance(x, list) and len(x) == 2:
112
  out.append((int(x[0]), int(x[1])))
 
 
 
 
113
  else:
114
- out.append((int(x), None))
115
  return out
116
 
117
  settings["banned_next_gw"] = norm_temporal_list(
 
6
  return []
7
  out = []
8
  for x in raw:
9
+ # THE FIX: Safely extract the ID from React's dictionary payload
10
+ if isinstance(x, dict):
11
+ pid = x.get("ID") or x.get("id", 0)
12
+ out.append(int(pid))
13
  else:
14
  out.append(int(x))
15
  return out
 
112
  for x in raw:
113
  if isinstance(x, list) and len(x) == 2:
114
  out.append((int(x[0]), int(x[1])))
115
+ elif isinstance(x, dict):
116
+ # THE FIX: Extract the ID and strictly bind it to the FIRST gameweek of the horizon!
117
+ pid = x.get("ID") or x.get("id", 0)
118
+ out.append((int(pid), horizon_gws[0]))
119
  else:
120
+ out.append((int(x), horizon_gws[0]))
121
  return out
122
 
123
  settings["banned_next_gw"] = norm_temporal_list(