-- ------------------------------------------------------------------------------ -- -- TradeSkillMaster -- -- https://tradeskillmaster.com -- -- All Rights Reserved - Detailed license information included with addon. -- -- ------------------------------------------------------------------------------ -- local _, TSM = ... local CraftingReports = TSM.UI.CraftingUI:NewPackage("CraftingReports") local L = TSM.Include("Locale").GetTable() local Math = TSM.Include("Util.Math") local Log = TSM.Include("Util.Log") local Money = TSM.Include("Util.Money") local String = TSM.Include("Util.String") local ItemString = TSM.Include("Util.ItemString") local Theme = TSM.Include("Util.Theme") local ItemInfo = TSM.Include("Service.ItemInfo") local Settings = TSM.Include("Service.Settings") local UIElements = TSM.Include("UI.UIElements") local private = { settings = nil, craftsQuery = nil, matsQuery = nil, filterText = "", craftProfessions = {}, matProfessions = {}, } local MAT_PRICE_SOURCES = {ALL, L["Default Price"], L["Custom Price"]} -- ============================================================================ -- Module Functions -- ============================================================================ function CraftingReports.OnInitialize() private.settings = Settings.NewView() :AddKey("global", "craftingUIContext", "craftsScrollingTable") :AddKey("global", "craftingUIContext", "matsScrollingTable") :AddKey("factionrealm", "internalData", "mats") :AddKey("global", "craftingOptions", "defaultMatCostMethod") TSM.UI.CraftingUI.RegisterTopLevelPage(L["Reports"], private.GetCraftingReportsFrame) end -- ============================================================================ -- CraftingReports UI -- ============================================================================ function private.GetCraftingReportsFrame() TSM.UI.AnalyticsRecordPathChange("crafting", "crafting_reports") if not private.craftsQuery then private.craftsQuery = TSM.Crafting.CreateCraftsQuery() private.craftsQuery:VirtualField("firstOperation", "string", private.FirstOperationVirtualField, "itemString") end private.craftsQuery:ResetFilters() private.craftsQuery:ResetOrderBy() private.craftsQuery:OrderBy("itemName", true) private.matsQuery = private.matsQuery or TSM.Crafting.CreateMatItemQuery() private.matsQuery:ResetFilters() private.matsQuery:ResetOrderBy() private.matsQuery:OrderBy("name", true) return UIElements.New("Frame", "craftingReportsContent") :SetLayout("VERTICAL") :SetPadding(0, 0, 6, 0) :SetBackgroundColor("PRIMARY_BG_ALT") :AddChild(UIElements.New("TabGroup", "buttons") :SetNavCallback(private.GetTabElements) :AddPath(L["Crafts"], true) :AddPath(L["Materials"]) ) end function private.GetTabElements(self, path) if path == L["Crafts"] then TSM.UI.AnalyticsRecordPathChange("crafting", "crafting_reports", "crafts") private.filterText = "" wipe(private.craftProfessions) tinsert(private.craftProfessions, L["All Professions"]) for _, player, profession in TSM.Crafting.PlayerProfessions.Iterator() do tinsert(private.craftProfessions, format("%s - %s", profession, player)) end private.craftsQuery:ResetFilters() return UIElements.New("Frame", "crafts") :SetLayout("VERTICAL") :AddChild(UIElements.New("Frame", "filters") :SetLayout("HORIZONTAL") :SetHeight(72) :SetPadding(10, 10, 8, 16) :AddChild(UIElements.New("Frame", "search") :SetLayout("VERTICAL") :AddChild(UIElements.New("Text", "label") :SetHeight(20) :SetMargin(0, 0, 0, 4) :SetFont("BODY_BODY3_MEDIUM") :SetText(L["Filter by Keyword"]) ) :AddChild(UIElements.New("Input", "input") :SetHeight(24) :AllowItemInsert() :SetIconTexture("iconPack.18x18/Search") :SetClearButtonEnabled(true) :SetHintText(L["Enter Keyword"]) :SetScript("OnValueChanged", private.CraftsInputOnValueChanged) ) ) :AddChild(UIElements.New("Frame", "profession") :SetLayout("VERTICAL") :SetMargin(16, 16, 0, 0) :AddChild(UIElements.New("Text", "label") :SetHeight(20) :SetMargin(0, 0, 0, 4) :SetFont("BODY_BODY3_MEDIUM") :SetText(L["Filter by Profession"]) ) :AddChild(UIElements.New("SelectionDropdown", "dropdown") :SetHeight(24) :SetItems(private.craftProfessions) :SetSelectedItem(private.craftProfessions[1], true) :SetScript("OnSelectionChanged", private.CraftsDropdownOnSelectionChanged) ) ) :AddChild(UIElements.New("Frame", "craftable") :SetLayout("HORIZONTAL") :SetSize(176, 24) :SetMargin(0, 0, 24, 0) :AddChild(UIElements.New("Checkbox", "checkbox") :SetWidth(24) :SetFont("BODY_BODY2") :SetScript("OnValueChanged", private.CheckboxOnValueChanged) ) :AddChild(UIElements.New("Text", "label") :SetWidth("AUTO") :SetFont("BODY_BODY2") :SetText(L["Only show craftable"]) ) ) ) :AddChild(UIElements.New("QueryScrollingTable", "crafts") :SetSettingsContext(private.settings, "craftsScrollingTable") :GetScrollingTableInfo() :SetMenuInfo(private.CraftsMatsMenuIterator, private.CraftsMenuClickHandler) :NewColumn("queued") :SetTitleIcon("iconPack.18x18/Queue") :SetFont("TABLE_TABLE1") :SetJustifyH("CENTER") :SetTextInfo("num") :SetSortInfo("num") :Commit() :NewColumn("craftName") :SetTitle(L["Name"]) :SetIconSize(12) :SetFont("ITEM_BODY3") :SetJustifyH("LEFT") :SetTextInfo(nil, private.CraftsGetCraftNameText) :SetIconInfo("itemString", ItemInfo.GetTexture) :SetTooltipInfo("itemString") :SetSortInfo("itemName") :DisableHiding() :Commit() :NewColumn("operation") :SetTitle(L["Operation"]) :SetFont("BODY_BODY3") :SetJustifyH("LEFT") :SetTextInfo("firstOperation") :SetSortInfo("firstOperation") :Commit() :NewColumn("bags") :SetTitle(L["Bag"]) :SetFont("TABLE_TABLE1") :SetJustifyH("RIGHT") :SetTextInfo("bagQuantity", private.CraftsGetBagsText) :SetSortInfo("bagQuantity") :Commit() :NewColumn("ah") :SetTitle(L["AH"]) :SetFont("TABLE_TABLE1") :SetJustifyH("RIGHT") :SetTextInfo("auctionQuantity", private.CraftsGetAHText) :SetSortInfo("auctionQuantity") :Commit() :NewColumn("craftingCost") :SetTitle(L["Crafting Cost"]) :SetFont("TABLE_TABLE1") :SetJustifyH("RIGHT") :SetTextInfo("craftingCost", private.CraftsGetCostItemValueText) :SetSortInfo("craftingCost") :Commit() :NewColumn("itemValue") :SetTitle(L["Item Value"]) :SetFont("TABLE_TABLE1") :SetJustifyH("RIGHT") :SetTextInfo("itemValue", private.CraftsGetCostItemValueText) :SetSortInfo("itemValue") :Commit() :NewColumn("profit") :SetTitle(L["Profit"]) :SetFont("TABLE_TABLE1") :SetJustifyH("RIGHT") :SetTextInfo("profit", private.CraftsGetProfitText) :SetSortInfo("profit") :Commit() :NewColumn("profitPct") :SetTitle("%") :SetFont("TABLE_TABLE1") :SetJustifyH("RIGHT") :SetTextInfo("profitPct", private.GetProfitPctText) :SetSortInfo("profitPct") :Commit() :NewColumn("saleRate") :SetTitleIcon("iconPack.18x18/SaleRate") :SetFont("TABLE_TABLE1") :SetJustifyH("CENTER") :SetTextInfo("saleRate", private.CraftsGetSaleRateText) :SetSortInfo("saleRate") :Commit() :Commit() :SetQuery(private.craftsQuery) :SetSelectionDisabled(true) :SetScript("OnRowClick", private.CraftsOnRowClick) ) elseif path == L["Materials"] then TSM.UI.AnalyticsRecordPathChange("crafting", "crafting_reports", "materials") wipe(private.matProfessions) tinsert(private.matProfessions, L["All Professions"]) for _, _, profession in TSM.Crafting.PlayerProfessions.Iterator() do if not private.matProfessions[profession] then tinsert(private.matProfessions, profession) private.matProfessions[profession] = true end end private.matsQuery:ResetFilters() return UIElements.New("Frame", "materials") :SetLayout("VERTICAL") :AddChild(UIElements.New("Frame", "filters") :SetLayout("HORIZONTAL") :SetHeight(72) :SetPadding(10, 10, 8, 16) :AddChild(UIElements.New("Frame", "search") :SetLayout("VERTICAL") :AddChild(UIElements.New("Text", "label") :SetHeight(20) :SetMargin(0, 0, 0, 4) :SetFont("BODY_BODY3_MEDIUM") :SetText(L["Filter by Keyword"]) ) :AddChild(UIElements.New("Input", "input") :SetHeight(24) :AllowItemInsert() :SetIconTexture("iconPack.18x18/Search") :SetClearButtonEnabled(true) :SetHintText(L["Enter Keyword"]) :SetScript("OnValueChanged", private.MatsInputOnValueChanged) ) ) :AddChild(UIElements.New("Frame", "profession") :SetLayout("VERTICAL") :SetMargin(16, 0, 0, 0) :AddChild(UIElements.New("Text", "label") :SetHeight(20) :SetMargin(0, 0, 0, 4) :SetFont("BODY_BODY3_MEDIUM") :SetText(L["Filter by Profession"]) ) :AddChild(UIElements.New("SelectionDropdown", "dropdown") :SetHeight(24) :SetItems(private.matProfessions) :SetSelectedItem(private.matProfessions[1], true) :SetScript("OnSelectionChanged", private.MatsDropdownOnSelectionChanged) ) ) :AddChild(UIElements.New("Frame", "priceSource") :SetLayout("VERTICAL") :SetMargin(16, 0, 0, 0) :AddChild(UIElements.New("Text", "label") :SetHeight(20) :SetMargin(0, 0, 0, 4) :SetFont("BODY_BODY3_MEDIUM") :SetText(L["Filter by Price Source"]) ) :AddChild(UIElements.New("SelectionDropdown", "dropdown") :SetHeight(24) :SetItems(MAT_PRICE_SOURCES) :SetSelectedItem(MAT_PRICE_SOURCES[1], true) :SetScript("OnSelectionChanged", private.MatsDropdownOnSelectionChanged) ) ) ) :AddChild(UIElements.New("QueryScrollingTable", "mats") :SetSettingsContext(private.settings, "matsScrollingTable") :GetScrollingTableInfo() :SetMenuInfo(private.CraftsMatsMenuIterator, private.MatsMenuClickHandler) :NewColumn("name") :SetTitle(L["Name"]) :SetIconSize(12) :SetFont("ITEM_BODY3") :SetJustifyH("LEFT") :SetTextInfo("itemString", private.MatsGetNameText) :SetIconInfo("itemString", ItemInfo.GetTexture) :SetTooltipInfo("itemString") :SetSortInfo("name") :DisableHiding() :Commit() :NewColumn("price") :SetTitle(L["Mat Price"]) :SetFont("TABLE_TABLE1") :SetJustifyH("RIGHT") :SetTextInfo("matCost", private.MatsGetPriceText) :SetSortInfo("matCost") :Commit() :NewColumn("professions") :SetTitle(L["Professions Used In"]) :SetFont("ITEM_BODY3") :SetJustifyH("LEFT") :SetTextInfo("professions") :SetSortInfo("professions") :Commit() :NewColumn("num") :SetTitle(L["Number Owned"]) :SetFont("TABLE_TABLE1") :SetJustifyH("RIGHT") :SetTextInfo("totalQuantity", private.MatsGetNumText) :SetSortInfo("totalQuantity") :Commit() :Commit() :SetQuery(private.matsQuery) :SetSelectionDisabled(true) :SetScript("OnRowClick", private.MatsOnRowClick) ) else error("Unknown path: "..tostring(path)) end end -- ============================================================================ -- ScrollingTable Functions -- ============================================================================ function private.CraftsGetCraftNameText(row) return TSM.UI.GetColoredItemName(row:GetField("itemString")) or row:GetField("name") end function private.CraftsGetBagsText(bagQuantity) return bagQuantity or "0" end function private.CraftsGetAHText(bagQuantity) return bagQuantity or "0" end function private.CraftsGetCostItemValueText(costItemValue) if Math.IsNan(costItemValue) then return "" end return Money.ToString(costItemValue) end function private.CraftsGetProfitText(profit) if Math.IsNan(profit) then return "" end return Money.ToString(profit, (profit >= 0 and Theme.GetFeedbackColor("GREEN") or Theme.GetFeedbackColor("RED")):GetTextColorPrefix()) end function private.GetProfitPctText(profitPct) if Math.IsNan(profitPct) then return "" end local color = Theme.GetFeedbackColor(profitPct >= 0 and "GREEN" or "RED") return color:ColorText(profitPct.."%") or "" end function private.CraftsGetSaleRateText(saleRate) if Math.IsNan(saleRate) then return "" end return format("%0.2f", saleRate) end function private.MatsGetNameText(itemString) return TSM.UI.GetColoredItemName(itemString) or TSM.UI.GetColoredItemName(ItemString.GetUnknown()) end function private.MatsGetPriceText(matCost) if Math.IsNan(matCost) then return "" end return Money.ToString(matCost) end function private.MatsGetNumText(totalQuantity) return totalQuantity or "0" end -- ============================================================================ -- Local Script Handlers -- ============================================================================ function private.CraftsInputOnValueChanged(input) local text = input:GetValue() if text == private.filterText then return end private.filterText = text private.UpdateCraftsQueryWithFilters(input:GetParentElement():GetParentElement()) end function private.CraftsDropdownOnSelectionChanged(dropdown) private.UpdateCraftsQueryWithFilters(dropdown:GetParentElement():GetParentElement()) end function private.CheckboxOnValueChanged(checkbox) private.UpdateCraftsQueryWithFilters(checkbox:GetParentElement():GetParentElement()) end function private.CraftsOnRowClick(scrollingTable, record, mouseButton) if mouseButton == "LeftButton" then TSM.Crafting.Queue.Add(record:GetField("spellId"), 1) elseif mouseButton == "RightButton" then TSM.Crafting.Queue.Remove(record:GetField("spellId"), 1) end scrollingTable:Draw() end function private.MatsInputOnValueChanged(input) private.UpdateMatsQueryWithFilters(input:GetParentElement():GetParentElement()) end function private.MatsDropdownOnSelectionChanged(dropdown) private.UpdateMatsQueryWithFilters(dropdown:GetParentElement():GetParentElement()) end function private.MatsOnRowClick(scrollingTable, row) local itemString = row:GetField("itemString") local priceStr = private.settings.mats[itemString].customValue or private.settings.defaultMatCostMethod scrollingTable:GetBaseElement():ShowDialogFrame(UIElements.New("Frame", "frame") :SetLayout("VERTICAL") :SetSize(478, 312) :SetPadding(12) :AddAnchor("CENTER") :SetBackgroundColor("FRAME_BG", true) :SetContext(itemString) :AddChild(UIElements.New("Frame", "header") :SetLayout("HORIZONTAL") :SetHeight(24) :SetMargin(0, 0, -4, 10) :AddChild(UIElements.New("Spacer", "spacer") :SetWidth(20) ) :AddChild(UIElements.New("Text", "title") :SetJustifyH("CENTER") :SetFont("BODY_BODY1_BOLD") :SetText(L["Edit Material Price"]) ) :AddChild(UIElements.New("Button", "closeBtn") :SetMargin(0, -4, 0, 0) :SetBackgroundAndSize("iconPack.24x24/Close/Default") :SetScript("OnClick", private.DialogCloseBtnOnClick) ) ) :AddChild(UIElements.New("Frame", "item") :SetLayout("HORIZONTAL") :SetPadding(6) :SetMargin(0, 0, 0, 10) :SetBackgroundColor("PRIMARY_BG_ALT", true) :AddChild(UIElements.New("Button", "icon") :SetSize(36, 36) :SetMargin(0, 8, 0, 0) :SetBackground(ItemInfo.GetTexture(itemString)) :SetTooltip(itemString) ) :AddChild(UIElements.New("Text", "name") :SetHeight(36) :SetFont("ITEM_BODY1") :SetText(TSM.UI.GetColoredItemName(itemString)) ) ) :AddChild(UIElements.New("Text", "desc") :SetHeight(20) :SetMargin(0, 0, 0, 6) :SetFont("BODY_BODY2_MEDIUM") :SetText(L["Material Price"]) ) :AddChild(UIElements.New("MultiLineInput", "input") :SetMargin(0, 0, 0, 12) :SetBackgroundColor("PRIMARY_BG_ALT") :SetFont("BODY_BODY2_MEDIUM") :SetValidateFunc("CUSTOM_PRICE") :SetValue(Money.ToString(priceStr) or priceStr) :SetScript("OnValueChanged", private.MatPriceInputOnValueChanged) ) :AddChild(UIElements.New("Frame", "buttons") :SetLayout("HORIZONTAL") :SetHeight(24) :AddChild(UIElements.New("Button", "resetBtn") :SetWidth("AUTO") :SetFont("BODY_BODY3_MEDIUM") :SetTextColor(private.settings.mats[itemString].customValue and "TEXT" or "TEXT_ALT") :SetDisabled(not private.settings.mats[itemString].customValue) :SetText(L["Reset to Default"]) :SetScript("OnClick", private.ResetButtonOnClick) ) :AddChild(UIElements.New("Frame", "spacer")) :AddChild(UIElements.New("ActionButton", "closeBtn") :SetWidth(342) :SetText(L["Save"]) :SetScript("OnClick", private.DialogCloseBtnOnClick) ) ) ) end function private.DialogCloseBtnOnClick(button) button:GetBaseElement():HideDialog() end function private.MatPriceInputOnValueChanged(input) local value = input:GetValue() local itemString = input:GetParentElement():GetContext() TSM.Crafting.SetMatCustomValue(itemString, value) input:GetElement("__parent.buttons.resetBtn") :SetTextColor("TEXT") :SetDisabled(false) :Draw() end function private.ResetButtonOnClick(button) local itemString = button:GetParentElement():GetParentElement():GetContext() TSM.Crafting.SetMatCustomValue(itemString, nil) assert(not private.settings.mats[itemString].customValue) button:SetTextColor("TEXT_ALT") button:SetDisabled(true) button:Draw() button:GetElement("__parent.__parent.input") :SetValue(Money.ToString(private.settings.defaultMatCostMethod) or private.settings.defaultMatCostMethod) :Draw() end -- ============================================================================ -- Private Helper Functions -- ============================================================================ function private.FirstOperationVirtualField(itemString) return TSM.Operations.GetFirstOperationByItem("Crafting", itemString) or "" end function private.UpdateCraftsQueryWithFilters(frame) private.craftsQuery:ResetFilters() -- apply search filter local filter = strtrim(frame:GetElement("search.input"):GetValue()) if filter ~= "" then private.craftsQuery:Matches("itemName", String.Escape(filter)) end -- apply dropdown filter local professionPlayer = frame:GetElement("profession.dropdown"):GetSelectedItem() if professionPlayer ~= private.craftProfessions[1] then local profession, player = strmatch(professionPlayer, "^(.+) %- ([^ ]+)$") private.craftsQuery :Equal("profession", profession) :Or() :Equal("players", player) :Matches("players", "^"..player..",") :Matches("players", ","..player..",") :Matches("players", ","..player.."$") :End() end -- apply craftable filter local craftableOnly = frame:GetElement("craftable.checkbox"):IsChecked() if craftableOnly then private.craftsQuery:Custom(private.IsCraftableQueryFilter) end frame:GetElement("__parent.crafts"):SetQuery(private.craftsQuery, true) end function private.IsCraftableQueryFilter(record) return TSM.Crafting.ProfessionUtil.GetNumCraftableFromDB(record:GetField("spellId")) > 0 end function private.UpdateMatsQueryWithFilters(frame) private.matsQuery:ResetFilters() -- apply search filter local filter = strtrim(frame:GetElement("search.input"):GetValue()) if filter ~= "" then private.matsQuery:Custom(private.MatItemNameQueryFilter, strlower(String.Escape(filter))) end -- apply dropdown filters local profession = frame:GetElement("profession.dropdown"):GetSelectedItem() if profession ~= private.matProfessions[1] then private.matsQuery :Or() :Equal("professions", profession) :Matches("professions", "^"..profession..",") :Matches("professions", ","..profession..",") :Matches("professions", ","..profession.."$") :End() end local priceSource = frame:GetElement("priceSource.dropdown"):GetSelectedItem() if priceSource == MAT_PRICE_SOURCES[2] then private.matsQuery:Equal("customValue", "") elseif priceSource == MAT_PRICE_SOURCES[3] then private.matsQuery:NotEqual("customValue", "") end frame:GetElement("__parent.mats"):SetQuery(private.matsQuery, true) end function private.MatItemNameQueryFilter(row, filter) local name = ItemInfo.GetName(row:GetField("itemString")) if not name then return end return strmatch(strlower(name), filter) end function private.CraftsMatsMenuIterator(scrollingTable, prevIndex) if prevIndex == "CREATE_GROUPS" then -- we're done return else return "CREATE_GROUPS", L["Create Groups from Table"] end end function private.CraftsMenuClickHandler(scrollingTable, index1, index2) if index1 == "CREATE_GROUPS" then assert(not index2) scrollingTable:GetBaseElement():HideDialog() local numCreated, numAdded = 0, 0 for _, row in private.craftsQuery:Iterator() do local itemString = row:GetField("itemString") local groupPath = TSM.Groups.Path.Join(L["Crafted Items"], row:GetField("profession")) if not TSM.Groups.Exists(groupPath) then TSM.Groups.Create(groupPath) numCreated = numCreated + 1 end if not TSM.Groups.IsItemInGroup(itemString) and not ItemInfo.IsSoulbound(itemString) then TSM.Groups.SetItemGroup(itemString, groupPath) numAdded = numAdded + 1 end end Log.PrintfUser(L["%d groups were created and %d items were added from the table."], numCreated, numAdded) else error("Unexpected index1: "..tostring(index1)) end end function private.MatsMenuClickHandler(scrollingTable, index1, index2) if index1 == "CREATE_GROUPS" then assert(not index2) scrollingTable:GetBaseElement():HideDialog() local numCreated, numAdded = 0, 0 for _, row in private.matsQuery:Iterator() do local itemString = row:GetField("itemString") local groupPath = L["Materials"] if not TSM.Groups.Exists(groupPath) then TSM.Groups.Create(groupPath) numCreated = numCreated + 1 end if not TSM.Groups.IsItemInGroup(itemString) and not ItemInfo.IsSoulbound(itemString) then TSM.Groups.SetItemGroup(itemString, groupPath) numAdded = numAdded + 1 end end Log.PrintfUser(L["%d groups were created and %d items were added from the table."], numCreated, numAdded) else error("Unexpected index1: "..tostring(index1)) end end