From 45c070209b734650ee04c9ccaebc422edb835249 Mon Sep 17 00:00:00 2001
From: Matt Lott <mattlott@live.com>
Date: Thu, 16 Apr 2015 15:26:14 -0700
Subject: [PATCH] Update private clans dashboard

---
 app/styles/clans/clan-details.sass     |  32 +++++++-
 app/templates/clans/clan-details.jade  | 106 +++++++++++++++++--------
 app/views/clans/ClanDetailsView.coffee |  37 +++++++--
 server/clans/clan_handler.coffee       |  13 +--
 4 files changed, 142 insertions(+), 46 deletions(-)

diff --git a/app/styles/clans/clan-details.sass b/app/styles/clans/clan-details.sass
index 2f092b68a..fc50e7559 100644
--- a/app/styles/clans/clan-details.sass
+++ b/app/styles/clans/clan-details.sass
@@ -25,9 +25,39 @@
 
   $spriteSheetSize: 30px
 
-  td.hero-icon-cell
+
+  .remove-hero-cell
+    width: 100px
+
+  .hero-icon-cell
     width: 30px
 
+  .level-cell
+    width: 50px
+    text-align: center
+
+  .name-cell
+    width: 100px
+
+  .level-progression-cell
+    background-color: lightblue
+    border: 1px solid gray
+    // cursor: pointer
+    padding: 4px
+
+    .level-popup-container
+      display: none
+      position: absolute
+      padding: 10px
+      border: 1px solid black
+      z-index: 3
+      background-color: blanchedalmond
+      font-size: 10pt
+
+  .level-progression-cell-name
+    background-color: lightblue
+    border: 1px solid gray
+
   .player-hero-icon
     background: transparent url(/images/pages/play/play-spritesheet.png)
     background-size: cover
diff --git a/app/templates/clans/clan-details.jade b/app/templates/clans/clan-details.jade
index f642e3bcf..2775edcc5 100644
--- a/app/templates/clans/clan-details.jade
+++ b/app/templates/clans/clan-details.jade
@@ -54,7 +54,7 @@ block content
       tr
         td Average Level
         td= stats.averageLevel
-    if stats.totalAchievements
+    if stats.totalAchievements && clan.get('type') === 'public'
       tr
         td Total Achievements
         td= stats.totalAchievements
@@ -67,40 +67,78 @@ block content
     else
       button.btn.btn-lg.btn-success.join-clan-btn Join Clan
 
-  div
-    span.spl.spr.join-link-prompt Invite:
-    input.join-clan-link(type="text", readonly, value="#{joinClanLink}")
-  .small *Invite players to this Clan by sending them this link.
+  if clan.get('ownerID') === me.id || clan.get('type') === 'public'
+    div
+      span.spl.spr.join-link-prompt Invite:
+      input.join-clan-link(type="text", readonly, value="#{joinClanLink}")
+    .small *Invite players to this Clan by sending them this link.
 
   if members
     h3 Heroes (#{members.length})
-    table.table.table-striped.table-condensed
-      thead
-        tr
-          th
-          th 
-          td Name
-          th Level
-          th Achievements
-          th Latest Achievement
-          th  
-      tbody
-        each member in members
+    if clan.get('type') === 'private'
+      table.table.table-condensed
+        thead
           tr
-            td.hero-icon-cell
-              span.spr.player-hero-icon(data-memberid="#{member.id}")
-            td.code-language-cell
-              if memberLanguageMap && memberLanguageMap[member.id]
-                span.code-language-cell(style="background-image: url(/images/common/code_languages/#{memberLanguageMap[member.id]}_small.png)", title=memberLanguageMap[member.id])
-            td  
-              a(href="/user/#{member.id}")= member.get('name') || 'Anoner'
-            td= member.level()
-            td
-              if memberAchievementsMap && memberAchievementsMap[member.id]
-                | #{memberAchievementsMap[member.id].length}
-            td
-              if memberAchievementsMap && memberAchievementsMap[member.id] && memberAchievementsMap[member.id].length
-                span= memberAchievementsMap[member.id][0].get('achievementName')
-            td
-              if isOwner && member.id !== clan.get('ownerID')
-                  button.btn.btn-xs.btn-warning.remove-member-btn(data-id="#{member.id}") Remove Hero
+            if isOwner
+              th
+            th
+            th
+            th Level
+            th Name
+            th(colspan="#{memberMaxLevelCount + 1}") Last Level Completed
+        tbody
+          each member in members
+            tr
+              if isOwner
+                td.remove-hero-cell
+                  if member.id !== clan.get('ownerID')
+                    button.btn.btn-xs.btn-warning.remove-member-btn(data-id="#{member.id}") Remove Hero
+              td.hero-icon-cell
+                span.spr.player-hero-icon(data-memberid="#{member.id}")
+              td.code-language-cell
+                if memberLanguageMap && memberLanguageMap[member.id]
+                  span.code-language-cell(style="background-image: url(/images/common/code_languages/#{memberLanguageMap[member.id]}_small.png)", title=memberLanguageMap[member.id])
+              td.level-cell= member.level()
+              td.name-cell
+                a(href="/user/#{member.id}")= member.get('name') || 'Anoner'
+              if memberLevelProgression && memberLevelProgression[member.id]
+                each levelInfo in memberLevelProgression[member.id]
+                  td.level-progression-cell
+                    .level-popup-container
+                      div Level: #{levelInfo.level}
+                      div Playtime: #{levelInfo.playtime}
+                      div Last played: #{levelInfo.changed}
+                td(colspan="#{memberMaxLevelCount - memberLevelProgression[member.id].length + 1}")= memberLevelProgression[member.id][memberLevelProgression[member.id].length - 1].level
+              else
+                td(colspan="#{memberMaxLevelCount + 1}")
+    else
+      table.table.table-striped.table-condensed
+        thead
+          tr
+            th
+            th 
+            td Name
+            th Level
+            th Achievements
+            th Latest Achievement
+            th  
+        tbody
+          each member in members
+            tr
+              td.hero-icon-cell
+                span.spr.player-hero-icon(data-memberid="#{member.id}")
+              td.code-language-cell
+                if memberLanguageMap && memberLanguageMap[member.id]
+                  span.code-language-cell(style="background-image: url(/images/common/code_languages/#{memberLanguageMap[member.id]}_small.png)", title=memberLanguageMap[member.id])
+              td  
+                a(href="/user/#{member.id}")= member.get('name') || 'Anoner'
+              td= member.level()
+              td
+                if memberAchievementsMap && memberAchievementsMap[member.id]
+                  | #{memberAchievementsMap[member.id].length}
+              td
+                if memberAchievementsMap && memberAchievementsMap[member.id] && memberAchievementsMap[member.id].length
+                  span= memberAchievementsMap[member.id][0].get('achievementName')
+              td
+                if isOwner && member.id !== clan.get('ownerID')
+                    button.btn.btn-xs.btn-warning.remove-member-btn(data-id="#{member.id}") Remove Hero
diff --git a/app/views/clans/ClanDetailsView.coffee b/app/views/clans/ClanDetailsView.coffee
index 894214b42..4a039c431 100644
--- a/app/views/clans/ClanDetailsView.coffee
+++ b/app/views/clans/ClanDetailsView.coffee
@@ -24,6 +24,8 @@ module.exports = class ClanDetailsView extends RootView
     'click .join-clan-btn': 'onJoinClan'
     'click .leave-clan-btn': 'onLeaveClan'
     'click .remove-member-btn': 'onRemoveMember'
+    'mouseenter .level-progression-cell': 'onMouseEnterPoint'
+    'mouseleave .level-progression-cell': 'onMouseLeavePoint'
 
   constructor: (options, @clanID) ->
     super options
@@ -61,6 +63,8 @@ module.exports = class ClanDetailsView extends RootView
     context.owner = @owner
     context.memberAchievementsMap = @memberAchievementsMap
     context.memberLanguageMap = @memberLanguageMap
+    context.memberLevelProgression = @memberLevelProgression
+    context.memberMaxLevelCount = @memberMaxLevelCount
     context.members = @members?.models
     context.isOwner = @clan.get('ownerID') is me.id
     context.isMember = @clanID in (me.get('clans') ? [])
@@ -75,6 +79,7 @@ module.exports = class ClanDetailsView extends RootView
     me.fetch cache: false
     @members.fetch cache: false
     @memberAchievements.fetch cache: false
+    @memberSessions.fetch cache: false
 
   updateHeroIcons: ->
     return unless @members?.models?
@@ -106,17 +111,27 @@ module.exports = class ClanDetailsView extends RootView
     @render?()
 
   onMemberSessionsSync: ->
-    @memberSessionMap = {}
+    @memberLevelProgression = {}
+    memberSessions = {}
     for levelSession in @memberSessions.models
       user = levelSession.get('creator')
-      @memberSessionMap[user] ?= []
-      @memberSessionMap[user].push levelSession
+      if not levelSession.isMultiplayer() and levelSession.get('state')?.complete is true
+        memberSessions[user] ?= []
+        memberSessions[user].push levelSession
+        @memberLevelProgression[user] ?= []
+        levelInfo =
+          level: levelSession.get('levelName')
+          changed: new Date(levelSession.get('changed')).toLocaleString()
+          playtime: levelSession.get('playtime')
+        @memberLevelProgression[user].push levelInfo
+    @memberMaxLevelCount = 0
     @memberLanguageMap = {}
-    for user of @memberSessionMap
+    for user of memberSessions
       languageCounts = {}
-      for levelSession in @memberSessionMap[user]
+      for levelSession in memberSessions[user]
         language = levelSession.get('codeLanguage') or levelSession.get('submittedCodeLanguage')
         languageCounts[language] = (languageCounts[language] or 0) + 1 if language
+      @memberMaxLevelCount = memberSessions[user].length if @memberMaxLevelCount < memberSessions[user].length
       mostUsedCount = 0
       for language, count of languageCounts
         if count > mostUsedCount
@@ -124,6 +139,18 @@ module.exports = class ClanDetailsView extends RootView
           @memberLanguageMap[user] = language
     @render?()
 
+  onMouseEnterPoint: (e) ->
+    container = $(e.target).find('.level-popup-container').show()
+    margin = 20
+    offset = $(e.target).offset()
+    scrollTop = $(e.target).offsetParent().scrollTop()
+    height = container.outerHeight()
+    container.css('left', offset.left + e.offsetX)
+    container.css('top', offset.top + scrollTop - height - margin)
+
+  onMouseLeavePoint: (e) ->
+    $(e.target).find('.level-popup-container').hide()
+
   onDeleteClan: (e) ->
     return @openModalView(new AuthModal()) if me.isAnonymous()
     options =
diff --git a/server/clans/clan_handler.coffee b/server/clans/clan_handler.coffee
index e52717a53..80d521a55 100644
--- a/server/clans/clan_handler.coffee
+++ b/server/clans/clan_handler.coffee
@@ -68,7 +68,7 @@ ClanHandler = class ClanHandler extends Handler
       return @sendNotFoundError(res, err)
     Clan.findById clanID, (err, clan) =>
       return @sendDatabaseError(res, err) if err
-      return @sendDatabaseError(res, err) unless clan
+      return @sendNotFoundError(res) unless clan
       return @sendDatabaseError(res, err) unless clanType = clan.get('type')
       return @sendForbiddenError(res) unless clanType is 'public' or req.user.isPremium()
       Clan.update {_id: clanID}, {$addToSet: {members: req.user._id}}, (err) =>
@@ -86,7 +86,7 @@ ClanHandler = class ClanHandler extends Handler
       return @sendNotFoundError(res, err)
     Clan.findById clanID, (err, clan) =>
       return @sendDatabaseError(res, err) if err
-      return @sendDatabaseError(res, err) unless clan
+      return @sendNotFoundError(res) unless clan
       return @sendForbiddenError(res) if clan.get('ownerID')?.equals req.user._id
       Clan.update {_id: clanID}, {$pull: {members: req.user._id}}, (err) =>
         return @sendDatabaseError(res, err) if err
@@ -99,7 +99,7 @@ ClanHandler = class ClanHandler extends Handler
     # TODO: add tests
     Clan.findById clanID, (err, clan) =>
       return @sendDatabaseError(res, err) if err
-      return @sendDatabaseError(res, err) unless clan
+      return @sendNotFoundError(res) unless clan
       memberIDs = _.map clan.get('members') ? [], (memberID) -> memberID.toHexString?() or memberID
       EarnedAchievement.find {user: {$in: memberIDs}}, (err, documents) =>
         return @sendDatabaseError(res, err) if err?
@@ -110,7 +110,7 @@ ClanHandler = class ClanHandler extends Handler
     # TODO: add tests
     Clan.findById clanID, (err, clan) =>
       return @sendDatabaseError(res, err) if err
-      return @sendDatabaseError(res, err) unless clan
+      return @sendNotFoundError(res) unless clan
       memberIDs = clan.get('members') ? []
       User.find {_id: {$in: memberIDs}}, (err, users) =>
         return @sendDatabaseError(res, err) if err
@@ -119,9 +119,10 @@ ClanHandler = class ClanHandler extends Handler
 
   getMemberSessions: (req, res, clanID) ->
     # TODO: add tests
+    # TODO: restrict information returned based on clan type
     Clan.findById clanID, (err, clan) =>
       return @sendDatabaseError(res, err) if err
-      return @sendDatabaseError(res, err) unless clan
+      return @sendNotFoundError(res) unless clan
       memberIDs = _.map   clan.get('members') ? [], (memberID) -> memberID.toHexString?() or memberID
       LevelSession.find {creator: {$in: memberIDs}}, (err, documents) =>
         return @sendDatabaseError(res, err) if err?
@@ -147,7 +148,7 @@ ClanHandler = class ClanHandler extends Handler
       return @sendNotFoundError(res, err)
     Clan.findById clanID, (err, clan) =>
       return @sendDatabaseError(res, err) if err
-      return @sendDatabaseError(res, err) unless clan
+      return @sendNotFoundError(res) unless clan
       return @sendForbiddenError res unless @hasAccessToDocument(req, clan)
       return @sendForbiddenError(res) if clan.get('ownerID').equals memberID
       Clan.update {_id: clanID}, {$pull: {members: memberID}}, (err) =>