diff --git a/app/styles/clans/clan-details.sass b/app/styles/clans/clan-details.sass index 8ad366396..0fda207bd 100644 --- a/app/styles/clans/clan-details.sass +++ b/app/styles/clans/clan-details.sass @@ -55,8 +55,11 @@ vertical-align: middle + .member-header + cursor: pointer + .progress-header - margin-right: 14px + cursor: pointer .progress-key cursor: default @@ -74,6 +77,7 @@ .progress-key-complete background-color: lightgray + margin-left: 14px .expand-progress-checkbox margin-left: 14px diff --git a/app/templates/clans/clan-details.jade b/app/templates/clans/clan-details.jade index b21284abe..4242610c4 100644 --- a/app/templates/clans/clan-details.jade +++ b/app/templates/clans/clan-details.jade @@ -83,10 +83,21 @@ block content table.table.table-condensed thead tr - th(data-i18n="resources.hero") Hero th - span.progress-header(data-i18n="clans.progress") Progress - span.progress-key.progress-key-complete(data-i18n="clans.complete_1") complete + span.member-header.spr(data-i18n="resources.hero") Hero + if memberSort === 'nameAsc' + span.member-header.glyphicon.glyphicon-chevron-up + else if memberSort === 'nameDesc' + span.member-header.glyphicon.glyphicon-chevron-down + th + span.progress-header.spr(data-i18n="clans.progress") Progress + if memberSort === 'progressAsc' + span.progress-header.glyphicon.glyphicon-chevron-up + else if memberSort === 'progressDesc' + span.progress-header.glyphicon.glyphicon-chevron-down + else + span(style='padding-left:16px;') + span.spl.progress-key.progress-key-complete(data-i18n="clans.complete_1") complete span.progress-key.progress-key-started(data-i18n="clans.started_1") started span.progress-key(data-i18n="clans.not_started_1") not started input.expand-progress-checkbox(type='checkbox') diff --git a/app/views/clans/ClanDetailsView.coffee b/app/views/clans/ClanDetailsView.coffee index 99c8eb2e6..367664a85 100644 --- a/app/views/clans/ClanDetailsView.coffee +++ b/app/views/clans/ClanDetailsView.coffee @@ -27,6 +27,8 @@ module.exports = class ClanDetailsView extends RootView 'click .edit-name-save-btn': 'onEditNameSave' 'click .join-clan-btn': 'onJoinClan' 'click .leave-clan-btn': 'onLeaveClan' + 'click .member-header': 'onClickMemberHeader' + 'click .progress-header': 'onClickProgressHeader' 'click .progress-level-cell': 'onClickLevel' 'click .remove-member-btn': 'onRemoveMember' 'mouseenter .progress-level-cell': 'onMouseEnterPoint' @@ -41,6 +43,7 @@ module.exports = class ClanDetailsView extends RootView initData: -> @showExpandedProgress = false + @memberSort = 'nameAsc' @stats = {} @campaigns = new CocoCollection([], { url: "/db/campaign", model: Campaign, comparator:'_id' }) @@ -77,22 +80,25 @@ module.exports = class ClanDetailsView extends RootView context.memberLanguageMap = @memberLanguageMap context.memberLevelStateMap = @memberLevelMap ? {} context.memberMaxLevelCount = @memberMaxLevelCount - context.members = @members?.models + context.memberSort = @memberSort context.isOwner = @clan.get('ownerID') is me.id context.isMember = @clanID in (me.get('clans') ? []) context.stats = @stats # Find last campaign level for each user + # TODO: why do we do this for every render? + highestUserLevelCountMap = {} lastUserCampaignLevelMap = {} maxLastUserCampaignLevel = 0 userConceptsMap = {} if @campaigns.loaded + levelCount = 0 for campaign in @campaigns.models campaignID = campaign.id lastLevelIndex = 0 for levelID, level of campaign.get('levels') levelSlug = level.slug - for member in context.members + for member in @members?.models ? [] if context.memberLevelStateMap[member.id]?[levelSlug] lastUserCampaignLevelMap[member.id] ?= {} lastUserCampaignLevelMap[member.id][campaignID] ?= {} @@ -105,8 +111,12 @@ module.exports = class ClanDetailsView extends RootView for concept in level.concepts continue if userConceptsMap[member.id][concept] is 'complete' userConceptsMap[member.id][concept] = context.memberLevelStateMap[member.id][levelSlug].state + highestUserLevelCountMap[member.id] = levelCount lastLevelIndex++ + levelCount++ + @sortMembers(highestUserLevelCountMap, userConceptsMap) if @clan.get('dashboardType') is 'premium' + context.members = @members?.models ? [] context.lastUserCampaignLevelMap = lastUserCampaignLevelMap context.showExpandedProgress = maxLastUserCampaignLevel <= 30 or @showExpandedProgress context.userConceptsMap = userConceptsMap @@ -122,6 +132,30 @@ module.exports = class ClanDetailsView extends RootView @memberAchievements.fetch cache: false @memberSessions.fetch cache: false + sortMembers: (highestUserLevelCountMap, userConceptsMap) -> + # Progress sort precedence: most concepts, most levels, name sort + return unless @members? and @memberSort? + switch @memberSort + when "nameDesc" + @members.comparator = (a, b) -> return (b.get('name') or 'Anoner').localeCompare(a.get('name') or 'Anoner') + when "progressAsc" + @members.comparator = (a, b) -> + if Object.keys(userConceptsMap[a.id]).length < Object.keys(userConceptsMap[b.id]).length then return -1 + else if Object.keys(userConceptsMap[a.id]).length > Object.keys(userConceptsMap[b.id]).length then return 1 + if highestUserLevelCountMap[a.id] < highestUserLevelCountMap[b.id] then return -1 + else if highestUserLevelCountMap[a.id] > highestUserLevelCountMap[b.id] then return 1 + (a.get('name') or 'Anoner').localeCompare(b.get('name') or 'Anoner') + when "progressDesc" + @members.comparator = (a, b) -> + if Object.keys(userConceptsMap[a.id]).length > Object.keys(userConceptsMap[b.id]).length then return -1 + else if Object.keys(userConceptsMap[a.id]).length < Object.keys(userConceptsMap[b.id]).length then return 1 + if highestUserLevelCountMap[a.id] > highestUserLevelCountMap[b.id] then return -1 + else if highestUserLevelCountMap[a.id] < highestUserLevelCountMap[b.id] then return 1 + (b.get('name') or 'Anoner').localeCompare(a.get('name') or 'Anoner') + else + @members.comparator = (a, b) -> return (a.get('name') or 'Anoner').localeCompare(b.get('name') or 'Anoner') + @members.sort() + updateHeroIcons: -> return unless @members?.models? for member in @members.models @@ -284,6 +318,14 @@ module.exports = class ClanDetailsView extends RootView success: (model, response, options) => @refreshData() @supermodel.addRequestResource( 'leave_clan', options).load() + onClickMemberHeader: (e) -> + @memberSort = if @memberSort is 'nameAsc' then 'nameDesc' else 'nameAsc' + @render?() + + onClickProgressHeader: (e) -> + @memberSort = if @memberSort is 'progressAsc' then 'progressDesc' else 'progressAsc' + @render?() + onRemoveMember: (e) -> return unless window.confirm("Remove Hero?") if memberID = $(e.target).data('id')