OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
 
master @ 254 LINES
 
[ HISTORY ]  [ UP ]
 

---
import type {
  AccountLeaderboardEntry,
  ChallengeRun,
  Player,
} from "../../../lib/types";
import Table from "../../Table/Table.astro";
import TableRow from "../../Table/TableRow.astro";
import TableCell from "../../Table/TableCell.astro";
import TeamComposition from "../TeamComposition/TeamComposition.astro";
import PlayerLink from "../PlayerLink/PlayerLink.astro";
import AccountAlts from "../../AccountAlts/AccountAlts.astro";
import { formatDurationMMSS } from "../../../lib/utils";
import DungeonTime from "../../DungeonTime/DungeonTime.astro";
import "./LeaderboardTable.scss";

// "account" reuses the player layout + adds an Alts column. Keeping it here
// (rather than a parallel AccountLeaderboardTable) means rank/realm/time
// styling, mobile responsive rules, and the bracket coloring are all shared.
// "account-total-runs" is the same row shape as "account" but shows Runs
// instead of Time (cross-season total runs sum).
type LeaderboardType =
  | "dungeon"
  | "player"
  | "account"
  | "total-runs"
  | "account-total-runs";

interface Props {
  type: LeaderboardType;
  runs?: ChallengeRun[];
  players?: Player[];
  accounts?: AccountLeaderboardEntry[];
  currentPage?: number;
  pageSize?: number;
  // data attributes for client-side hydration
  region?: string;
  realm?: string;
  dungeon?: string;
}

const {
  type,
  runs = [],
  players = [],
  accounts = [],
  currentPage = 1,
  pageSize = 25,
  region = "",
  realm = "",
  dungeon = "",
} = Astro.props;

const isAccountCombined = type === "account";
const isAccountRuns = type === "account-total-runs";
const isAccountMode = isAccountCombined || isAccountRuns;
const isPlayerLikeTable =
  type === "player" || type === "total-runs" || isAccountMode;
const showTimeColumn = type === "player" || isAccountCombined;
const showRunsColumn = type === "total-runs" || isAccountRuns;
const showAltsColumn = isAccountMode;
// Wider grid + Alts column when in account mode; otherwise the standard player gr
id.
const tableMode = isAccountMode ? "accounts" : "players";

// Normalise account rows into the same player-shaped tuple the row template
// already knows how to render. Keeps a single iteration loop instead of
// branching on type per cell.
type Row = {
  name: string;
  realm_slug: string;
  realm_name?: string;
  region: string;
  main_spec_id?: number;
  combined_best_time?: number;
  total_runs?: number;
  ranking_percentile?: string;
  alts?: AccountLeaderboardEntry["alts"];
};
const rows: Row[] = isAccountMode
  ? accounts.map((a) => ({
      name: a.main.name,
      realm_slug: a.main.realm_slug,
      realm_name: a.main.realm_name ?? a.main.realm_slug,
      region: a.main.region,
      main_spec_id: a.main.main_spec_id,
      combined_best_time: a.account_combined_best_time,
      total_runs: a.total_runs,
      ranking_percentile: a.bracket,
      alts: a.alts,
    }))
  : (players as Row[]);

const formatDate = (timestamp: number) => {
  return new Date(timestamp).toLocaleDateString("en-US", {
    month: "short",
    day: "numeric",
    year: "numeric",
  });
};

const formatTime = (ms: number) => {
  return formatDurationMMSS(ms);
};
---

<div
  class="leaderboard-table-container"
  data-type={type}
  data-region={region}
  data-realm={realm}
  data-dungeon={dungeon}
>
  <div class="leaderboard-content">
    {
      !isPlayerLikeTable ? (
        <Table mode="leaderboard">
          <div slot="header" class="table-header-cell">
            Rank
          </div>
          <div slot="header" class="table-header-cell">
            Time
          </div>
          <div slot="header" class="table-header-cell">
            Team
          </div>
          <div slot="header" class="table-header-cell">
            Date
          </div>

          {runs.length > 0 ? (
            runs.map((run, index) => {
              const rank = (currentPage - 1) * pageSize + index + 1;
              const date = formatDate(run.completed_timestamp);
              const bracketClass = run.ranking_percentile
                ? `bracket-${run.ranking_percentile.toLowerCase()}`
                : "";

              return (
                <TableRow>
                  <TableCell label="Rank">
                    <span class:list={["rank", "ranking-number", bracketClass]}>
                      {rank}
                    </span>
                  </TableCell>
                  <TableCell label="Time">
                    <DungeonTime
                      dungeonId={Number(dungeon) || null}
                      dungeonSlug={dungeon}
                      durationMs={run.duration}
                    />
                  </TableCell>
                  <TableCell label="Team">
                    <TeamComposition
                      members={run.members}
                      currentRegion={region}
                      currentRealm={realm}
                    />
                  </TableCell>
                  <TableCell label="Date">{date}</TableCell>
                </TableRow>
              );
            })
          ) : (
            <div class="table-empty">
              <p>No runs found for this leaderboard.</p>
            </div>
          )}
        </Table>
      ) : (
        <Table mode={tableMode}>
          <div slot="header" class="table-header-cell">
            Rank
          </div>
          <div slot="header" class="table-header-cell">
            Player
          </div>
          <div slot="header" class="table-header-cell">
            Realm
          </div>
          {showTimeColumn && (
            <div slot="header" class="table-header-cell">
              Combined Time
            </div>
          )}
          {showRunsColumn && (
            <div slot="header" class="table-header-cell">
              Total Runs
            </div>
          )}
          {showAltsColumn && (
            <div slot="header" class="table-header-cell">
              Alts
            </div>
          )}

          {rows.length > 0 ? (
            rows.map((row, index) => {
              const rank = (currentPage - 1) * pageSize + index + 1;
              const time = row.combined_best_time
                ? formatTime(row.combined_best_time)
                : "N/A";
              const totalRuns = row.total_runs ?? 0;
              const bracketClass = row.ranking_percentile
                ? `bracket-${row.ranking_percentile.toLowerCase()}`
                : "";

              return (
                <TableRow>
                  <TableCell label="Rank">
                    <span class:list={["rank", "ranking-number", bracketClass]}>
                      {rank}
                    </span>
                  </TableCell>
                  <TableCell label="Player">
                    <PlayerLink
                      name={row.name}
                      specId={row.main_spec_id || 0}
                      region={row.region}
                      realmSlug={row.realm_slug}
                    />
                  </TableCell>
                  <TableCell label="Realm">{row.realm_name}</TableCell>
                  {showTimeColumn && (
                    <TableCell label="Combined Time">{time}</TableCell>
                  )}
                  {showRunsColumn && (
                    <TableCell label="Total Runs">{totalRuns}</TableCell>
                  )}
                  {showAltsColumn && (
                    <TableCell label="Alts">
                      <AccountAlts
                        alts={row.alts ?? []}
                        align="right"
                        emptyLabel=""
                      />
                    </TableCell>
                  )}
                </TableRow>
              );
            })
          ) : (
            <div class="table-empty">
              <p>
                No {isAccountMode ? "accounts" : "players"} found for this
                leaderboard.
              </p>
            </div>
          )}
        </Table>
      )
    }
  </div>
</div>

---
import type {
  AccountLeaderboardEntry,
  ChallengeRun,
  Player,
} from "../../../lib/types";
import Table from "../../Table/Table.astro";
import TableRow from "../../Table/TableRow.a
stro";
import TableCell from "../../Table/TableCell
.astro";
import TeamComposition from "../TeamComposit
ion/TeamComposition.astro";
import PlayerLink from "../PlayerLink/Player
Link.astro";
import AccountAlts from "../../AccountAlts/A
ccountAlts.astro";
import { formatDurationMMSS } from "../../..
/lib/utils";
import DungeonTime from "../../DungeonTime/D
ungeonTime.astro";
import "./LeaderboardTable.scss";

// "account" reuses the player layout + adds
 an Alts column. Keeping it here
// (rather than a parallel AccountLeaderboar
dTable) means rank/realm/time
// styling, mobile responsive rules, and the
 bracket coloring are all shared.
// "account-total-runs" is the same row shap
e as "account" but shows Runs
// instead of Time (cross-season total runs 
sum).
type LeaderboardType =
  | "dungeon"
  | "player"
  | "account"
  | "total-runs"
  | "account-total-runs";

interface Props {
  type: LeaderboardType;
  runs?: ChallengeRun[];
  players?: Player[];
  accounts?: AccountLeaderboardEntry[];
  currentPage?: number;
  pageSize?: number;
  // data attributes for client-side hydrati
on
  region?: string;
  realm?: string;
  dungeon?: string;
}

const {
  type,
  runs = [],
  players = [],
  accounts = [],
  currentPage = 1,
  pageSize = 25,
  region = "",
  realm = "",
  dungeon = "",
} = Astro.props;

const isAccountCombined = type === "account"
;
const isAccountRuns = type === "account-tota
l-runs";
const isAccountMode = isAccountCombined || i
sAccountRuns;
const isPlayerLikeTable =
  type === "player" || type === "total-runs"
 || isAccountMode;
const showTimeColumn = type === "player" || 
isAccountCombined;
const showRunsColumn = type === "total-runs"
 || isAccountRuns;
const showAltsColumn = isAccountMode;
// Wider grid + Alts column when in account 
mode; otherwise the standard player grid.
const tableMode = isAccountMode ? "accounts"
 : "players";

// Normalise account rows into the same play
er-shaped tuple the row template
// already knows how to render. Keeps a sing
le iteration loop instead of
// branching on type per cell.
type Row = {
  name: string;
  realm_slug: string;
  realm_name?: string;
  region: string;
  main_spec_id?: number;
  combined_best_time?: number;
  total_runs?: number;
  ranking_percentile?: string;
  alts?: AccountLeaderboardEntry["alts"];
};
const rows: Row[] = isAccountMode
  ? accounts.map((a) => ({
      name: a.main.name,
      realm_slug: a.main.realm_slug,
      realm_name: a.main.realm_name ?? a.mai
n.realm_slug,
      region: a.main.region,
      main_spec_id: a.main.main_spec_id,
      combined_best_time: a.account_combined
_best_time,
      total_runs: a.total_runs,
      ranking_percentile: a.bracket,
      alts: a.alts,
    }))
  : (players as Row[]);

const formatDate = (timestamp: number) => {
  return new Date(timestamp).toLocaleDateStr
ing("en-US", {
    month: "short",
    day: "numeric",
    year: "numeric",
  });
};

const formatTime = (ms: number) => {
  return formatDurationMMSS(ms);
};
---

<div
  class="leaderboard-table-container"
  data-type={type}
  data-region={region}
  data-realm={realm}
  data-dungeon={dungeon}
>
  <div class="leaderboard-content">
    {
      !isPlayerLikeTable ? (
        <Table mode="leaderboard">
          <div slot="header" class="table-he
ader-cell">
            Rank
          </div>
          <div slot="header" class="table-he
ader-cell">
            Time
          </div>
          <div slot="header" class="table-he
ader-cell">
            Team
          </div>
          <div slot="header" class="table-he
ader-cell">
            Date
          </div>

          {runs.length > 0 ? (
            runs.map((run, index) => {
              const rank = (currentPage - 1)
 * pageSize + index + 1;
              const date = formatDate(run.co
mpleted_timestamp);
              const bracketClass = run.ranki
ng_percentile
                ? `bracket-${run.ranking_per
centile.toLowerCase()}`
                : "";

              return (
                <TableRow>
                  <TableCell label="Rank">
                    <span class:list={["rank
", "ranking-number", bracketClass]}>
                      {rank}
                    </span>
                  </TableCell>
                  <TableCell label="Time">
                    <DungeonTime
                      dungeonId={Number(dung
eon) || null}
                      dungeonSlug={dungeon}
                      durationMs={run.durati
on}
                    />
                  </TableCell>
                  <TableCell label="Team">
                    <TeamComposition
                      members={run.members}
                      currentRegion={region}
                      currentRealm={realm}
                    />
                  </TableCell>
                  <TableCell label="Date">{d
ate}</TableCell>
                </TableRow>
              );
            })
          ) : (
            <div class="table-empty">
              <p>No runs found for this lead
erboard.</p>
            </div>
          )}
        </Table>
      ) : (
        <Table mode={tableMode}>
          <div slot="header" class="table-he
ader-cell">
            Rank
          </div>
          <div slot="header" class="table-he
ader-cell">
            Player
          </div>
          <div slot="header" class="table-he
ader-cell">
            Realm
          </div>
          {showTimeColumn && (
            <div slot="header" class="table-
header-cell">
              Combined Time
            </div>
          )}
          {showRunsColumn && (
            <div slot="header" class="table-
header-cell">
              Total Runs
            </div>
          )}
          {showAltsColumn && (
            <div slot="header" class="table-
header-cell">
              Alts
            </div>
          )}

          {rows.length > 0 ? (
            rows.map((row, index) => {
              const rank = (currentPage - 1)
 * pageSize + index + 1;
              const time = row.combined_best
_time
                ? formatTime(row.combined_be
st_time)
                : "N/A";
              const totalRuns = row.total_ru
ns ?? 0;
              const bracketClass = row.ranki
ng_percentile
                ? `bracket-${row.ranking_per
centile.toLowerCase()}`
                : "";

              return (
                <TableRow>
                  <TableCell label="Rank">
                    <span class:list={["rank
", "ranking-number", bracketClass]}>
                      {rank}
                    </span>
                  </TableCell>
                  <TableCell label="Player">
                    <PlayerLink
                      name={row.name}
                      specId={row.main_spec_
id || 0}
                      region={row.region}
                      realmSlug={row.realm_s
lug}
                    />
                  </TableCell>
                  <TableCell label="Realm">{
row.realm_name}</TableCell>
                  {showTimeColumn && (
                    <TableCell label="Combin
ed Time">{time}</TableCell>
                  )}
                  {showRunsColumn && (
                    <TableCell label="Total 
Runs">{totalRuns}</TableCell>
                  )}
                  {showAltsColumn && (
                    <TableCell label="Alts">
                      <AccountAlts
                        alts={row.alts ?? []
}
                        align="right"
                        emptyLabel=""
                      />
                    </TableCell>
                  )}
                </TableRow>
              );
            })
          ) : (
            <div class="table-empty">
              <p>
                No {isAccountMode ? "account
s" : "players"} found for this
                leaderboard.
              </p>
            </div>
          )}
        </Table>
      )
    }
  </div>
</div>
 
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET