Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@petrvecera
Copy link
Member

@petrvecera petrvecera commented Nov 21, 2025

Summary by CodeRabbit

  • New Features

    • Counter statistics view showing detailed total and per-match averages for many player metrics.
    • Average counters widget with KD, squads/vehicles K/L/P, APM and game-type toggles.
    • Localized counter statistics added for multiple languages.
  • Improvements

    • Default patch updated to 2.1.7.
    • Faction and game-mode selections now persist in the URL for shareable state.
  • Documentation

    • Added guideline to include key props when rendering lists in React.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 21, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds counter-statistics UI and widgets, i18n translations across multiple locales, URL-synced faction/type state in detailed stats, type changes for counters to PlayerReportCounters, a config patch bump to 2.1.7, and a small guideline note about adding keys when mapping components.

Changes

Cohort / File(s) Summary
Configuration Update
config.ts
Swaps default patch selector from 2.1.52.1.7; updates statsPatchSelector ranges/labels; adds patch entry 2.1.7 with dataTag: "v2.1.5-1" and dataTime: "13/Nov/2025"; updates latestPatch.
Localization (counterStatistics & counters)
public/locales/{en,cs,de,es,ru,zh-Hans}/players.json
Adds counterStatistics (title, totalMatches, total, average, categories, stats, labels) in cs, de, ru, zh-Hans, and updates/enhances en and es locales; adds counters.widget in en/players.json.
Guidelines
.augment-guidelines
Adds guideline: "Add key props when iterating over array of components."
Type Definitions
src/coh3/coh3-types.ts
Changes counters type from Record<string, number>PlayerReportCounters in PlayerPersonalCOHStats and ProcessedCOHPlayerStats.
New Component – CounterStatisticsCard
screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx
New default-export React component rendering Total & Average counter stat groups with helpers for KD, APM, time formatting, grouped/simple stat renderers, and i18n t prop.
Detailed Stats – routing & props
screens/players/tabs/detailed-stats-tab/detailed-stats-tab.tsx
Adds URL-backed faction/type state, syncs via router and useEffect, shallow router.push on selection, and accepts/passes t: TFunction prop.
Inner Detailed Stats – counters UI
screens/players/tabs/detailed-stats-tab/inner-detailed-stats.tsx
Changes stats.counters type to PlayerReportCounters, adds t prop, conditionally renders CounterStatisticsCard with totalMatches.
Parent usage update
screens/players/index.tsx
Passes t (translation function) into DetailedStatsTab.
Standings tab – navigation & widget
screens/players/tabs/standings-tab/player-standings-tab.tsx
Extends changeView(value, faction?) to accept optional faction; updates call sites and inserts CountersWidget into the UI.
New Component – CountersWidget
screens/players/tabs/standings-tab/widgets/counters-widget.tsx
New default-export component computing per-type average counters (KD, APM, totals), maintains selectedType state, renders mode selector buttons and stat rows, uses t for localization.

Sequence Diagram(s)

sequenceDiagram
    participant Parent as PlayerCard / StandingsTab
    participant Detailed as DetailedStatsTab
    participant Inner as InnerDetailedStats
    participant CounterCard as CounterStatisticsCard
    participant CountersWgt as CountersWidget

    Parent->>Detailed: pass playerStatsData, t
    Detailed->>Detailed: read/sync faction & type from URL
    Detailed->>Inner: pass stats, t
    Inner->>CounterCard: pass counters (PlayerReportCounters), totalMatches, t
    CounterCard->>CounterCard: compute KD/APM, format numbers, render Total & Average
    Parent->>CountersWgt: pass playerStatsData, playerStandings, t
    CountersWgt->>CountersWgt: compute typeWithMostGames, selectedType
    CountersWgt->>CountersWgt: aggregate counters → averageCounters
    CountersWgt->>Parent: emit navigation (changeView) with selectedType/faction
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to: CounterStatisticsCard formatting and edge-case math (divide-by-zero), CountersWidget aggregation/memo correctness, router useEffect synchronization (query parsing/validation), and consistent application of PlayerReportCounters type across usages.
  • Spot-check locale files for structural parity across languages.

Possibly related PRs

Suggested labels

localization

Poem

🐰 I hop with keys in every map and trace,
Counters count and patch numbers find their place.
KD and APM in columns neat and spry,
Languages sing stats in every sky.
Hop, hop—new widgets bloom, I twitch my nose with pride.

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is partially related to the changeset, referring to counter statistics additions, but misleadingly suggests 'v .7' when the version update (2.1.5 to 2.1.7) is secondary to the main feature addition. Consider a more precise title like 'Add counter statistics UI component' or 'Add counter statistics feature and update patch version' to better reflect the primary changes.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0500b6e and 8f891f3.

📒 Files selected for processing (1)
  • config.ts (3 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (4)
screens/players/tabs/standings-tab/widgets/counters-widget.tsx (2)

70-88: Consider safer alternatives to non-null assertions.

The code uses multiple non-null assertions (!) when aggregating counters. While the aggregation initializes these fields, using non-null assertions can mask potential runtime errors if the data structure changes.

Consider refactoring to avoid non-null assertions:

    raceTypeArray.forEach((faction) => {
      const factionData = playerStatsData?.statGroups?.[selectedType]?.[faction];
      if (factionData && factionData.counters) {
        const matches = factionData.w + factionData.l;
        totalMatches += matches;

        // Aggregate counters
-       aggregatedCounters.dmgdone! += factionData.counters.dmgdone || 0;
-       aggregatedCounters.ekills! += factionData.counters.ekills || 0;
-       aggregatedCounters.edeaths! += factionData.counters.edeaths || 0;
+       aggregatedCounters.dmgdone = (aggregatedCounters.dmgdone || 0) + (factionData.counters.dmgdone || 0);
+       aggregatedCounters.ekills = (aggregatedCounters.ekills || 0) + (factionData.counters.ekills || 0);
+       aggregatedCounters.edeaths = (aggregatedCounters.edeaths || 0) + (factionData.counters.edeaths || 0);
        // ... apply same pattern to other fields
      }
    });

189-223: Consider indicating the selected game type.

The buttons for selecting game types (1v1, 2v2, 3v3, 4v4) don't visually indicate which type is currently selected, which could confuse users.

Add visual feedback for the selected type:

                <Button
                  variant="default"
                  size={"compact-sm"}
                  className={classes.mapsWidgetButton}
                  onClick={() => setSelectedType("1v1")}
+                 variant={selectedType === "1v1" ? "filled" : "default"}
                >
                  1 vs 1
                </Button>

Apply the same pattern to the other three buttons.

screens/players/tabs/detailed-stats-tab/inner-detailed-stats.tsx (1)

73-79: Simplify stats.counters guard or relax the type

Given the prop type is stats: { ...; counters: PlayerReportCounters } | null, a non‑null stats always has counters. The runtime guard stats.counters && <CounterStatisticsCard ...> is therefore redundant unless counters can actually be missing.

Either:

  • Keep the invariant and drop the guard:
-      {stats.counters && (
-        <CounterStatisticsCard counters={stats.counters} totalMatches={totalMatches} t={t} />
-      )}
+      <CounterStatisticsCard counters={stats.counters} totalMatches={totalMatches} t={t} />

or

  • If counters can be absent, update the type to counters?: PlayerReportCounters | null; so TypeScript reflects reality.
screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx (1)

37-91: KD/APM behavior under the “Average (per match)” column

In renderGroupedStat:

  • KD: for both total and average columns, you effectively show the same ratio (ekills / edeaths or ekills when deaths are 0); the “Average (per match)” column doesn’t change the KD semantics.
  • APM: similarly, APM is computed from totalcmds and gt only, so the total and average columns will show identical APM values.

This is logically consistent (KD/APM are rate metrics, not aggregatable counts), but the “Average (per match)” heading may imply that KD/APM are also averaged per match.

If you want to avoid confusion, consider either:

  • Showing KD/APM only in the Total column, or
  • Adjusting the label (e.g., “Overall rates”) for these metrics in the Average column, or
  • Explicitly documenting that KD/APM are global rates, not per‑match averages.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7ce135 and 0500b6e.

📒 Files selected for processing (15)
  • .augment-guidelines (1 hunks)
  • config.ts (3 hunks)
  • public/locales/cs/players.json (1 hunks)
  • public/locales/de/players.json (1 hunks)
  • public/locales/en/players.json (1 hunks)
  • public/locales/es/players.json (1 hunks)
  • public/locales/ru/players.json (1 hunks)
  • public/locales/zh-Hans/players.json (1 hunks)
  • screens/players/index.tsx (1 hunks)
  • screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx (1 hunks)
  • screens/players/tabs/detailed-stats-tab/detailed-stats-tab.tsx (4 hunks)
  • screens/players/tabs/detailed-stats-tab/inner-detailed-stats.tsx (5 hunks)
  • screens/players/tabs/standings-tab/player-standings-tab.tsx (4 hunks)
  • screens/players/tabs/standings-tab/widgets/counters-widget.tsx (1 hunks)
  • src/coh3/coh3-types.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx (1)
src/coh3/coh3-types.ts (1)
  • PlayerReportCounters (205-252)
screens/players/tabs/detailed-stats-tab/detailed-stats-tab.tsx (1)
src/coh3/coh3-types.ts (5)
  • ProcessedCOHPlayerStats (440-490)
  • raceTypeArray (4-4)
  • raceType (6-6)
  • leaderBoardTypeArray (8-8)
  • leaderBoardType (10-10)
screens/players/tabs/standings-tab/widgets/counters-widget.tsx (2)
src/coh3/coh3-types.ts (6)
  • ProcessedCOHPlayerStats (440-490)
  • InternalStandings (126-129)
  • leaderBoardType (10-10)
  • leaderBoardTypeArray (8-8)
  • PlayerReportCounters (205-252)
  • raceTypeArray (4-4)
src/coh3/coh3-data.ts (1)
  • localizedGameTypes (333-338)
screens/players/tabs/detailed-stats-tab/inner-detailed-stats.tsx (1)
src/coh3/coh3-types.ts (3)
  • PlayerReportCounters (205-252)
  • FactionSide (18-18)
  • HistoricLeaderBoardStat (430-438)
🔇 Additional comments (19)
config.ts (4)

45-45: LGTM!

The default patch selector has been correctly updated to "2.1.7" and matches a key in the statsPatchSelector object.


222-222: LGTM!

The latestPatch has been correctly updated to "2.1.7" and matches a key in the patches object.


227-230: Fix the date format inconsistency.

Line 229 uses "13/Nov/2025" with an abbreviated month name, while other entries in the patches object consistently use full month names (e.g., "11/Sep/2025" on Line 233, "31/July/2025" on Line 237). Please update to "13/November/2025" for consistency.

     "2.1.7": {
       dataTag: "v2.1.5-1", // This is the tag of the data repo
-      dataTime: "13/Nov/2025",
+      dataTime: "13/November/2025",
     },

Likely an incorrect or invalid review comment.


228-228: Verification complete—no action required.

The dataTag "v2.1.5-1" exists in the coh3-data repository, and both "2.1.7" and "2.1.5" intentionally reference it. This pattern is consistent throughout the config where multiple patches share the same dataTag when there are no game data changes between them.

src/coh3/coh3-types.ts (1)

390-390: LGTM! Type refinement improves type safety.

The change from Record<string, number> to PlayerReportCounters provides stronger typing for the counters field, ensuring consistency with the well-defined interface at lines 205-252.

Also applies to: 481-481

.augment-guidelines (1)

9-9: LGTM! Useful guideline for React development.

Adding a reminder about key props when iterating over arrays is a valuable best practice that helps prevent common React issues.

public/locales/zh-Hans/players.json (1)

248-297: LGTM! Comprehensive localization for counter statistics.

The translation keys are well-structured and provide complete coverage for the counter statistics feature.

public/locales/de/players.json (1)

248-297: LGTM! German translations added consistently.

The counterStatistics localization follows the same structure as other locales.

screens/players/tabs/standings-tab/player-standings-tab.tsx (2)

50-56: LGTM! URL state management enhanced cleanly.

The optional faction parameter allows passing additional context to the detailed stats view while maintaining backward compatibility.


111-116: LGTM! CountersWidget integration is correct.

The widget is properly wired with all required props including the translation function and navigation handler.

public/locales/cs/players.json (1)

248-297: LGTM! Czech translations added consistently.

The counterStatistics localization follows the established pattern across all locales.

screens/players/index.tsx (1)

263-263: LGTM! Translation function properly propagated.

Passing the t prop enables localization throughout the detailed stats tab.

public/locales/en/players.json (1)

248-314: Counter statistics and counters widget strings look consistent

counterStatistics keys (categories, stats, labels, including stats.apm) align with CounterStatisticsCard usage, and the counters.widget block appears internally consistent.

screens/players/tabs/detailed-stats-tab/inner-detailed-stats.tsx (1)

4-50: Stats typing and t wiring align with shared types

Using PlayerReportCounters for stats.counters and adding t: TFunction to InnerDetailedStats matches the shared ProcessedCOHPlayerStats definition and the new i18n flow. This keeps the component strongly typed and ready for CounterStatisticsCard.

screens/players/tabs/detailed-stats-tab/detailed-stats-tab.tsx (2)

24-52: URL‑backed selection state is implemented correctly

Using raceTypeArray and leaderBoardTypeArray to validate query.faction / query.type before seeding state, and syncing back from URL via useEffect, gives deterministic behavior and tolerates invalid query params cleanly.


69-105: Verification script is misaligned with review content

The shell script provided verifies that the t prop is being passed to DetailedStatsTab, which is correct and unrelated to the review's suggestions. However, the review's actual recommendations—removing redundant defaultValue props and sanitizing invalid query parameters on mount—are optional refactoring suggestions that remain valid but were not verified by the script.

The Select components shown are indeed controlled (both have value={selectedFaction} and value={selectedGameMode} props), making the defaultValue attributes redundant and safe to remove as suggested. The URL synchronization suggestion is also a reasonable enhancement for UX consistency.

Likely an incorrect or invalid review comment.

screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx (3)

12-21: Number and average formatting are robust

formatNumber (rounded, localized) and formatAverage (division by totalMatches with 0‑match guard) give sensible totals and per‑match values and avoid divide‑by‑zero issues.


93-172: Game time averaging and stat‑group rendering look correct

  • convertSecondsToReadableTime correctly converts seconds to xh ym and handles 0 gracefully.
  • For gt, you compute per‑match average seconds before converting, which avoids double‑division.
  • Stat groups are only rendered when they contain at least one non‑zero stat, keeping the UI focused.

No functional issues here.


327-369: Card layout cleanly separates total vs. per‑match views

The main card header, total‑matches summary, and two‑column (Total vs Average) layout are easy to read, and the reuse of statGroups for both columns keeps things DRY.

Comment on lines +248 to +296
},
"counterStatistics": {
"title": "Estadísticas de Contadores",
"totalMatches": "Total de Partidas: {{count}}",
"total": "Total",
"average": "Promedio (por partida)",
"categories": {
"combat": "Combate",
"productionBuildings": "Producción y Edificios",
"abilitiesCommands": "Habilidades y Comandos",
"other": "Otros"
},
"stats": {
"damageData": "Daño Causado",
"units": "Unidades",
"squads": "Escuadrones",
"vehicles": "Vehículos",
"structureDamage": "Daño a Estructuras",
"vehiclesAbandoned": "Vehículos Abandonados",
"buildings": "Edificios",
"unitsProduced": "Unidades Producidas",
"points": "Puntos",
"abilitiesUsed": "Habilidades Usadas",
"totalCommands": "Comandos Totales",
"upgrades": "Mejoras",
"requisition": "Requisición",
"power": "Energía",
"commandPointsEarned": "Puntos de Comando Ganados",
"entityReinforcements": "Refuerzos de Entidades",
"veterancy": "Veteranía",
"maxPopulation": "Población Máxima",
"gameTime": "Tiempo de Juego"
},
"labels": {
"kills": "Bajas",
"deaths": "Muertes",
"kd": "KD",
"killed": "Eliminados",
"lost": "Perdidos",
"produced": "Producidos",
"captured": "Capturados",
"recaptured": "Recapturados",
"earned": "Ganados",
"spent": "Gastados",
"max": "Máx",
"squadXp": "XP de Escuadrón",
"squadRank": "Rango de Escuadrón",
"vehicleRank": "Rango de Vehículo"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Define counterStatistics.stats.apm for Spanish locale

CounterStatisticsCard calls t("counterStatistics.stats.apm"), but this key is not present here, so the label will fall back to another locale. To keep the UI fully localized, add a Spanish translation for counterStatistics.stats.apm (e.g., matching the style of other stats.* entries).

🤖 Prompt for AI Agents
In public/locales/es/players.json around lines 248 to 296, the Spanish locale is
missing the key counterStatistics.stats.apm which CounterStatisticsCard expects;
add an "apm" entry under the "stats" object (following the existing style, e.g.,
"apm": "APM" or a localized phrase like "Acciones por Minuto") so
t("counterStatistics.stats.apm") resolves to a Spanish string.

Comment on lines +248 to +296
},
"counterStatistics": {
"title": "Статистика счетчиков",
"totalMatches": "Всего матчей: {{count}}",
"total": "Всего",
"average": "Среднее (за матч)",
"categories": {
"combat": "Бой",
"productionBuildings": "Производство и здания",
"abilitiesCommands": "Способности и команды",
"other": "Прочее"
},
"stats": {
"damageData": "Нанесено урона",
"units": "Юниты",
"squads": "Отряды",
"vehicles": "Техника",
"structureDamage": "Урон по зданиям",
"vehiclesAbandoned": "Брошенная техника",
"buildings": "Здания",
"unitsProduced": "Произведено юнитов",
"points": "Точки",
"abilitiesUsed": "Использовано способностей",
"totalCommands": "Всего команд",
"upgrades": "Улучшения",
"requisition": "Реквизиция",
"power": "Энергия",
"commandPointsEarned": "Получено командных очков",
"entityReinforcements": "Подкрепления юнитов",
"veterancy": "Ветеранство",
"maxPopulation": "Макс. население",
"gameTime": "Время игры"
},
"labels": {
"kills": "Убийств",
"deaths": "Смертей",
"kd": "КД",
"killed": "Убито",
"lost": "Потеряно",
"produced": "Произведено",
"captured": "Захвачено",
"recaptured": "Отбито",
"earned": "Получено",
"spent": "Потрачено",
"max": "Макс",
"squadXp": "Опыт отряда",
"squadRank": "Ранг отряда",
"vehicleRank": "Ранг техники"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add missing counterStatistics.stats.apm translation

CounterStatisticsCard uses t("counterStatistics.stats.apm"), but this locale does not define that key, so it will fall back to the default language. For consistency with en (and to avoid mixed-language UI), add a Russian translation for counterStatistics.stats.apm.

🤖 Prompt for AI Agents
In public/locales/ru/players.json around lines 248-296 the key
counterStatistics.stats.apm is missing which causes fallback to the default
locale; add the missing "apm" entry under counterStatistics.stats with an
appropriate Russian translation (for example "Действий в минуту" or similar) to
match the en locale and keep the UI fully localized.

Comment on lines +174 to +325
const statGroups = [
{
title: t("counterStatistics.categories.combat"),
stats: [
{
type: "simple" as const,
label: t("counterStatistics.stats.damageData"),
key: "dmgdone" as const,
},
{
type: "grouped" as const,
label: t("counterStatistics.stats.units"),
items: [
{ key: "ekills" as const, shortLabel: t("counterStatistics.labels.kills") },
{ key: "edeaths" as const, shortLabel: t("counterStatistics.labels.deaths") },
{ key: "kd" as const, shortLabel: t("counterStatistics.labels.kd") },
],
},
{
type: "grouped" as const,
label: t("counterStatistics.stats.squads"),
items: [
{ key: "sqkill" as const, shortLabel: t("counterStatistics.labels.killed") },
{ key: "sqlost" as const, shortLabel: t("counterStatistics.labels.lost") },
{ key: "sqprod" as const, shortLabel: t("counterStatistics.labels.produced") },
],
},
{
type: "grouped" as const,
label: t("counterStatistics.stats.vehicles"),
items: [
{ key: "vkill" as const, shortLabel: t("counterStatistics.labels.killed") },
{ key: "vlost" as const, shortLabel: t("counterStatistics.labels.lost") },
{ key: "vprod" as const, shortLabel: t("counterStatistics.labels.produced") },
],
},
{
type: "simple" as const,
label: t("counterStatistics.stats.structureDamage"),
key: "structdmg" as const,
},
{
type: "simple" as const,
label: t("counterStatistics.stats.vehiclesAbandoned"),
key: "vabnd" as const,
},
],
},
{
title: t("counterStatistics.categories.productionBuildings"),
stats: [
{
type: "grouped" as const,
label: t("counterStatistics.stats.buildings"),
items: [
{ key: "bprod" as const, shortLabel: t("counterStatistics.labels.produced") },
{ key: "blost" as const, shortLabel: t("counterStatistics.labels.lost") },
],
},
{
type: "simple" as const,
label: t("counterStatistics.stats.unitsProduced"),
key: "unitprod" as const,
},
],
},
{
title: t("counterStatistics.categories.abilitiesCommands"),
stats: [
{
type: "grouped" as const,
label: t("counterStatistics.stats.points"),
items: [
{ key: "pcap" as const, shortLabel: t("counterStatistics.labels.captured") },
{ key: "precap" as const, shortLabel: t("counterStatistics.labels.recaptured") },
],
},
{
type: "simple" as const,
label: t("counterStatistics.stats.abilitiesUsed"),
key: "abil" as const,
},
{
type: "simple" as const,
label: t("counterStatistics.stats.totalCommands"),
key: "totalcmds" as const,
},
{
type: "simple" as const,
label: t("counterStatistics.stats.apm"),
key: "apm" as const,
},
{
type: "simple" as const,
label: t("counterStatistics.stats.upgrades"),
key: "upg" as const,
},
],
},
{
title: t("counterStatistics.categories.other"),
stats: [
{
type: "grouped" as const,
label: t("counterStatistics.stats.requisition"),
items: [
{ key: "reqearn" as const, shortLabel: t("counterStatistics.labels.earned") },
{ key: "reqspnt" as const, shortLabel: t("counterStatistics.labels.spent") },
{ key: "reqmax" as const, shortLabel: t("counterStatistics.labels.max") },
],
},
{
type: "grouped" as const,
label: t("counterStatistics.stats.power"),
items: [
{ key: "powearn" as const, shortLabel: t("counterStatistics.labels.earned") },
{ key: "powspnt" as const, shortLabel: t("counterStatistics.labels.spent") },
{ key: "powmax" as const, shortLabel: t("counterStatistics.labels.max") },
],
},
{
type: "simple" as const,
label: t("counterStatistics.stats.commandPointsEarned"),
key: "cpearn" as const,
},
{
type: "simple" as const,
label: t("counterStatistics.stats.entityReinforcements"),
key: "erein" as const,
},
{
type: "grouped" as const,
label: t("counterStatistics.stats.veterancy"),
items: [
{ key: "svetxp" as const, shortLabel: t("counterStatistics.labels.squadXp") },
{ key: "svetrank" as const, shortLabel: t("counterStatistics.labels.squadRank") },
{ key: "vvetrank" as const, shortLabel: t("counterStatistics.labels.vehicleRank") },
],
},
{
type: "simple" as const,
label: t("counterStatistics.stats.maxPopulation"),
key: "popmax" as const,
},
{
type: "simple" as const,
label: t("counterStatistics.stats.gameTime"),
key: "gt" as const,
},
],
},
];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Good mapping between stat groups and PlayerReportCounters, with one small simplification opportunity

The statGroups definitions:

  • Use PlayerReportCounters keys that exist in the shared type (dmgdone, ekills, edeaths, sqkill, sqlost, sqprod, vkill, vlost, vprod, structdmg, vabnd, bprod, blost, unitprod, pcap, precap, abil, totalcmds, upg, reqearn, reqspnt, reqmax, powearn, powspnt, powmax, cpearn, erein, svetxp, svetrank, vvetrank, popmax, gt), so there’s no type mismatch.
  • Align i18n keys with locale JSON (counterStatistics.categories.*, .stats.*, .labels.*).

One small cleanup: renderGroupedStat has special handling for "apm", but there’s currently no grouped stat that uses key: "apm"—APM is handled only as a simple stat. Unless you plan to add a grouped APM row, you can drop the "apm" branch from renderGroupedStat to reduce complexity.


🌐 Web query:

In Mantine and React 18, is using numeric values (e.g. `gap="4"`, `mb="4"`) on layout components like `Stack` and `Title` valid and idiomatic?

💡 Result:

Short answer: Yes — but use numbers (not quoted strings) or theme keys.

  • Mantine's layout props (Stack's gap/spacing and the style props like m/mb/px) accept a MantineSpacing which can be a number, a theme spacing key ("xs" | "sm" | "md" | …) or any valid CSS value. [1][2].
  • In JSX prefer gap={4} or mb={4} (numeric) or gap="md"/mb="md" (theme key). Using gap="4" (a string "4") is legal JS/JSX but ambiguous (it's a plain string, not a named theme key) and may not behave as you expect because plain "4" lacks units. Prefer numeric values or the theme keys for idiomatic code. [1][2]

Sources: Mantine Stack docs (gap/spacing) and Mantine Style Props. [1] [2]



🏁 Script executed:

#!/bin/bash
# Search for spacing props in counter-statistics-card.tsx
rg -n "(gap|mb|m|px|py|p)=\"[0-9]" screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx

Length of output: 87


🏁 Script executed:

#!/bin/bash
# Also search for any spacing prop usage to understand the context
rg -n -B2 -A2 "(gap|spacing|mb|mt|m[xy]?|p[xy]?|p=|b[rlt]?)=" screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx | head -100

Length of output: 1576


🏁 Script executed:

#!/bin/bash
# Look at the entire file to understand structure
cat screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx | head -400

Length of output: 12558


🏁 Script executed:

#!/bin/bash
# Search specifically in the range mentioned (174-325)
sed -n '174,325p' screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx

Length of output: 5356


🏁 Script executed:

#!/bin/bash
# Find renderGroupedStat function mentioned in review to check for "apm" handling
rg -n -B5 -A15 "renderGroupedStat|renderSimpleStat" screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx

Length of output: 3585


🏁 Script executed:

#!/bin/bash
# Check if "apm" appears anywhere in this file
rg -n "apm" screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx

Length of output: 775


🏁 Script executed:

#!/bin/bash
# Look for all grouped stat types to understand the pattern
rg -n "type.*grouped|type.*simple" screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx

Length of output: 943


🏁 Script executed:

#!/bin/bash
# Find where renderGroupedStat is actually called and what it does with "apm"
rg -n -B3 -A10 '"apm"' screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx

Length of output: 3080


🏁 Script executed:

#!/bin/bash
# Check the complete renderGroupedStat function
ast-grep --pattern 'function renderGroupedStat($_) { $$$ }'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Alternative: search for renderGroupedStat with rg to find exact location
rg -n "renderGroupedStat" screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx -A 30

Length of output: 2039


🏁 Script executed:

#!/bin/bash
# Check PlayerReportCounters type to confirm key existence
fd -type f "*.ts" -o -type f "*.tsx" | xargs grep -l "type PlayerReportCounters" | head -5

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Also verify the i18n structure for the mentioned keys
fd -type f "*.json" | xargs grep -l "counterStatistics" | head -5

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Find where keys like "apm", "dmgdone" might be used
rg -n "\"apm\"|\"dmgdone\"|\"kd\"" screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx

Length of output: 599


🏁 Script executed:

#!/bin/bash
# Check the actual structure with more context around line 174-325
wc -l screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx

Length of output: 82


🏁 Script executed:

#!/bin/bash
# Expand the view of the file section
sed -n '150,350p' screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx | cat -n

Length of output: 8270


🏁 Script executed:

#!/bin/bash
# Direct view of lines 174-325
sed -n '174,325p' screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx | nl -v 174

Length of output: 40


🏁 Script executed:

#!/bin/bash
# Search for 'apm' in the entire codebase to understand usage
rg -n '"apm"' --type tsx --type ts

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Find renderGroupedStat and understand the apm handling
rg -n "apm" screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx -B5 -A5

Length of output: 3018


🏁 Script executed:

#!/bin/bash
# Check what the review says about apm more carefully - look for the renderGroupedStat implementation
rg -n -A50 "function renderGroupedStat" screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look at the complete file context
head -50 screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx && echo "---" && tail -100 screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx

Length of output: 5198


🏁 Script executed:

#!/bin/bash
# Try to find renderGroupedStat more explicitly
rg -n "renderGroupedStat\s*=" screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx -A 30

Length of output: 1204


🏁 Script executed:

#!/bin/bash
# Look for the const/function declarations
rg -n "const\s+(render|stat)" screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx -B2 -A10

Length of output: 2247


🏁 Script executed:

#!/bin/bash
# Search for apm within a 50-line window in render functions
rg -n "apm" screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx -C10

Length of output: 4416


🏁 Script executed:

#!/bin/bash
# Get broader context: lines 100-400
sed -n '100,400p' screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx | nl -v 100

Length of output: 40


🏁 Script executed:

#!/bin/bash
# Check file length first to ensure valid range
wc -l < screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx

Length of output: 3


🏁 Script executed:

#!/bin/bash
# If file is large, show structure: find function/const definitions
rg -n "^(const|function|export)" screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx | head -20

Length of output: 139


🏁 Script executed:

#!/bin/bash
# Look for apm anywhere in this file  
grep -n "apm" screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx

Length of output: 775


🏁 Script executed:

#!/bin/bash
# Show entire file with line numbers to understand context
nl screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx | head -500

Length of output: 40


🏁 Script executed:

#!/bin/bash
# Just cat the file to see full content
cat screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx

Length of output: 12558


Fix spacing props to use idiomatic Mantine values; remove unreachable APM handling from renderGroupedStat

The statGroups mapping is correct. However, two improvements are needed:

  1. Lines 166 & 169: Replace string numeric spacing (mb="4", gap="4") with either numeric values (mb={4}, gap={4}) or theme keys (mb="md", gap="md") for idiomatic Mantine usage.

  2. Lines 55–63 & 83–85 in renderGroupedStat: APM is only defined as a simple stat in statGroups (line 264), never in grouped stat items. Remove the special "apm" handling branch from renderGroupedStat to reduce complexity.

🤖 Prompt for AI Agents
In
screens/players/tabs/detailed-stats-tab/components/counter-statistics-card.tsx
around lines 166–169 and within the renderGroupedStat function (remove the
unreachable branches at lines ~55–63 and ~83–85), replace non-idiomatic Mantine
spacing props like mb="4" and gap="4" with numeric or theme-key values (e.g.,
mb={4} or mb="md", gap={4} or gap="md"), and delete the special-case "apm"
handling in renderGroupedStat since APM is defined only as a simple stat in
statGroups; leave statGroups unchanged.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 21, 2025

🌐 Branch deployed as preview to:
https://coh3-stats-r5lnf83az-cohstats.vercel.app

@petrvecera petrvecera merged commit 48afc3d into master Nov 21, 2025
2 of 5 checks passed
@petrvecera petrvecera deleted the counter-stats branch November 21, 2025 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants