TradeSkillMaster/Core/UI/MainUI/Ledger/Core.lua

525 lines
17 KiB
Lua

-- ------------------------------------------------------------------------------ --
-- TradeSkillMaster --
-- https://tradeskillmaster.com --
-- All Rights Reserved - Detailed license information included with addon. --
-- ------------------------------------------------------------------------------ --
local _, TSM = ...
local Ledger = TSM.MainUI:NewPackage("Ledger")
local L = TSM.Include("Locale").GetTable()
local TempTable = TSM.Include("Util.TempTable")
local Table = TSM.Include("Util.Table")
local Money = TSM.Include("Util.Money")
local Theme = TSM.Include("Util.Theme")
local Log = TSM.Include("Util.Log")
local ItemInfo = TSM.Include("Service.ItemInfo")
local Settings = TSM.Include("Service.Settings")
local UIElements = TSM.Include("UI.UIElements")
local SECONDS_PER_DAY = 24 * 60 * 60
local private = {
settings = nil,
pages = {},
childPages = {},
callback = {},
contextPath = nil,
contextItemString = nil,
itemDetailType = "sale",
}
local NUM_TOP_PLAYERS = 3
local PAGE_PATH_SEP = "`"
-- ============================================================================
-- Module Functions
-- ============================================================================
function Ledger.OnInitialize()
private.settings = Settings.NewView()
:AddKey("global", "mainUIContext", "ledgerDetailScrollingTable")
TSM.MainUI.RegisterTopLevelPage(L["Ledger"], private.GetLedgerFrame)
end
function Ledger.RegisterPage(name, callback)
tinsert(private.pages, name)
private.callback[name] = callback
end
function Ledger.RegisterChildPage(parentName, childName, callback)
local path = parentName..PAGE_PATH_SEP..childName
private.childPages[parentName] = private.childPages[parentName] or {}
tinsert(private.childPages[parentName], childName)
private.callback[path] = callback
end
function Ledger.ShowItemDetail(frame, itemString, detailType)
assert(detailType == "sale" or detailType == "buy")
private.contextItemString = itemString
private.itemDetailType = detailType
frame:SetPath("itemDetail", true)
end
-- ============================================================================
-- Ledger UI
-- ============================================================================
function private.GetLedgerFrame()
TSM.UI.AnalyticsRecordPathChange("main", "ledger")
local defaultPage = private.pages[1]
local frame = UIElements.New("Frame", "ledger")
:SetLayout("HORIZONTAL")
:SetBackgroundColor("PRIMARY_BG_ALT")
:AddChild(UIElements.New("Frame", "navigation")
:SetLayout("VERTICAL")
:SetWidth(160)
:SetPadding(12, 12, 1, 9)
)
:AddChild(UIElements.New("Texture", "divider")
:SetWidth(2)
:SetTexture("ACTIVE_BG")
)
:AddChild(UIElements.New("Frame", "contentFrame")
:SetLayout("VERTICAL")
:AddChild(UIElements.New("ViewContainer", "content")
:SetNavCallback(private.ContentNavCallback)
:AddPath("itemDetail")
)
)
:SetScript("OnHide", private.NavButtonOnHide)
local content = frame:GetElement("contentFrame.content")
local navFrame = frame:GetElement("navigation")
for _, pageName in ipairs(private.pages) do
navFrame:AddChild(UIElements.New("Button", pageName)
:SetHeight(20)
:SetMargin(0, 0, 8, 0)
:SetFont("BODY_BODY2_BOLD")
:SetJustifyH("LEFT")
:SetContext(pageName)
:SetText(pageName)
:SetScript("OnClick", private.NavButtonOnClick)
)
content:AddPath(pageName, pageName == defaultPage)
if private.childPages[pageName] then
for _, childPageName in ipairs(private.childPages[pageName]) do
local path = pageName..PAGE_PATH_SEP..childPageName
navFrame:AddChild(UIElements.New("Button", path)
:SetHeight(20)
:SetMargin(9, 0, 8, 0)
:SetFont("BODY_BODY3_MEDIUM")
:SetJustifyH("LEFT")
:SetContext(path)
:SetText(childPageName)
:SetScript("OnClick", private.NavButtonOnClick)
)
content:AddPath(path, path == defaultPage)
end
end
end
-- make all the navigation align to the top
navFrame:AddChild(UIElements.New("Spacer", "spacer"))
private.UpdateNavFrame(navFrame, defaultPage)
private.contextPath = L["Inventory"]
return frame
end
function private.ContentNavCallback(self, path)
if path == "itemDetail" then
return private.GetItemDetail()
else
return private.callback[path]()
end
end
-- ============================================================================
-- Local Script Handlers
-- ============================================================================
function private.NavButtonOnClick(button)
local path = button:GetContext()
if private.contextPath == path then
return
end
if private.childPages[path] then
-- select the first child
path = path..PAGE_PATH_SEP..private.childPages[path][1]
end
local ledgerFrame = button:GetParentElement():GetParentElement()
local contentFrame = ledgerFrame:GetElement("contentFrame")
local navFrame = ledgerFrame:GetElement("navigation")
private.UpdateNavFrame(navFrame, path)
navFrame:Draw()
contentFrame:GetElement("content"):SetPath(path, private.contextPath ~= path)
private.contextPath = path
end
function private.NavButtonOnHide(button)
private.contextPath = nil
end
-- ============================================================================
-- Private Helper Functions
-- ============================================================================
function private.UpdateNavFrame(navFrame, selectedPath)
local selectedPage = strsplit(PAGE_PATH_SEP, selectedPath)
for _, pageName in ipairs(private.pages) do
navFrame:GetElement(pageName):SetTextColor(pageName == selectedPage and "TEXT" or "ACTIVE_BG_ALT")
if private.childPages[pageName] then
for _, childPageName in ipairs(private.childPages[pageName]) do
local path = pageName..PAGE_PATH_SEP..childPageName
if pageName == selectedPage then
navFrame:GetElement(path)
:SetTextColor(path == selectedPath and "INDICATOR" or "TEXT")
:Show()
else
navFrame:GetElement(path):Hide()
end
end
end
end
end
function private.GetItemDetail()
local query = TSM.Accounting.Transactions.CreateQuery()
:Equal("itemString", private.contextItemString)
:OrderBy("time", false)
local topPlayersQuantity = TempTable.Acquire()
local topPlayers = TempTable.Acquire()
for _, row in query:Iterator() do
local recordType, otherPlayer, quantity = row:GetFields("type", "otherPlayer", "quantity")
if recordType == private.itemDetailType then
if not topPlayersQuantity[otherPlayer] then
topPlayersQuantity[otherPlayer] = 0
tinsert(topPlayers, otherPlayer)
end
topPlayersQuantity[otherPlayer] = topPlayersQuantity[otherPlayer] + quantity
end
end
Table.SortWithValueLookup(topPlayers, topPlayersQuantity, true)
local numTopPlayers = min(#topPlayers, NUM_TOP_PLAYERS)
local topPlayersText = ""
if numTopPlayers > 0 then
for i = 1, numTopPlayers do
local player = topPlayers[i]
local quantity = topPlayersQuantity[player]
topPlayers[i] = player..Theme.GetColor("INDICATOR_ALT"):ColorText(" (" .. quantity .. ")")
end
topPlayersText = table.concat(topPlayers, ", ", 1, numTopPlayers)
else
topPlayersText = L["None"]
end
TempTable.Release(topPlayers)
TempTable.Release(topPlayersQuantity)
return UIElements.New("Frame", "content")
:SetLayout("VERTICAL")
:AddChild(UIElements.New("Frame", "top")
:SetLayout("VERTICAL")
:SetPadding(8)
:SetBackgroundColor("PRIMARY_BG_ALT")
:AddChild(UIElements.New("Frame", "header")
:SetLayout("HORIZONTAL")
:SetHeight(24)
:AddChild(UIElements.New("ActionButton", "button")
:SetWidth(64)
:SetIcon("iconPack.14x14/Chevron/Right@180")
:SetText(BACK)
:SetScript("OnClick", private.ItemDetailBackButtonOnClick)
)
:AddChild(UIElements.New("Button", "icon")
:SetSize(24, 24)
:SetMargin(14, 8, 0, 0)
:SetBackground(ItemInfo.GetTexture(private.contextItemString))
:SetTooltip(private.contextItemString)
)
:AddChild(UIElements.New("Text", "itemName")
:SetFont("ITEM_BODY1")
:SetText(TSM.UI.GetColoredItemName(private.contextItemString))
)
)
:AddChild(UIElements.New("Frame", "content")
:SetLayout("VERTICAL")
:SetMargin(0, 0, 8, 0)
:SetPadding(12, 12, 8, 10)
:SetBackgroundColor("PRIMARY_BG_ALT", true)
:SetBorderColor("ACTIVE_BG")
:AddChild(UIElements.New("Frame", "heading")
:SetLayout("HORIZONTAL")
:SetHeight(20)
:AddChild(UIElements.New("Button", "saleBtn")
:SetWidth("AUTO")
:SetMargin(0, 8, 0, 0)
:SetFont("BODY_BODY2_BOLD")
:SetTextColor(private.itemDetailType == "sale" and "INDICATOR" or "ACTIVE_BG_ALT")
:SetContext("sale")
:SetText(L["Sale Data"])
:SetScript("OnClick", private.ItemDetailTabOnClick)
)
:AddChild(UIElements.New("Button", "buyBtn")
:SetWidth("AUTO")
:SetMargin(0, 8, 0, 0)
:SetFont("BODY_BODY2_BOLD")
:SetTextColor(private.itemDetailType == "buy" and "INDICATOR" or "ACTIVE_BG_ALT")
:SetContext("buy")
:SetText(L["Purchase Data"])
:SetScript("OnClick", private.ItemDetailTabOnClick)
)
:AddChild(UIElements.New("Spacer", "spacer"))
:AddChild(UIElements.New("Text", "total")
:SetWidth(120)
:SetMargin(0, 8, 0, 0)
:SetFont("BODY_BODY3_MEDIUM")
:SetJustifyH("RIGHT")
:SetText(L["Total"])
)
:AddChild(UIElements.New("Text", "last7")
:SetWidth(120)
:SetMargin(0, 8, 0, 0)
:SetFont("BODY_BODY3_MEDIUM")
:SetJustifyH("RIGHT")
:SetText(L["Last 7 Days"])
)
:AddChild(UIElements.New("Text", "last30")
:SetWidth(120)
:SetFont("BODY_BODY3_MEDIUM")
:SetJustifyH("RIGHT")
:SetText(L["Last 30 Days"])
)
)
:AddChild(UIElements.New("Frame", "quantity")
:SetLayout("HORIZONTAL")
:SetHeight(20)
:SetMargin(0, 0, 8, 0)
:AddChild(UIElements.New("Text", "label")
:SetMargin(0, 8, 0, 0)
:SetFont("BODY_BODY3")
:SetTextColor("TEXT_ALT")
:SetText(private.itemDetailType == "sale" and L["Quantity Sold:"] or L["Quantity Purchased:"])
)
:AddChild(UIElements.New("Text", "total")
:SetWidth(120)
:SetMargin(0, 8, 0, 0)
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetText(TSM.Accounting.Transactions.GetQuantity(private.contextItemString, nil, private.itemDetailType))
)
:AddChild(UIElements.New("Text", "last7")
:SetWidth(120)
:SetMargin(0, 8, 0, 0)
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetText(TSM.Accounting.Transactions.GetQuantity(private.contextItemString, SECONDS_PER_DAY * 7, private.itemDetailType))
)
:AddChild(UIElements.New("Text", "last30")
:SetWidth(120)
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetText(TSM.Accounting.Transactions.GetQuantity(private.contextItemString, SECONDS_PER_DAY * 30, private.itemDetailType))
)
)
:AddChild(UIElements.New("Frame", "avgPrice")
:SetLayout("HORIZONTAL")
:SetHeight(20)
:SetMargin(0, 0, 8, 0)
:AddChild(UIElements.New("Text", "label")
:SetMargin(0, 8, 0, 0)
:SetFont("BODY_BODY3")
:SetTextColor("TEXT_ALT")
:SetText(L["Average Prices:"])
)
:AddChild(UIElements.New("Text", "total")
:SetWidth(120)
:SetMargin(0, 8, 0, 0)
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetText(Money.ToString(TSM.Accounting.Transactions.GetAveragePrice(private.contextItemString, nil, private.itemDetailType)))
)
:AddChild(UIElements.New("Text", "last7")
:SetWidth(120)
:SetMargin(0, 8, 0, 0)
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetText(Money.ToString(TSM.Accounting.Transactions.GetAveragePrice(private.contextItemString, SECONDS_PER_DAY * 7, private.itemDetailType)))
)
:AddChild(UIElements.New("Text", "last30")
:SetWidth(120)
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetText(Money.ToString(TSM.Accounting.Transactions.GetAveragePrice(private.contextItemString, SECONDS_PER_DAY * 30, private.itemDetailType)))
)
)
:AddChild(UIElements.New("Frame", "totalPrice")
:SetLayout("HORIZONTAL")
:SetHeight(20)
:SetMargin(0, 0, 8, 0)
:AddChild(UIElements.New("Text", "label")
:SetMargin(0, 8, 0, 0)
:SetFont("BODY_BODY3")
:SetTextColor("TEXT_ALT")
:SetText(L["Total Prices:"])
)
:AddChild(UIElements.New("Text", "total")
:SetWidth(120)
:SetMargin(0, 8, 0, 0)
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetText(Money.ToString(TSM.Accounting.Transactions.GetTotalPrice(private.contextItemString, nil, private.itemDetailType)))
)
:AddChild(UIElements.New("Text", "last7")
:SetWidth(120)
:SetMargin(0, 8, 0, 0)
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetText(Money.ToString(TSM.Accounting.Transactions.GetTotalPrice(private.contextItemString, SECONDS_PER_DAY * 7, private.itemDetailType)))
)
:AddChild(UIElements.New("Text", "last30")
:SetWidth(120)
:SetFont("TABLE_TABLE1")
:SetJustifyH("RIGHT")
:SetText(Money.ToString(TSM.Accounting.Transactions.GetTotalPrice(private.contextItemString, SECONDS_PER_DAY * 30, private.itemDetailType)))
)
)
:AddChild(UIElements.New("Frame", "top")
:SetLayout("HORIZONTAL")
:SetHeight(20)
:SetMargin(0, 0, 8, 0)
:AddChild(UIElements.New("Text", "label")
:SetWidth("AUTO")
:SetMargin(0, 8, 0, 0)
:SetFont("BODY_BODY3")
:SetTextColor("TEXT_ALT")
:SetText(private.itemDetailType == "sale" and L["Top Buyers"]..":" or L["Top Sellers"]..":")
)
:AddChild(UIElements.New("Text", "value")
:SetFont("BODY_BODY3")
:SetText(topPlayersText)
)
)
)
)
:AddChild(UIElements.New("QueryScrollingTable", "scrollingTable")
:SetSettingsContext(private.settings, "ledgerDetailScrollingTable")
:GetScrollingTableInfo()
:NewColumn("activityType")
:SetTitle(L["Activity Type"])
:SetFont("ITEM_BODY3")
:SetJustifyH("LEFT")
:SetTextInfo("type", private.TableGetActivityTypeText)
:Commit()
:NewColumn("source")
:SetTitle(L["Source"])
:SetFont("ITEM_BODY3")
:SetJustifyH("LEFT")
:SetTextInfo("source")
:Commit()
:NewColumn("buyerSeller")
:SetTitle(L["Buyer/Seller"])
:SetFont("ITEM_BODY3")
:SetJustifyH("LEFT")
:SetTextInfo("otherPlayer")
:Commit()
:NewColumn("qty")
:SetTitle(L["Qty"])
:SetFont("ITEM_BODY3")
:SetJustifyH("RIGHT")
:SetTextInfo("quantity")
:Commit()
:NewColumn("perItem")
:SetTitle(L["Per Item"])
:SetFont("ITEM_BODY3")
:SetJustifyH("RIGHT")
:SetTextInfo(nil, private.TableGetPerItemText)
:Commit()
:NewColumn("totalPrice")
:SetTitle(L["Total Price"])
:SetFont("ITEM_BODY3")
:SetJustifyH("RIGHT")
:SetTextInfo(nil, private.TableGetTotalPriceText)
:Commit()
:NewColumn("time")
:SetTitle(L["Time"])
:SetFont("ITEM_BODY3")
:SetJustifyH("RIGHT")
:SetTextInfo("time", private.TableGetTimeframeText)
:Commit()
:Commit()
:SetQuery(query)
:SetAutoReleaseQuery(true)
:SetSelectionDisabled(true)
:SetScript("OnRowClick", private.ItemDetailScrollingTableOnRowClick)
)
end
function private.ItemDetailBackButtonOnClick(button)
button:GetParentElement():GetParentElement():GetParentElement():GetParentElement():SetPath(private.contextPath, true)
end
function private.ItemDetailTabOnClick(button)
private.itemDetailType = button:GetContext()
button:GetParentElement():GetParentElement():GetParentElement():GetParentElement():GetParentElement():ReloadContent()
end
function private.ItemDetailScrollingTableOnRowClick(scrollingTable, row, button)
if button ~= "RightButton" then
return
elseif not TSM.Accounting.Transactions.CanDeleteByUUID(row:GetUUID()) then
Log.PrintUser(L["This record belongs to another account and can only be deleted on that account."])
return
end
local subtitle = nil
local recordType, itemString, quantity, otherPlayer, price = row:GetFields("type", "itemString", "quantity", "otherPlayer", "price")
local name = TSM.UI.GetColoredItemName(itemString) or "?"
local amount = Money.ToString(price * quantity)
if recordType == "sale" then
subtitle = format(L["Sold %d of %s to %s for %s"], quantity, name, otherPlayer, amount)
elseif recordType == "buy" then
subtitle = format(L["Bought %d of %s from %s for %s"], quantity, name, otherPlayer, amount)
else
error("Unexpected Type: "..tostring(recordType))
end
scrollingTable:GetBaseElement():ShowConfirmationDialog(L["Delete Record?"], subtitle, private.DeleteRecordConfirmed, row:GetUUID())
end
function private.DeleteRecordConfirmed(uuid)
TSM.Accounting.Transactions.RemoveRowByUUID(uuid)
end
-- ============================================================================
-- Scrolling Table Helper Functions
-- ============================================================================
function private.TableGetActivityTypeText(recordType)
if recordType == "sale" then
return L["Sale"]
elseif recordType == "buy" then
return L["Buy"]
else
error("Unexpected Type: "..tostring(recordType))
end
end
function private.TableGetTimeframeText(timestamp)
return SecondsToTime(time() - timestamp)
end
function private.TableGetTotalPriceText(row)
return Money.ToString(row:GetField("price") * row:GetField("quantity"))
end
function private.TableGetPerItemText(row)
return Money.ToString(row:GetField("price"))
end