Module:TeamDashboard
From eSportsAmaze
More actions
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()
-- ── Helpers ──────────────────────────────────────────────────────
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
-- Role color config — identical to Module:Team
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 from Cargo ───────────────────────────────────
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
-- ── 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 ─────────────────────────────────────────
-- Merge DB players with manual args (same logic as Module:Team|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 (join Players for display info) ─────────
-- Step 1: get former player IDs for this team
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 {}
-- Step 2: batch fetch player details
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
-- Step 3: merge into formerRows
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 of leave_date
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 ───────────────────────────────────────────
local achRows = cargo.query("PrizeMoney","tournament,placement,prize,award",
{where="team='"..sqlTeam.."' AND (player='' OR player IS NULL)",
orderBy="prize DESC", limit=50})
achRows = achRows or {}
-- Batch fetch tournament meta (tier + date)
local tournNames,tournSeen2 = {},{}
for _,r in ipairs(achRows) 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
local tournMeta = {}
if #tournNames>0 then
local mRows = cargo.query("Tournaments","name,tier,end_date",
{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}
end
end
end
-- ── BUILD HTML ────────────────────────────────────────────────
local root = html.create('div'):addClass('td-dashboard')
-- ════ HERO ════
-- [Logo] [Display name + short_code tag + status/game/rank tags] [Earnings]
local hero = root:tag('div'):addClass('td-hero')
-- Logo (light/dark)
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]]")
-- Info: name + tags
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 tm.game and tm.game~="" then
tags:tag('span'):addClass('pd-tag pd-tag-game'):wikitext(tm.game)
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
-- Earnings (right side, same structure as player dashboard)
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 panel ───────────────────────────────────────────────
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', tm.game)
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
-- Roster count (players only, exclude coach/analyst)
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 panel ──────────────────────────────────────────────
local right = body:tag('div'):addClass('td-right')
-- ── Active Roster ─────────────────────────────────────────────
-- Reuses existing .hero-player-card CSS exactly as Module:Team|roster does
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)
-- Image area (same as Module:Team)
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).."]]")
-- Body
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 using hidden radio inputs — no JS, no <button>
-- MediaWiki strips <script> and escapes <button>, so we use
-- <input type="radio"> + <label> + CSS :checked sibling selectors
if #formerRows>0 then
right:tag('div'):addClass('pd-table-title td-section-gap')
:wikitext('📋 Former Players')
local tabId = 'tdfp'..teamName:gsub("[^%w]","")
local wrap = right:tag('div'):addClass('td-tabs-wrap')
-- Hidden radio inputs (one per year)
for i,year in ipairs(yearOrder) do
local inputId = tabId..year
local checked = (i==1) and ' checked' or ''
wrap:wikitext('<input type="radio" class="td-radio" name="'
..tabId..'" id="'..inputId..'"'..checked..'>')
end
-- Label bar (the visible "tabs")
local labelBar = wrap:tag('div'):addClass('td-tab-bar')
for i,year in ipairs(yearOrder) do
local inputId = tabId..year
labelBar:wikitext('<label class="td-tab-btn" for="'..inputId..'">'..year..'</label>')
end
-- Year panels (shown/hidden via CSS :checked ~ .td-panels .td-year-panel)
local panels = wrap:tag('div'):addClass('td-panels')
for _,year in ipairs(yearOrder) do
local panel = panels:tag('div')
:addClass('td-year-panel')
:attr('data-year', year)
local tbl = panel:tag('table'):addClass('pd-tourn-table td-former-table')
local hdr = tbl:tag('tr')
hdr:tag('th'):wikitext('Player')
hdr:tag('th'):wikitext('Name')
hdr:tag('th'):wikitext('Role')
hdr:tag('th'):wikitext('Joined')
hdr:tag('th'):wikitext('Left')
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 tr = tbl:tag('tr')
tr:tag('td'):css('font-weight','700')
:wikitext("[["..pid.."|"..dispId.."]]")
tr:tag('td'):wikitext(r.real_name or "—")
local rCell = tr:tag('td')
if r.role and r.role~="" then
rCell:tag('span'):addClass('td-role-badge')
:css('background',getRoleColor(r.role)):wikitext(r.role)
else rCell:wikitext("—") end
tr:tag('td'):addClass('ac-date'):wikitext(r.join_date or "?")
tr:tag('td'):addClass('ac-date'):wikitext(r.leave_date or "?")
end
end
-- Inline CSS to wire up the radio → panel visibility
-- One rule per year: #tabIdYEAR:checked ~ .td-panels [data-year="YEAR"]
local cssRules = '<style>'
-- Hide all panels by default
cssRules = cssRules..'.td-panels .td-year-panel{display:none}'
for _,year in ipairs(yearOrder) do
cssRules = cssRules..'#'..tabId..year
..':checked~.td-tab-bar~.td-panels [data-year="'..year..'"]'
..'{display:block}'
-- Active label highlight
cssRules = cssRules..'#'..tabId..year
..':checked~.td-tab-bar label[for="'..tabId..year..'"]'
..'{background:rgba(37,99,235,.25);color:#93c5fd;'
..'border-color:rgba(37,99,235,.5)}'
end
cssRules = cssRules..'</style>'
wrap:wikitext(cssRules)
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