Phase 22: mapper round 2, response shaper, card detail modal, market filters

- mapper.rs: 22 new exact routes (squad list, trophies, rivals, objectives
  sub-groups, catalogue, consumables, loan items, active messages, price
  tiers, fitness, customization) + 5 new prefix routes (squad GET/DELETE by
  ID, item chemistry PUT, SBC set GET); total exact routes now ≥55
- shaper.rs: shape /achievements → trophyData envelope; /squads → squads
  list envelope; /packs/store → purchasablePacks envelope; also shapes
  parameterised /squads/{id} responses
- dashboard: card detail modal (click any collection card → full stat view,
  chemistry/training/loan info, quick-sell action); loan-only checkbox in
  collection filters; position + min/max price filters in transfer market

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-25 18:28:43 -07:00
parent 72ffe9a806
commit 893ee369c2
3 changed files with 417 additions and 7 deletions
+64
View File
@@ -28,6 +28,9 @@ pub fn shape_response(core_path: &str, core_response: Value) -> Value {
"/sbc" => return shape_sbc_response(core_response),
"/fut-champs" => return shape_fut_champs_response(core_response),
"/chemistry-styles" => return shape_chemistry_styles_response(core_response),
"/achievements" => return shape_achievements_response(core_response),
"/squads" => return shape_squads_list_response(core_response),
"/packs/store" => return shape_pack_store_response(core_response),
_ => {}
}
@@ -41,6 +44,9 @@ pub fn shape_response(core_path: &str, core_response: Value) -> Value {
if core_path.starts_with("/fut-champs/") {
return shape_fut_champs_response(core_response);
}
if core_path.starts_with("/squads/") {
return shape_squads_list_response(core_response);
}
// Unknown — pass through unchanged
core_response
@@ -290,6 +296,40 @@ fn shape_chemistry_styles_response(core: Value) -> Value {
})
}
fn shape_achievements_response(core: Value) -> Value {
// FIFA 23 uses a "trophies" envelope with "trophyData" array
let achievements = core.get("achievements").cloned().unwrap_or(json!([]));
let earned = core.get("earned").and_then(|v| v.as_i64()).unwrap_or(0);
let total = core.get("total").and_then(|v| v.as_i64()).unwrap_or(0);
json!({
"trophyData": achievements,
"totalEarned": earned,
"totalAvailable": total,
"openfut": true,
})
}
fn shape_squads_list_response(core: Value) -> Value {
// Wrap squads in EA's squad list envelope
let squads = if core.is_array() {
core
} else {
core.get("squads").cloned().unwrap_or(json!([]))
};
json!({
"squads": squads,
"openfut": true,
})
}
fn shape_pack_store_response(core: Value) -> Value {
let packs = core.get("packs").cloned().unwrap_or(json!([]));
json!({
"purchasablePacks": packs,
"openfut": true,
})
}
#[cfg(test)]
mod tests {
use super::*;
@@ -328,4 +368,28 @@ mod tests {
assert!(shaped["auctionInfo"].as_array().is_some());
assert_eq!(shaped["total"], 1);
}
#[test]
fn test_shape_achievements_response() {
let core = json!({ "achievements": [{ "id": "first_match" }], "earned": 1, "total": 18 });
let shaped = shape_response("/achievements", core);
assert!(shaped["trophyData"].is_array());
assert_eq!(shaped["totalEarned"], 1);
assert_eq!(shaped["totalAvailable"], 18);
}
#[test]
fn test_shape_squads_list_response() {
let core = json!({ "squads": [{ "id": "sq-1", "name": "My Squad" }] });
let shaped = shape_response("/squads", core);
assert!(shaped["squads"].is_array());
assert_eq!(shaped["squads"].as_array().unwrap().len(), 1);
}
#[test]
fn test_shape_pack_store_response() {
let core = json!({ "packs": [{ "id": "gold-pack" }] });
let shaped = shape_response("/packs/store", core);
assert!(shaped["purchasablePacks"].is_array());
}
}