CareerAchievementsView.razor

Razor panel for the club career achievements screen. Lists career achievements and their claim buttons, shows season challenges with progress meters, and renders recent season history; has a compact mode that delegates to CareerFinaleView. Claim clicks call the OnClaim callback.

@using Sandbox
@using Sandbox.UI
@using Socer
@using System
@using System.Linq
@inherits Panel

<root class="career-achievements localized-text @(Compact?"compact":"expanded")">
    @if(Compact)
    {
        <CareerFinaleView Career="@Career" />
    }
    else
    {
        <label class="career-achievements-title">@T("Достижения клуба","Club achievements")</label>
        @if(Career.Achievements.Any(a=>a.RewardAvailable && !Career.CanReceiveTrainingCards(a.TrainingCount)) || Career.SeasonChallenges.Any(c=>c.Earned && !c.Claimed && !Career.CanReceiveTrainingCards(c.TrainingCount)))
        {<label class="career-challenge-hint">@T("Для награды нужно место в инвентаре улучшений. Примените или спишите ненужные карточки в «Мой клуб → Улучшения». Награда остаётся доступной.","This reward needs upgrade inventory space. Apply or discard unwanted cards in My club → Upgrades. The reward remains available.")</label>}
        <div class="career-achievement-grid">
        @foreach(var id in AchievementIds)
        {
            var achievement=Career.Achievements.FirstOrDefault(a=>a.Id==id);
            <div class="career-achievement @(achievement==null?"locked":"earned")"><img src="/ui-icons/ui-bold-nav-cup.svg"/><div><label>@AchievementName(id)</label><span>@(achievement==null?AchievementGoal(id):T($"Сезон {achievement.EarnedSeason}",$"Season {achievement.EarnedSeason}"))</span></div>
            @if(achievement?.RewardAvailable==true){<button class="primary claim-career-reward" disabled="@(Career.Pending!=null || !Career.CanReceiveTrainingCards(achievement.TrainingCount))" onclick=@(()=>OnClaim?.Invoke(id))>@Reward(achievement.CoinReward,achievement.TrainingGrade,achievement.TrainingCount)</button>}
            else if(achievement!=null){<b>✓</b>}
            </div>
        }
        </div>
        <label class="career-achievements-title">@T("Новые вызовы","Next challenges")</label>
        @if(!Career.PremierChampion){<label class="career-challenge-hint">@T("После первого титула Премьер-лиги каждый новый сезон получает отдельный вызов. Карьера продолжается, состав и достижения сохраняются.","After your first Premier title, each new season has a separate challenge. Your career continues with your squad and achievements.")</label>}
        else if(!Career.SeasonChallenges.Any()){<label class="career-challenge-hint">@T("Первый титул выигран! С нового сезона начнутся новые вызовы.","The first title is yours! New challenges start next season.")</label>}
        @foreach(var challenge in Career.SeasonChallenges.OrderByDescending(c=>c.Season).Where(c=>c.Season==Career.Season || c.Earned && !c.Claimed).Take(6))
        {
            <div class="career-season-challenge @(challenge.Earned?"earned":"")"><div><span>@T($"Сезон {challenge.Season}",$"Season {challenge.Season}")</span><label>@ChallengeName(challenge.Objective)</label><span>@ChallengeGoal(challenge)</span></div><div class="career-challenge-meter"><span>@($"{challenge.Progress}/{challenge.Target}")</span><div><span style="@($"width:{Math.Clamp(challenge.Progress*100/Math.Max(1,challenge.Target),0,100)}%;")"></span></div></div><button class="primary claim-career-reward" disabled="@(!challenge.Earned || challenge.Claimed || Career.Pending!=null || !Career.CanReceiveTrainingCards(challenge.TrainingCount))" onclick=@(()=>OnClaim?.Invoke(challenge.Id))>@(challenge.Claimed?T("Получено","Claimed"):challenge.Earned?Reward(challenge.CoinReward,challenge.TrainingGrade,challenge.TrainingCount):challenge.Resolved?T("Не выполнено","Missed"):T("В процессе","In progress"))</button></div>
        }
        @if(Career.LeagueHistory.Count>0){<label class="career-achievements-title">@T("История сезонов","Season history")</label><div class="career-season-history">@foreach(var result in Career.LeagueHistory.OrderByDescending(r=>r.Season).Take(8)){<div><label>@T($"Сезон {result.Season} · {League(result.Division)}",$"Season {result.Season} · {League(result.Division)}")</label><b>@($"#{result.Place}")</b><span>@T($"{result.Points} оч. · {result.Wins} В / {result.Draws} Н / {result.Losses} П",$"{result.Points} pts · {result.Wins} W / {result.Draws} D / {result.Losses} L")</span></div>}</div>}
    }
</root>

@code {
    [Parameter] public ClubCareer Career {get;set;}
    [Parameter] public bool Compact {get;set;}
    [Parameter] public Action<string> OnClaim {get;set;}
    static readonly string[] AchievementIds={"first-league-title","cup-winner","premier-champion","unbeaten-champion","domestic-double"};
    static string T(string ru,string en)=>GamePreferences.Russian?ru:en;
    static string League(int division)=>T(new[]{"Третья лига","Вторая лига","Первая лига","Национальная лига","Премьер-лига"}[Math.Clamp(division-1,0,4)],new[]{"Third League","Second League","First League","National League","Premier League"}[Math.Clamp(division-1,0,4)]);
    static string AchievementName(string id)=>id switch{
        "first-league-title"=>T("Первое чемпионство","First league title"),"cup-winner"=>T("Первый кубок","First cup"),
        "premier-champion"=>T("Чемпион Премьер-лиги","Premier champion"),"unbeaten-champion"=>T("Непобеждённый чемпион","Unbeaten champion"),
        "domestic-double"=>T("Золотой дубль","Domestic double"),_=>id};
    static string AchievementGoal(string id)=>id switch{
        "first-league-title"=>T("Выиграйте любую лигу.","Win any league."),"cup-winner"=>T("Выиграйте кубок.","Win a cup."),
        "premier-champion"=>T("Выиграйте Премьер-лигу.","Win the Premier League."),"unbeaten-champion"=>T("Выиграйте Премьер-лигу без поражений.","Win the Premier League without a loss."),
        "domestic-double"=>T("Выиграйте Премьер-лигу и кубок в одном сезоне.","Win the Premier League and a cup in one season."),_=>""};
    static string ChallengeName(string id)=>id switch{
        "defend-title"=>T("Защитить титул","Defend the title"),"double"=>T("Золотой дубль","Domestic double"),
        "strong-season"=>T("Сильный сезон","Strong season"),"clean-sheets"=>T("Надёжная оборона","Clean sheets"),
        "return-premier"=>T("Вернуться в Премьер-лигу","Return to the Premier League"),_=>id};
    static string ChallengeGoal(CareerSeasonChallenge c)=>c.Objective switch{
        "defend-title"=>T("Снова выиграйте Премьер-лигу.","Win the Premier League again."),"double"=>AchievementGoal("domestic-double"),
        "strong-season"=>T($"Наберите {c.Target} очков в лиге.",$"Earn {c.Target} league points."),
        "clean-sheets"=>T($"Завершите {c.Target} матчей лиги без пропущенных голов.",$"Keep {c.Target} league clean sheets."),
        "return-premier"=>T("Финишируйте в первой двойке Национальной лиги.","Finish in the National League's top two."),_=>""};
    static string Reward(int coins,int grade,int count)=>T($"Забрать: {coins} ◈ + {count} {Grade(grade)} улучш.",$"Claim: {coins} ◈ + {count} {Grade(grade)} upgrade");
    static string Grade(int grade)=>T(new[]{"бронз.","серебр.","золот."}[Math.Clamp(grade-1,0,2)],new[]{"bronze","silver","gold"}[Math.Clamp(grade-1,0,2)]);
    protected override int BuildHash()=>HashCode.Combine(Career?.Season,Career?.PremierTitleCount,Career?.ClaimableCareerRewards,Career?.CurrentSeasonChallenge?.Progress,Career?.LeagueHistory?.Count,Compact,GamePreferences.Russian)^HashCode.Combine(Career?.Achievements?.Count,Career?.CurrentSeasonChallenge?.Earned,Career?.CurrentSeasonChallenge?.Resolved,Career?.Pending!=null,Career?.CareerWon,Career?.FinalCup!=null);
}