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
+220 -2
View File
@@ -236,6 +236,116 @@ const EXACT: &[ExactRoute] = &[
core_method: "GET", core_path: "/notifications",
notes: "FUT notifications (plural form) → Core notifications",
},
// ── Squad list ────────────────────────────────────────────────────────────
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/squad/list",
core_method: "GET", core_path: "/squads",
notes: "FUT squad list → Core all squads",
},
ExactRoute {
ea_method: "DELETE", ea_path: "/ut/game/fut/squad/active",
core_method: "DELETE", core_path: "/squad",
notes: "FUT delete active squad → Core delete squad (best-effort)",
},
// ── Achievements / Trophies ───────────────────────────────────────────────
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/trophies",
core_method: "GET", core_path: "/achievements",
notes: "FUT trophies → Core achievements",
},
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/trophy",
core_method: "GET", core_path: "/achievements",
notes: "FUT trophy (singular) → Core achievements",
},
// ── Rivals extra endpoints ────────────────────────────────────────────────
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/rivals/rank",
core_method: "GET", core_path: "/division",
notes: "FUT rivals rank → Core division",
},
ExactRoute {
ea_method: "POST", ea_path: "/ut/game/fut/rivals/result",
core_method: "POST", core_path: "/matches/result",
notes: "FUT rivals match result → Core match result",
},
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/rivals/leaderboard",
core_method: "GET", core_path: "/statistics",
notes: "FUT rivals leaderboard → Core statistics (offline approximation)",
},
// ── Objectives sub-groups ─────────────────────────────────────────────────
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/objectives/group",
core_method: "GET", core_path: "/objectives",
notes: "FUT objectives group → Core objectives",
},
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/objectives/daily",
core_method: "GET", core_path: "/objectives",
notes: "FUT daily objectives → Core objectives",
},
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/objectives/weekly",
core_method: "GET", core_path: "/objectives",
notes: "FUT weekly objectives → Core objectives",
},
ExactRoute {
ea_method: "POST", ea_path: "/ut/game/fut/objectives/group/claim",
core_method: "POST", core_path: "/objectives/claim",
notes: "FUT objectives group claim → Core objectives claim",
},
// ── Catalogue / Card search ───────────────────────────────────────────────
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/catalogue/item",
core_method: "GET", core_path: "/cards",
notes: "FUT catalogue items → Core card catalogue",
},
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/catalogue",
core_method: "GET", core_path: "/cards",
notes: "FUT catalogue → Core card catalogue",
},
// ── Store / Pricing ───────────────────────────────────────────────────────
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/store/pricetiers",
core_method: "GET", core_path: "/packs/store",
notes: "FUT price tiers → Core pack store definitions",
},
// ── Consumables / Chemistry / Fitness ─────────────────────────────────────
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/consumables",
core_method: "GET", core_path: "/chemistry-styles",
notes: "FUT consumables → Core chemistry styles (approximation)",
},
ExactRoute {
ea_method: "POST", ea_path: "/ut/game/fut/fitness",
core_method: "GET", core_path: "/club",
notes: "FUT fitness apply → Core club (placeholder, fitness not tracked)",
},
// ── Loan items ────────────────────────────────────────────────────────────
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/loanitems",
core_method: "GET", core_path: "/collection",
notes: "FUT loan items → Core collection (client filters is_loan)",
},
// ── Customization / Kit ───────────────────────────────────────────────────
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/customization",
core_method: "GET", core_path: "/settings",
notes: "FUT customization → Core settings",
},
ExactRoute {
ea_method: "PUT", ea_path: "/ut/game/fut/customization",
core_method: "PUT", core_path: "/settings",
notes: "FUT save customization → Core save settings",
},
// ── Active messages / MOTD ────────────────────────────────────────────────
ExactRoute {
ea_method: "GET", ea_path: "/ut/game/fut/activeMessage",
core_method: "GET", core_path: "/notifications",
notes: "FUT active messages → Core notifications (mapped to nearest equivalent)",
},
];
// ── Prefix mappings (dynamic path segments after a known prefix) ───────────────
@@ -300,6 +410,44 @@ fn prefix_routes() -> &'static [PrefixRoute] {
core_path_fn: |suffix| format!("/collection/{suffix}"),
notes: "FUT quick-sell item → Core delete owned card",
},
// /ut/game/fut/item/{owned_id} (PUT) → PUT /collection/{owned_id}/chemistry
PrefixRoute {
ea_method: "PUT",
ea_prefix: "/ut/game/fut/item/",
core_path_fn: |suffix| {
// suffix is just the owned_card_id; game sends chemistry style in body
format!("/collection/{suffix}/chemistry")
},
notes: "FUT apply chemistry style → Core update card chemistry",
},
// /ut/game/fut/squad/{id} (GET) → GET /squads/{id}
// Note: must come after exact matches for /squad/active, /squad/0, etc.
PrefixRoute {
ea_method: "GET",
ea_prefix: "/ut/game/fut/squad/",
core_path_fn: |suffix| {
// Skip known non-ID suffixes already handled as exact routes
match suffix {
"active" | "chemistry" | "list" => "/squad".to_string(),
_ => format!("/squads/{suffix}"),
}
},
notes: "FUT squad by ID → Core squad by ID",
},
// /ut/game/fut/squad/{id} (DELETE) → DELETE /squads/{id}
PrefixRoute {
ea_method: "DELETE",
ea_prefix: "/ut/game/fut/squad/",
core_path_fn: |suffix| format!("/squads/{suffix}"),
notes: "FUT delete squad by ID → Core delete squad",
},
// /ut/game/fut/sbc/set/{set_id} (GET) → GET /sbc/{set_id}
PrefixRoute {
ea_method: "GET",
ea_prefix: "/ut/game/fut/sbc/set/",
core_path_fn: |suffix| format!("/sbc/{suffix}"),
notes: "FUT SBC set details → Core individual SBC",
},
// /ut/game/fut/store/pack/{pack_id}/open → POST /packs/open/{pack_id}
PrefixRoute {
ea_method: "POST",
@@ -549,7 +697,77 @@ mod tests {
#[test]
fn test_total_exact_routes_count() {
// Ensure we have a meaningful number of exact mappings
assert!(EXACT.len() >= 35, "expected at least 35 exact mappings, got {}", EXACT.len());
assert!(EXACT.len() >= 55, "expected at least 55 exact mappings, got {}", EXACT.len());
}
// ── Phase 22 new mappings ─────────────────────────────────────────────────
#[test]
fn test_squad_list_maps() {
let m = map_to_core("GET", "/ut/game/fut/squad/list");
assert!(m.is_some());
assert_eq!(m.unwrap().core_path, "/squads");
}
#[test]
fn test_squad_by_id_get_maps() {
let m = map_to_core_with_method("GET", "/ut/game/fut/squad/abc-squad-id");
assert!(m.is_some());
assert_eq!(m.unwrap().core_path, "/squads/abc-squad-id");
}
#[test]
fn test_squad_by_id_delete_maps() {
let m = map_to_core_with_method("DELETE", "/ut/game/fut/squad/abc-squad-id");
assert!(m.is_some());
assert_eq!(m.unwrap().core_path, "/squads/abc-squad-id");
}
#[test]
fn test_item_put_chemistry_maps() {
let m = map_to_core_with_method("PUT", "/ut/game/fut/item/owned-999");
assert!(m.is_some());
assert_eq!(m.unwrap().core_path, "/collection/owned-999/chemistry");
}
#[test]
fn test_sbc_set_get_maps() {
let m = map_to_core_with_method("GET", "/ut/game/fut/sbc/set/sbc-123");
assert!(m.is_some());
assert_eq!(m.unwrap().core_path, "/sbc/sbc-123");
}
#[test]
fn test_trophies_maps() {
let m = map_to_core("GET", "/ut/game/fut/trophies");
assert!(m.is_some());
assert_eq!(m.unwrap().core_path, "/achievements");
}
#[test]
fn test_rivals_result_maps() {
let m = map_to_core("POST", "/ut/game/fut/rivals/result");
assert!(m.is_some());
assert_eq!(m.unwrap().core_path, "/matches/result");
}
#[test]
fn test_catalogue_item_maps() {
let m = map_to_core("GET", "/ut/game/fut/catalogue/item");
assert!(m.is_some());
assert_eq!(m.unwrap().core_path, "/cards");
}
#[test]
fn test_objectives_daily_maps() {
let m = map_to_core("GET", "/ut/game/fut/objectives/daily");
assert!(m.is_some());
assert_eq!(m.unwrap().core_path, "/objectives");
}
#[test]
fn test_active_message_maps() {
let m = map_to_core("GET", "/ut/game/fut/activeMessage");
assert!(m.is_some());
}
}