Module:TeamDashboard: Difference between revisions
From eSportsAmaze
More actions
Esportsamaze (talk | contribs) No edit summary |
Esportsamaze (talk | contribs) No edit summary |
||
| Line 3: | Line 3: | ||
local html = mw.html | local html = mw.html | ||
local lang = mw.getContentLanguage() | local lang = mw.getContentLanguage() | ||
local function esc(s) | local function esc(s) | ||
| Line 46: | Line 44: | ||
end | end | ||
local roleConfig = { | local roleConfig = { | ||
{key="igl", sort=1, color="#eab308"}, | {key="igl", sort=1, color="#eab308"}, | ||
| Line 97: | Line 94: | ||
local sqlTeam = esc(teamName) | local sqlTeam = esc(teamName) | ||
-- ── 1. Team data | -- ── 1. Team data ────────────────────────────────────────────── | ||
local tRows = cargo.query("Teams", | local tRows = cargo.query("Teams", | ||
"display_name,short_code,game,image,image_dark,country,status," | "display_name,short_code,game,image,image_dark,country,status," | ||
| Line 103: | Line 100: | ||
{where="name='"..sqlTeam.."'", limit=1}) | {where="name='"..sqlTeam.."'", limit=1}) | ||
local tm = (tRows and #tRows>0) and tRows[1] or {} | local tm = (tRows and #tRows>0) and tRows[1] or {} | ||
local displayName = (tm.display_name and tm.display_name~="") | local displayName = (tm.display_name and tm.display_name~="") | ||
and tm.display_name or teamName | and tm.display_name or teamName | ||
local teamGame = tm.game or "" | |||
-- ── 2. Krafton rank ─────────────────────────────────────────── | -- ── 2. Krafton rank ─────────────────────────────────────────── | ||
| Line 120: | Line 117: | ||
-- ── 4. Active roster ───────────────────────────────────────── | -- ── 4. Active roster ───────────────────────────────────────── | ||
local dbPlayers = {} | local dbPlayers = {} | ||
local dbRows = cargo.query("Players","id,real_name,role,nationality,image,_pageName", | local dbRows = cargo.query("Players","id,real_name,role,nationality,image,_pageName", | ||
| Line 133: | Line 129: | ||
end | end | ||
end | end | ||
local roster = {} | local roster = {} | ||
for _,p2 in pairs(dbPlayers) do table.insert(roster,p2) end | for _,p2 in pairs(dbPlayers) do table.insert(roster,p2) end | ||
| Line 140: | Line 135: | ||
if mid and not dbPlayers[mid] then | if mid and not dbPlayers[mid] then | ||
table.insert(roster,{ | table.insert(roster,{ | ||
id | id=mid, name=clean(args["name"..i]) or "", | ||
role=clean(args["role"..i]) or "Player", | |||
role | nat=clean(args["flag"..i]) or "", | ||
nat | image=clean(args["image"..i]) or "", link=mid, | ||
image = clean(args["image"..i]) or "", | |||
}) | }) | ||
end | end | ||
end | end | ||
table.sort(roster,function(a,b) | table.sort(roster,function(a,b) | ||
local sa,sb = getRoleSort(a.role),getRoleSort(b.role) | local sa,sb=getRoleSort(a.role),getRoleSort(b.role) | ||
if sa~=sb then return sa<sb end | if sa~=sb then return sa<sb end | ||
return (a.id or ""):lower()<(b.id or ""):lower() | return (a.id or ""):lower()<(b.id or ""):lower() | ||
end) | end) | ||
-- ── 5. Former players | -- ── 5. Former players ───────────────────────────────────────── | ||
local fRows = cargo.query("Player_Former_Teams", | local fRows = cargo.query("Player_Former_Teams", | ||
"player_id,role,join_date,leave_date", | "player_id,role,join_date,leave_date", | ||
{ where | {where="team='"..sqlTeam.."' AND leave_date!=''", | ||
orderBy="leave_date DESC", limit=200}) | |||
fRows = fRows or {} | fRows = fRows or {} | ||
local pageNames = {} | local pageNames = {} | ||
for _,r in ipairs(fRows) do | for _,r in ipairs(fRows) do | ||
| Line 174: | Line 164: | ||
if #pageNames>0 then | if #pageNames>0 then | ||
local dRows = cargo.query("Players","_pageName,real_name,id", | local dRows = cargo.query("Players","_pageName,real_name,id", | ||
{ where="_pageName IN ("..table.concat(pageNames,",")..")", limit=200 }) | {where="_pageName IN ("..table.concat(pageNames,",")..")", limit=200}) | ||
if dRows then | if dRows then | ||
for _,d in ipairs(dRows) do | for _,d in ipairs(dRows) do | ||
| Line 181: | Line 171: | ||
end | end | ||
end | end | ||
local formerRows = {} | local formerRows = {} | ||
for _,r in ipairs(fRows) do | for _,r in ipairs(fRows) do | ||
local d = playerDetails[r.player_id] or {} | local d = playerDetails[r.player_id] or {} | ||
table.insert(formerRows, { | table.insert(formerRows,{ | ||
pid | pid=r.player_id, role=r.role, | ||
join_date=r.join_date, leave_date=r.leave_date, | |||
join_date | real_name=d.real_name or "", short_id=d.short_id or "", | ||
real_name | |||
}) | }) | ||
end | end | ||
-- Group by year | |||
-- Group by year | |||
local formerByYear,yearOrder,yearSeen = {},{},{} | local formerByYear,yearOrder,yearSeen = {},{},{} | ||
for _,r in ipairs(formerRows) do | for _,r in ipairs(formerRows) do | ||
local year = (r.leave_date or ""):match("^(%d%d%d%d)") or "Unknown" | local year=(r.leave_date or ""):match("^(%d%d%d%d)") or "Unknown" | ||
if not yearSeen[year] then yearSeen[year]=true; table.insert(yearOrder,year) end | if not yearSeen[year] then yearSeen[year]=true; table.insert(yearOrder,year) end | ||
if not formerByYear[year] then formerByYear[year]={} end | if not formerByYear[year] then formerByYear[year]={} end | ||
| Line 207: | Line 193: | ||
end) | end) | ||
-- ── 6. Achievements | -- ── 6. Achievements — filtered by team's game ───────────────── | ||
local | -- PrizeMoney has no game field, so we get tournament names first | ||
-- then cross-check with Tournaments.game | |||
local achRaw = cargo.query("PrizeMoney","tournament,placement,prize,award", | |||
{where="team='"..sqlTeam.."' AND (player='' OR player IS NULL)", | {where="team='"..sqlTeam.."' AND (player='' OR player IS NULL)", | ||
orderBy="prize DESC", limit= | orderBy="prize DESC", limit=100}) | ||
achRaw = achRaw or {} | |||
-- | -- Collect tournament names to fetch metadata | ||
local tournNames,tournSeen2 = {},{} | local tournNames,tournSeen2 = {},{} | ||
for _,r in ipairs( | for _,r in ipairs(achRaw) do | ||
if r.tournament and r.tournament~="" and not tournSeen2[r.tournament] then | if r.tournament and r.tournament~="" and not tournSeen2[r.tournament] then | ||
tournSeen2[r.tournament]=true | tournSeen2[r.tournament]=true | ||
| Line 221: | Line 209: | ||
end | end | ||
end | end | ||
-- Fetch tier + date + game for all tournaments | |||
local tournMeta = {} | local tournMeta = {} | ||
if #tournNames>0 then | if #tournNames>0 then | ||
local mRows = cargo.query("Tournaments","name,tier,end_date", | local mRows = cargo.query("Tournaments","name,tier,end_date,game", | ||
{where="name IN ("..table.concat(tournNames,",")..")", limit=200}) | {where="name IN ("..table.concat(tournNames,",")..")", limit=200}) | ||
if mRows then | if mRows then | ||
for _,row in ipairs(mRows) do | for _,row in ipairs(mRows) do | ||
tournMeta[row.name]={tier=row.tier, date=row.end_date} | tournMeta[row.name]={tier=row.tier, date=row.end_date, game=row.game} | ||
end | |||
end | |||
end | |||
-- Filter achievements: only keep rows where tournament game matches team game | |||
-- If team game is blank, show all (safe fallback) | |||
local achRows = {} | |||
for _,r in ipairs(achRaw) do | |||
if teamGame=="" then | |||
table.insert(achRows,r) | |||
else | |||
local meta = tournMeta[r.tournament or ""] or {} | |||
local tGame = (meta.game or ""):lower() | |||
local tTeam = teamGame:lower() | |||
-- match if game contains the team's game string (e.g. "bgmi" in "Battlegrounds Mobile India") | |||
if tGame=="" or tGame:find(tTeam,1,true) or tTeam:find(tGame,1,true) then | |||
table.insert(achRows,r) | |||
end | end | ||
end | end | ||
| Line 237: | Line 244: | ||
-- ════ HERO ════ | -- ════ HERO ════ | ||
local hero = root:tag('div'):addClass('td-hero') | local hero = root:tag('div'):addClass('td-hero') | ||
local lightLogo=(tm.image and tm.image~="") and tm.image or "Shield_team.png" | |||
local lightLogo = (tm.image | local darkLogo =(tm.image_dark and tm.image_dark~="") and tm.image_dark or lightLogo | ||
local darkLogo | local logoDiv = hero:tag('div'):addClass('td-hero-logo') | ||
local logoDiv | |||
logoDiv:wikitext("[[File:"..lightLogo.."|72px|link=|class=logo-lightmode]]") | logoDiv:wikitext("[[File:"..lightLogo.."|72px|link=|class=logo-lightmode]]") | ||
logoDiv:wikitext("[[File:"..darkLogo .."|72px|link=|class=logo-darkmode]]") | logoDiv:wikitext("[[File:"..darkLogo .."|72px|link=|class=logo-darkmode]]") | ||
local heroInfo = hero:tag('div'):addClass('td-hero-info') | local heroInfo = hero:tag('div'):addClass('td-hero-info') | ||
heroInfo:tag('div'):addClass('td-hero-name'):wikitext(displayName) | heroInfo:tag('div'):addClass('td-hero-name'):wikitext(displayName) | ||
local tags = heroInfo:tag('div'):addClass('pd-hero-tags') | local tags = heroInfo:tag('div'):addClass('pd-hero-tags') | ||
local sl = (tm.status or "active"):lower() | local sl=(tm.status or "active"):lower() | ||
local stColor,stBg,stBorder | local stColor,stBg,stBorder | ||
if sl=="active" then | if sl=="active" then | ||
| Line 266: | Line 269: | ||
:css('border','1px solid '..stBorder) | :css('border','1px solid '..stBorder) | ||
:wikitext('● '..(tm.status or "Active")) | :wikitext('● '..(tm.status or "Active")) | ||
if | if teamGame~="" then | ||
tags:tag('span'):addClass('pd-tag pd-tag-game'):wikitext( | tags:tag('span'):addClass('pd-tag pd-tag-game'):wikitext(teamGame) | ||
end | end | ||
if rankVal then | if rankVal then | ||
| Line 277: | Line 280: | ||
end | end | ||
if totalEarnings>0 then | if totalEarnings>0 then | ||
local er = hero:tag('div'):addClass('pd-hero-right') | local er = hero:tag('div'):addClass('pd-hero-right') | ||
| Line 288: | Line 290: | ||
local body = root:tag('div'):addClass('td-body') | local body = root:tag('div'):addClass('td-body') | ||
-- ── LEFT | -- ── LEFT ───────────────────────────────────────────────────── | ||
local left = body:tag('div'):addClass('td-left') | local left = body:tag('div'):addClass('td-left') | ||
left:tag('div'):addClass('pd-section-title'):wikitext('Team Info') | left:tag('div'):addClass('pd-section-title'):wikitext('Team Info') | ||
| Line 302: | Line 304: | ||
infoRow('Country', getFlag(tm.country)..tm.country) | infoRow('Country', getFlag(tm.country)..tm.country) | ||
end | end | ||
infoRow('Game', | infoRow('Game', teamGame) | ||
if tm.short_code and tm.short_code~="" then | if tm.short_code and tm.short_code~="" then infoRow('Tag', tm.short_code) end | ||
if rankVal then infoRow('Krafton Rank', rankVal) end | if rankVal then infoRow('Krafton Rank', rankVal) end | ||
if totalEarnings>0 then | if totalEarnings>0 then infoRow('Total Earnings', fmtCurrency(totalEarnings)) end | ||
if tm.sponsors and tm.sponsors~="" then | if tm.sponsors and tm.sponsors~="" then | ||
infoRow('Sponsors', tm.sponsors:gsub(",",", ")) | infoRow('Sponsors', tm.sponsors:gsub(",",", ")) | ||
end | end | ||
local playerCount = 0 | local playerCount=0 | ||
for _,pl in ipairs(roster) do | for _,pl in ipairs(roster) do | ||
local r = (pl.role or ""):lower() | local r=(pl.role or ""):lower() | ||
if not r:find("coach") and not r:find("analyst") and not r:find("manager") then | if not r:find("coach") and not r:find("analyst") and not r:find("manager") then | ||
playerCount = playerCount+1 | playerCount=playerCount+1 | ||
end | end | ||
end | end | ||
if playerCount>0 then | if playerCount>0 then infoRow('Active Players', tostring(playerCount)) end | ||
if #formerRows>0 then infoRow('Alumni', tostring(#formerRows)) end | |||
if #formerRows>0 then | |||
-- Socials | -- Socials | ||
local socials = { | local socials = { | ||
{k='instagram', file='Icon_instagram.png', base='https://instagram.com/'}, | {k='instagram',file='Icon_instagram.png',base='https://instagram.com/'}, | ||
{k='twitter', | {k='twitter', file='Icon_twitter.png', base='https://twitter.com/' }, | ||
{k='youtube', | {k='youtube', file='Icon_youtube.png', base='https://youtube.com/' }, | ||
{k='discord', | {k='discord', file='Icon_discord.png', base='https://discord.gg/' }, | ||
{k='facebook', | {k='facebook', file='Icon_facebook.png', base='https://facebook.com/' }, | ||
{k='website', | {k='website', file='Icon_website.png', base='https://' }, | ||
} | } | ||
local hasSocial = false | local hasSocial=false | ||
for _,s in ipairs(socials) do | for _,s in ipairs(socials) do | ||
if tm[s.k] and tm[s.k]~="" then hasSocial=true; break end | if tm[s.k] and tm[s.k]~="" then hasSocial=true; break end | ||
end | end | ||
if hasSocial then | if hasSocial then | ||
local socDiv = left:tag('div'):addClass('pd-socials') | local socDiv=left:tag('div'):addClass('pd-socials') | ||
for _,s in ipairs(socials) do | for _,s in ipairs(socials) do | ||
local v = tm[s.k] | local v=tm[s.k] | ||
if v and v~="" then | if v and v~="" then | ||
local url = v:match("^https?://") and v or (s.base..v) | local url=v:match("^https?://") and v or (s.base..v) | ||
socDiv:wikitext("[[File:"..s.file.."|32px|link="..url.."|class=social-img]]") | socDiv:wikitext("[[File:"..s.file.."|32px|link="..url.."|class=social-img]]") | ||
end | end | ||
| Line 352: | Line 346: | ||
end | end | ||
-- ── RIGHT | -- ── RIGHT ───────────────────────────────────────────────────── | ||
local right = body:tag('div'):addClass('td-right') | local right = body:tag('div'):addClass('td-right') | ||
-- ── Active Roster ───────────────────────────────────────────── | -- ── Active Roster ───────────────────────────────────────────── | ||
right:tag('div'):addClass('pd-table-title'):wikitext('👥 Active Roster') | right:tag('div'):addClass('pd-table-title'):wikitext('👥 Active Roster') | ||
if #roster>0 then | if #roster>0 then | ||
local grid = right:tag('div'):addClass('hero-roster-grid') | local grid=right:tag('div'):addClass('hero-roster-grid') | ||
for _,pl in ipairs(roster) do | for _,pl in ipairs(roster) do | ||
local card | local card=grid:tag('div'):addClass('hero-player-card') | ||
local color = getRoleColor(pl.role | local color=getRoleColor(pl.role) | ||
local imgDiv=card:tag('div'):addClass('hero-card-image') | |||
local imgFile=(pl.image~="") and pl.image or "Player_Placeholder.png" | |||
local imgDiv = card:tag('div'):addClass('hero-card-image') | |||
local imgFile = (pl.image~="") and pl.image or "Player_Placeholder.png" | |||
imgDiv:wikitext("[[File:"..imgFile.."|link="..(pl.link or pl.id).."]]") | imgDiv:wikitext("[[File:"..imgFile.."|link="..(pl.link or pl.id).."]]") | ||
local cb=card:tag('div'):addClass('hero-card-body') | |||
local cb = card:tag('div'):addClass('hero-card-body') | |||
cb:tag('div'):addClass('hero-role-pill') | cb:tag('div'):addClass('hero-role-pill') | ||
:css('background-color',color):wikitext(pl.role or "Player") | :css('background-color',color):wikitext(pl.role or "Player") | ||
| Line 388: | Line 377: | ||
-- ── Former Players ──────────────────────────────────────────── | -- ── Former Players ──────────────────────────────────────────── | ||
-- Pure CSS tabs | -- Pure CSS tabs: hidden radio inputs + label bar + panels | ||
-- | -- All inputs, labels, and panels are direct siblings of .td-tabs-wrap children | ||
-- | -- so the :checked ~ sibling selector works correctly | ||
if #formerRows>0 then | if #formerRows>0 then | ||
right:tag('div'):addClass('pd-table-title td-section-gap') | right:tag('div'):addClass('pd-table-title td-section-gap') | ||
:wikitext('📋 Former Players') | :wikitext('📋 Former Players') | ||
local tabId = 'tdfp'..teamName:gsub("[^%w]","" | -- Safe ID: only alphanumeric | ||
local tabId='tdfp'..teamName:gsub("[^%w]","") | |||
-- | -- Build raw HTML string (mw.html escapes inputs/labels incorrectly) | ||
local tabHtml = '<div class="td-tabs-wrap">' | |||
-- Radio inputs first (must be siblings of label bar and panels) | |||
for i,year in ipairs(yearOrder) do | for i,year in ipairs(yearOrder) do | ||
local checked = (i==1) and ' checked="checked"' or '' | |||
local checked = (i==1) and ' checked' or '' | tabHtml = tabHtml..'<input type="radio" class="td-radio" ' | ||
..'name="'..tabId..'" id="'..tabId..year..'"'..checked..'/>' | |||
end | end | ||
-- Label bar | -- Label bar | ||
tabHtml = tabHtml..'<div class="td-tab-bar">' | |||
for | for _,year in ipairs(yearOrder) do | ||
tabHtml = tabHtml..'<label class="td-tab-btn" for="' | |||
..tabId..year..'">'..year..'</label>' | |||
end | end | ||
tabHtml = tabHtml..'</div>' | |||
-- | -- Panels | ||
tabHtml = tabHtml..'<div class="td-panels">' | |||
for _,year in ipairs(yearOrder) do | for _,year in ipairs(yearOrder) do | ||
tabHtml = tabHtml..'<div class="td-year-panel" data-year="'..year..'">' | |||
-- Table header | |||
tabHtml = tabHtml..'<table class="pd-tourn-table td-former-table">' | |||
..'<tr><th>Player</th><th>Name</th><th>Role</th>' | |||
..'<th>Joined</th><th>Left</th></tr>' | |||
for _,r in ipairs(formerByYear[year]) do | for _,r in ipairs(formerByYear[year]) do | ||
local pid | local pid=r.pid or "" | ||
local dispId = (r.short_id and r.short_id~="") | local dispId=(r.short_id and r.short_id~="") | ||
and r.short_id or pid:gsub("^.*/","") | and r.short_id or pid:gsub("^.*/","") | ||
local | local roleColor=getRoleColor(r.role) | ||
local roleBadge="" | |||
local | |||
if r.role and r.role~="" then | if r.role and r.role~="" then | ||
roleBadge='<span class="td-role-badge" style="background:' | |||
..roleColor..'">'..r.role..'</span>' | |||
else | else roleBadge="—" end | ||
tr: | tabHtml = tabHtml..'<tr>' | ||
..'<td style="font-weight:700">[['..'<nowiki/>'..pid..'|'..dispId..']]</td>' | |||
..'<td>'..(r.real_name~="" and r.real_name or "—")..'</td>' | |||
..'<td>'..roleBadge..'</td>' | |||
..'<td class="ac-date">'..(r.join_date or "?")..'</td>' | |||
..'<td class="ac-date">'..(r.leave_date or "?")..'</td>' | |||
..'</tr>' | |||
end | end | ||
tabHtml = tabHtml..'</table></div>' | |||
end | end | ||
tabHtml = tabHtml..'</div>' -- td-panels | |||
-- Inline CSS | -- Inline CSS: one rule per year for :checked → show panel + highlight label | ||
tabHtml = tabHtml..'<style>' | |||
tabHtml = tabHtml..'.td-panels .td-year-panel{display:none}' | |||
for _,year in ipairs(yearOrder) do | for _,year in ipairs(yearOrder) do | ||
local id=tabId..year | |||
-- Show panel | |||
tabHtml = tabHtml..'#'..id..':checked~.td-tab-bar~.td-panels' | |||
-- | ..' .td-year-panel[data-year="'..year.."\"]{display:block}" | ||
-- Highlight label | |||
tabHtml = tabHtml..'#'..id..':checked~.td-tab-bar' | |||
..' label[for="'..id..'"]' | |||
..'{background:rgba(37,99,235,.25);color:#93c5fd;' | ..'{background:rgba(37,99,235,.25);color:#93c5fd;' | ||
..'border-color:rgba(37,99,235,.5)}' | ..'border-color:rgba(37,99,235,.5)}' | ||
end | end | ||
tabHtml = tabHtml..'</style></div>' -- close td-tabs-wrap | |||
right:wikitext(tabHtml) | |||
end | end | ||
-- ── Achievements | -- ── Achievements ────────────────────────────────────────────── | ||
right:tag('div'):addClass('pd-table-title td-section-gap') | right:tag('div'):addClass('pd-table-title td-section-gap') | ||
:wikitext('🏆 Achievements') | :wikitext('🏆 Achievements') | ||
if #achRows>0 then | if #achRows>0 then | ||
local tbl2 = right:tag('table'):addClass('pd-tourn-table') | local tbl2=right:tag('table'):addClass('pd-tourn-table') | ||
local hdr2 = tbl2:tag('tr') | local hdr2=tbl2:tag('tr') | ||
hdr2:tag('th'):wikitext('Date') | hdr2:tag('th'):wikitext('Date') | ||
hdr2:tag('th'):wikitext('Tier') | hdr2:tag('th'):wikitext('Tier') | ||
| Line 478: | Line 469: | ||
for _,r in ipairs(achRows) do | for _,r in ipairs(achRows) do | ||
local meta = tournMeta[r.tournament or ""] or {} | local meta=tournMeta[r.tournament or ""] or {} | ||
local tr2 | local tr2=tbl2:tag('tr') | ||
tr2:tag('td'):addClass('ac-date') | tr2:tag('td'):addClass('ac-date') | ||
:wikitext(meta.date and meta.date~="" and meta.date or "—") | :wikitext(meta.date and meta.date~="" and meta.date or "—") | ||
local tierTd = tr2:tag('td'):addClass('ac-tier') | local tierTd=tr2:tag('td'):addClass('ac-tier') | ||
if meta.tier and meta.tier~="" then | if meta.tier and meta.tier~="" then | ||
tierTd:tag('span'):addClass('tier-badge '..getTierClass(meta.tier)) | tierTd:tag('span'):addClass('tier-badge '..getTierClass(meta.tier)) | ||
| Line 489: | Line 480: | ||
tr2:tag('td'):css('font-weight','600') | tr2:tag('td'):css('font-weight','600') | ||
:wikitext("[["..(r.tournament or "").."]]") | :wikitext("[["..(r.tournament or "").."]]") | ||
local pTd = tr2:tag('td'):addClass('ac-place') | local pTd=tr2:tag('td'):addClass('ac-place') | ||
if r.award and r.award~="" then | if r.award and r.award~="" then | ||
pTd:tag('span'):addClass('pd-award-badge'):wikitext(r.award) | pTd:tag('span'):addClass('pd-award-badge'):wikitext(r.award) | ||
else | else renderPlaceBadge(pTd,r.placement) end | ||
tr2:tag('td'):addClass('ac-prize') | tr2:tag('td'):addClass('ac-prize') | ||
:wikitext(tonumber(r.prize) and tonumber(r.prize)>0 | :wikitext(tonumber(r.prize) and tonumber(r.prize)>0 | ||
Revision as of 03:30, 9 March 2026
Documentation for this module may be created at Module:TeamDashboard/doc
local p = {}
local cargo = mw.ext.cargo
local html = mw.html
local lang = mw.getContentLanguage()
local function esc(s)
if not s then return "" end
return s:gsub("\\","\\\\"):gsub("'","\\'")
end
local function clean(s)
if not s then return nil end
local c = s:gsub("[\r\n]",""):gsub("^%s*(.-)%s*$","%1")
return c ~= "" and c or nil
end
local function fmtCurrency(n)
if not n or n == 0 then return "—" end
local num = math.floor(tonumber(n) or 0)
local s = tostring(num)
if #s <= 3 then return "₹ " .. s end
local result = s:sub(-3)
local rem = s:sub(1,-4)
while #rem > 2 do result = rem:sub(-2)..","..result; rem = rem:sub(1,-3) end
return "₹ " .. rem .. "," .. result
end
local function getTierClass(tier)
if not tier then return "tier-def" end
local t = tier:lower()
if t:find("s") and t:find("tier") then return "tier-s" end
if t:find("a") and t:find("tier") then return "tier-a" end
if t:find("b") and t:find("tier") then return "tier-b" end
if t:find("c") and t:find("tier") then return "tier-c" end
return "tier-def"
end
local FLAGS = {
india="🇮🇳",bangladesh="🇧🇩",pakistan="🇵🇰",nepal="🇳🇵",
srilanka="🇱🇰",myanmar="🇲🇲",indonesia="🇮🇩",thailand="🇹🇭",
malaysia="🇲🇾",philippines="🇵🇭",vietnam="🇻🇳",singapore="🇸🇬",
china="🇨🇳",japan="🇯🇵",korea="🇰🇷",usa="🇺🇸",
uk="🇬🇧",germany="🇩🇪",brazil="🇧🇷",australia="🇦🇺"
}
local function getFlag(c)
if not c or c=="" then return "" end
return (FLAGS[c:lower():gsub("%s+",""):gsub("-","")] or "").." "
end
local roleConfig = {
{key="igl", sort=1, color="#eab308"},
{key="filter", sort=2, color="#14b8a6"},
{key="entry", sort=3, color="#f97316"},
{key="assault", sort=4, color="#3b82f6"},
{key="fragger", sort=4, color="#3b82f6"},
{key="support", sort=5, color="#22c55e"},
{key="medic", sort=5, color="#22c55e"},
{key="scout", sort=6, color="#14b8a6"},
{key="sniper", sort=7, color="#ef4444"},
{key="coach", sort=8, color="#a855f7"},
{key="analyst", sort=9, color="#a855f7"},
{key="manager", sort=10, color="#1f2937"},
}
local function getRoleColor(role)
if not role then return "#64748b" end
local r = role:lower()
for _,cfg in ipairs(roleConfig) do
if r:find(cfg.key) then return cfg.color end
end
return "#64748b"
end
local function getRoleSort(role)
if not role then return 99 end
local r = role:lower()
for _,cfg in ipairs(roleConfig) do
if r:find(cfg.key) then return cfg.sort end
end
return 99
end
local function renderPlaceBadge(td, placement)
local rank = tonumber(placement) or 99
local placeText = tostring(placement or "—")
local badgeClass = "place-def"
if rank==1 then badgeClass="place-1"; placeText="1st"
elseif rank==2 then badgeClass="place-2"; placeText="2nd"
elseif rank==3 then badgeClass="place-3"; placeText="3rd"
elseif placeText:match("^%d+$") then placeText=placeText.."th"
end
td:tag('span'):addClass('place-badge '..badgeClass):wikitext(placeText)
end
-- ── Main ─────────────────────────────────────────────────────────
function p.main(frame)
local args = (frame:getParent() and frame:getParent().args) or frame.args
local teamName = clean(args.team) or mw.title.getCurrentTitle().subpageText
local sqlTeam = esc(teamName)
-- ── 1. Team data ──────────────────────────────────────────────
local tRows = cargo.query("Teams",
"display_name,short_code,game,image,image_dark,country,status,"
.."sponsors,instagram,twitter,youtube,facebook,discord,website",
{where="name='"..sqlTeam.."'", limit=1})
local tm = (tRows and #tRows>0) and tRows[1] or {}
local displayName = (tm.display_name and tm.display_name~="")
and tm.display_name or teamName
local teamGame = tm.game or ""
-- ── 2. Krafton rank ───────────────────────────────────────────
local rankVal = nil
local rRows = cargo.query("Krafton_Rankings","rank",
{where="name='"..sqlTeam.."' AND type='Team'", limit=1})
if rRows and #rRows>0 then rankVal="#"..rRows[1].rank end
-- ── 3. Total earnings ─────────────────────────────────────────
local totalEarnings = 0
local eRows = cargo.query("PrizeMoney","SUM(prize)=total",
{where="team='"..sqlTeam.."' AND (player='' OR player IS NULL)"})
if eRows and #eRows>0 then totalEarnings=tonumber(eRows[1].total) or 0 end
-- ── 4. Active roster ─────────────────────────────────────────
local dbPlayers = {}
local dbRows = cargo.query("Players","id,real_name,role,nationality,image,_pageName",
{where="current_team='"..sqlTeam.."' AND status='Active'", limit=20})
if dbRows then
for _,r in ipairs(dbRows) do
dbPlayers[r.id] = {
id=r.id, name=r.real_name or "", role=r.role or "Player",
nat=r.nationality or "", image=r.image or "",
link=r._pageName or r.id
}
end
end
local roster = {}
for _,p2 in pairs(dbPlayers) do table.insert(roster,p2) end
for i=1,10 do
local mid = clean(args["player"..i])
if mid and not dbPlayers[mid] then
table.insert(roster,{
id=mid, name=clean(args["name"..i]) or "",
role=clean(args["role"..i]) or "Player",
nat=clean(args["flag"..i]) or "",
image=clean(args["image"..i]) or "", link=mid,
})
end
end
table.sort(roster,function(a,b)
local sa,sb=getRoleSort(a.role),getRoleSort(b.role)
if sa~=sb then return sa<sb end
return (a.id or ""):lower()<(b.id or ""):lower()
end)
-- ── 5. Former players ─────────────────────────────────────────
local fRows = cargo.query("Player_Former_Teams",
"player_id,role,join_date,leave_date",
{where="team='"..sqlTeam.."' AND leave_date!=''",
orderBy="leave_date DESC", limit=200})
fRows = fRows or {}
local pageNames = {}
for _,r in ipairs(fRows) do
if r.player_id and r.player_id~="" then
table.insert(pageNames,"'"..esc(r.player_id).."'")
end
end
local playerDetails = {}
if #pageNames>0 then
local dRows = cargo.query("Players","_pageName,real_name,id",
{where="_pageName IN ("..table.concat(pageNames,",")..")", limit=200})
if dRows then
for _,d in ipairs(dRows) do
playerDetails[d._pageName]={real_name=d.real_name, short_id=d.id}
end
end
end
local formerRows = {}
for _,r in ipairs(fRows) do
local d = playerDetails[r.player_id] or {}
table.insert(formerRows,{
pid=r.player_id, role=r.role,
join_date=r.join_date, leave_date=r.leave_date,
real_name=d.real_name or "", short_id=d.short_id or "",
})
end
-- Group by year
local formerByYear,yearOrder,yearSeen = {},{},{}
for _,r in ipairs(formerRows) do
local year=(r.leave_date or ""):match("^(%d%d%d%d)") or "Unknown"
if not yearSeen[year] then yearSeen[year]=true; table.insert(yearOrder,year) end
if not formerByYear[year] then formerByYear[year]={} end
table.insert(formerByYear[year],r)
end
table.sort(yearOrder,function(a,b)
return (tonumber(a) or 0)>(tonumber(b) or 0)
end)
-- ── 6. Achievements — filtered by team's game ─────────────────
-- PrizeMoney has no game field, so we get tournament names first
-- then cross-check with Tournaments.game
local achRaw = cargo.query("PrizeMoney","tournament,placement,prize,award",
{where="team='"..sqlTeam.."' AND (player='' OR player IS NULL)",
orderBy="prize DESC", limit=100})
achRaw = achRaw or {}
-- Collect tournament names to fetch metadata
local tournNames,tournSeen2 = {},{}
for _,r in ipairs(achRaw) do
if r.tournament and r.tournament~="" and not tournSeen2[r.tournament] then
tournSeen2[r.tournament]=true
table.insert(tournNames,"'"..esc(r.tournament).."'")
end
end
-- Fetch tier + date + game for all tournaments
local tournMeta = {}
if #tournNames>0 then
local mRows = cargo.query("Tournaments","name,tier,end_date,game",
{where="name IN ("..table.concat(tournNames,",")..")", limit=200})
if mRows then
for _,row in ipairs(mRows) do
tournMeta[row.name]={tier=row.tier, date=row.end_date, game=row.game}
end
end
end
-- Filter achievements: only keep rows where tournament game matches team game
-- If team game is blank, show all (safe fallback)
local achRows = {}
for _,r in ipairs(achRaw) do
if teamGame=="" then
table.insert(achRows,r)
else
local meta = tournMeta[r.tournament or ""] or {}
local tGame = (meta.game or ""):lower()
local tTeam = teamGame:lower()
-- match if game contains the team's game string (e.g. "bgmi" in "Battlegrounds Mobile India")
if tGame=="" or tGame:find(tTeam,1,true) or tTeam:find(tGame,1,true) then
table.insert(achRows,r)
end
end
end
-- ── BUILD HTML ────────────────────────────────────────────────
local root = html.create('div'):addClass('td-dashboard')
-- ════ HERO ════
local hero = root:tag('div'):addClass('td-hero')
local lightLogo=(tm.image and tm.image~="") and tm.image or "Shield_team.png"
local darkLogo =(tm.image_dark and tm.image_dark~="") and tm.image_dark or lightLogo
local logoDiv = hero:tag('div'):addClass('td-hero-logo')
logoDiv:wikitext("[[File:"..lightLogo.."|72px|link=|class=logo-lightmode]]")
logoDiv:wikitext("[[File:"..darkLogo .."|72px|link=|class=logo-darkmode]]")
local heroInfo = hero:tag('div'):addClass('td-hero-info')
heroInfo:tag('div'):addClass('td-hero-name'):wikitext(displayName)
local tags = heroInfo:tag('div'):addClass('pd-hero-tags')
local sl=(tm.status or "active"):lower()
local stColor,stBg,stBorder
if sl=="active" then
stColor="#4ade80";stBg="rgba(34,197,94,.2)";stBorder="rgba(34,197,94,.35)"
elseif sl=="inactive" or sl=="disbanded" then
stColor="#f87171";stBg="rgba(239,68,68,.2)";stBorder="rgba(239,68,68,.35)"
else
stColor="#94a3b8";stBg="rgba(148,163,184,.2)";stBorder="rgba(148,163,184,.35)"
end
tags:tag('span'):addClass('pd-tag')
:css('color',stColor):css('background',stBg)
:css('border','1px solid '..stBorder)
:wikitext('● '..(tm.status or "Active"))
if teamGame~="" then
tags:tag('span'):addClass('pd-tag pd-tag-game'):wikitext(teamGame)
end
if rankVal then
tags:tag('span'):addClass('pd-tag')
:css('color','#fbbf24')
:css('background','rgba(251,191,36,.15)')
:css('border','1px solid rgba(251,191,36,.3)')
:wikitext('Krafton '..rankVal)
end
if totalEarnings>0 then
local er = hero:tag('div'):addClass('pd-hero-right')
local et = er:tag('div'):addClass('pd-earnings-total')
et:tag('div'):addClass('pd-earnings-label'):wikitext('Total Earnings')
et:tag('div'):addClass('pd-earnings-val'):wikitext(fmtCurrency(totalEarnings))
end
-- ════ BODY ════
local body = root:tag('div'):addClass('td-body')
-- ── LEFT ─────────────────────────────────────────────────────
local left = body:tag('div'):addClass('td-left')
left:tag('div'):addClass('pd-section-title'):wikitext('Team Info')
local function infoRow(label,val)
if not val or val=="" then return end
left:tag('div'):addClass('pd-info-row')
:tag('span'):addClass('pd-info-label'):wikitext(label):done()
:tag('span'):addClass('pd-info-val'):wikitext(val)
end
if tm.country and tm.country~="" then
infoRow('Country', getFlag(tm.country)..tm.country)
end
infoRow('Game', teamGame)
if tm.short_code and tm.short_code~="" then infoRow('Tag', tm.short_code) end
if rankVal then infoRow('Krafton Rank', rankVal) end
if totalEarnings>0 then infoRow('Total Earnings', fmtCurrency(totalEarnings)) end
if tm.sponsors and tm.sponsors~="" then
infoRow('Sponsors', tm.sponsors:gsub(",",", "))
end
local playerCount=0
for _,pl in ipairs(roster) do
local r=(pl.role or ""):lower()
if not r:find("coach") and not r:find("analyst") and not r:find("manager") then
playerCount=playerCount+1
end
end
if playerCount>0 then infoRow('Active Players', tostring(playerCount)) end
if #formerRows>0 then infoRow('Alumni', tostring(#formerRows)) end
-- Socials
local socials = {
{k='instagram',file='Icon_instagram.png',base='https://instagram.com/'},
{k='twitter', file='Icon_twitter.png', base='https://twitter.com/' },
{k='youtube', file='Icon_youtube.png', base='https://youtube.com/' },
{k='discord', file='Icon_discord.png', base='https://discord.gg/' },
{k='facebook', file='Icon_facebook.png', base='https://facebook.com/' },
{k='website', file='Icon_website.png', base='https://' },
}
local hasSocial=false
for _,s in ipairs(socials) do
if tm[s.k] and tm[s.k]~="" then hasSocial=true; break end
end
if hasSocial then
local socDiv=left:tag('div'):addClass('pd-socials')
for _,s in ipairs(socials) do
local v=tm[s.k]
if v and v~="" then
local url=v:match("^https?://") and v or (s.base..v)
socDiv:wikitext("[[File:"..s.file.."|32px|link="..url.."|class=social-img]]")
end
end
end
-- ── RIGHT ─────────────────────────────────────────────────────
local right = body:tag('div'):addClass('td-right')
-- ── Active Roster ─────────────────────────────────────────────
right:tag('div'):addClass('pd-table-title'):wikitext('👥 Active Roster')
if #roster>0 then
local grid=right:tag('div'):addClass('hero-roster-grid')
for _,pl in ipairs(roster) do
local card=grid:tag('div'):addClass('hero-player-card')
local color=getRoleColor(pl.role)
local imgDiv=card:tag('div'):addClass('hero-card-image')
local imgFile=(pl.image~="") and pl.image or "Player_Placeholder.png"
imgDiv:wikitext("[[File:"..imgFile.."|link="..(pl.link or pl.id).."]]")
local cb=card:tag('div'):addClass('hero-card-body')
cb:tag('div'):addClass('hero-role-pill')
:css('background-color',color):wikitext(pl.role or "Player")
cb:tag('div'):addClass('hero-player-id')
:wikitext("[["..( pl.link or pl.id).."|"..(pl.id or "").."]]")
if pl.name~="" then
cb:tag('div'):addClass('hero-player-name'):wikitext(pl.name)
end
if pl.nat~="" then
cb:tag('div'):addClass('hero-player-flag')
:wikitext(getFlag(pl.nat)..pl.nat)
end
end
else
right:tag('div'):addClass('pd-empty'):wikitext('No active roster data found.')
end
-- ── Former Players ────────────────────────────────────────────
-- Pure CSS tabs: hidden radio inputs + label bar + panels
-- All inputs, labels, and panels are direct siblings of .td-tabs-wrap children
-- so the :checked ~ sibling selector works correctly
if #formerRows>0 then
right:tag('div'):addClass('pd-table-title td-section-gap')
:wikitext('📋 Former Players')
-- Safe ID: only alphanumeric
local tabId='tdfp'..teamName:gsub("[^%w]","")
-- Build raw HTML string (mw.html escapes inputs/labels incorrectly)
local tabHtml = '<div class="td-tabs-wrap">'
-- Radio inputs first (must be siblings of label bar and panels)
for i,year in ipairs(yearOrder) do
local checked = (i==1) and ' checked="checked"' or ''
tabHtml = tabHtml..'<input type="radio" class="td-radio" '
..'name="'..tabId..'" id="'..tabId..year..'"'..checked..'/>'
end
-- Label bar
tabHtml = tabHtml..'<div class="td-tab-bar">'
for _,year in ipairs(yearOrder) do
tabHtml = tabHtml..'<label class="td-tab-btn" for="'
..tabId..year..'">'..year..'</label>'
end
tabHtml = tabHtml..'</div>'
-- Panels
tabHtml = tabHtml..'<div class="td-panels">'
for _,year in ipairs(yearOrder) do
tabHtml = tabHtml..'<div class="td-year-panel" data-year="'..year..'">'
-- Table header
tabHtml = tabHtml..'<table class="pd-tourn-table td-former-table">'
..'<tr><th>Player</th><th>Name</th><th>Role</th>'
..'<th>Joined</th><th>Left</th></tr>'
for _,r in ipairs(formerByYear[year]) do
local pid=r.pid or ""
local dispId=(r.short_id and r.short_id~="")
and r.short_id or pid:gsub("^.*/","")
local roleColor=getRoleColor(r.role)
local roleBadge=""
if r.role and r.role~="" then
roleBadge='<span class="td-role-badge" style="background:'
..roleColor..'">'..r.role..'</span>'
else roleBadge="—" end
tabHtml = tabHtml..'<tr>'
..'<td style="font-weight:700">[['..'<nowiki/>'..pid..'|'..dispId..']]</td>'
..'<td>'..(r.real_name~="" and r.real_name or "—")..'</td>'
..'<td>'..roleBadge..'</td>'
..'<td class="ac-date">'..(r.join_date or "?")..'</td>'
..'<td class="ac-date">'..(r.leave_date or "?")..'</td>'
..'</tr>'
end
tabHtml = tabHtml..'</table></div>'
end
tabHtml = tabHtml..'</div>' -- td-panels
-- Inline CSS: one rule per year for :checked → show panel + highlight label
tabHtml = tabHtml..'<style>'
tabHtml = tabHtml..'.td-panels .td-year-panel{display:none}'
for _,year in ipairs(yearOrder) do
local id=tabId..year
-- Show panel
tabHtml = tabHtml..'#'..id..':checked~.td-tab-bar~.td-panels'
..' .td-year-panel[data-year="'..year.."\"]{display:block}"
-- Highlight label
tabHtml = tabHtml..'#'..id..':checked~.td-tab-bar'
..' label[for="'..id..'"]'
..'{background:rgba(37,99,235,.25);color:#93c5fd;'
..'border-color:rgba(37,99,235,.5)}'
end
tabHtml = tabHtml..'</style></div>' -- close td-tabs-wrap
right:wikitext(tabHtml)
end
-- ── Achievements ──────────────────────────────────────────────
right:tag('div'):addClass('pd-table-title td-section-gap')
:wikitext('🏆 Achievements')
if #achRows>0 then
local tbl2=right:tag('table'):addClass('pd-tourn-table')
local hdr2=tbl2:tag('tr')
hdr2:tag('th'):wikitext('Date')
hdr2:tag('th'):wikitext('Tier')
hdr2:tag('th'):wikitext('Tournament')
hdr2:tag('th'):addClass('ac-place'):wikitext('Place')
hdr2:tag('th'):css('text-align','right'):wikitext('Prize')
for _,r in ipairs(achRows) do
local meta=tournMeta[r.tournament or ""] or {}
local tr2=tbl2:tag('tr')
tr2:tag('td'):addClass('ac-date')
:wikitext(meta.date and meta.date~="" and meta.date or "—")
local tierTd=tr2:tag('td'):addClass('ac-tier')
if meta.tier and meta.tier~="" then
tierTd:tag('span'):addClass('tier-badge '..getTierClass(meta.tier))
:wikitext(meta.tier)
else tierTd:wikitext("—") end
tr2:tag('td'):css('font-weight','600')
:wikitext("[["..(r.tournament or "").."]]")
local pTd=tr2:tag('td'):addClass('ac-place')
if r.award and r.award~="" then
pTd:tag('span'):addClass('pd-award-badge'):wikitext(r.award)
else renderPlaceBadge(pTd,r.placement) end
tr2:tag('td'):addClass('ac-prize')
:wikitext(tonumber(r.prize) and tonumber(r.prize)>0
and fmtCurrency(tonumber(r.prize)) or "—")
end
else
right:tag('div'):addClass('pd-empty'):wikitext('No achievements recorded.')
end
return tostring(root)
end
return p