class WIP
parent
5510460d69
commit
5a2a8ea80a
|
@ -52,15 +52,15 @@ final class Clas(
|
|||
isGranted(_.Teacher).??(env.clas.api.clas.isTeacherOf(me, lila.clas.Clas.Id(id))) flatMap {
|
||||
case true =>
|
||||
WithClass(me, id) { _ => clas =>
|
||||
env.clas.api.student.allOfWithUsers(clas) map { students =>
|
||||
env.clas.api.student.activeWithUsers(clas) map { students =>
|
||||
preloadStudentUsers(students)
|
||||
views.html.clas.teacherDashboard(clas, students)
|
||||
views.html.clas.teacherDashboard.active(clas, students)
|
||||
}
|
||||
}
|
||||
case _ =>
|
||||
env.clas.api.clas.byId(lila.clas.Clas.Id(id)) flatMap {
|
||||
_ ?? { clas =>
|
||||
env.clas.api.student.activeOfWithUsers(clas) flatMap { students =>
|
||||
env.clas.api.student.activeWithUsers(clas) flatMap { students =>
|
||||
if (students.exists(_.student is me)) {
|
||||
preloadStudentUsers(students)
|
||||
Ok(views.html.clas.studentDashboard(clas, students)).fuccess
|
||||
|
@ -71,6 +71,24 @@ final class Clas(
|
|||
}
|
||||
}
|
||||
|
||||
def archived(id: String) = Secure(_.Teacher) { implicit ctx => me =>
|
||||
WithClass(me, id) { _ => clas =>
|
||||
env.clas.api.student.allWithUsers(clas) map { students =>
|
||||
views.html.clas.teacherDashboard.archived(clas, students)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def perfType(id: String, key: String) = Secure(_.Teacher) { implicit ctx => me =>
|
||||
lila.rating.PerfType(key) ?? { perfType =>
|
||||
WithClass(me, id) { _ => clas =>
|
||||
env.clas.api.student.activeWithUsers(clas) map { students =>
|
||||
views.html.clas.teacherDashboard.perf(clas, students, perfType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def edit(id: String) = Secure(_.Teacher) { implicit ctx => me =>
|
||||
WithClass(me, id) { _ => clas =>
|
||||
Ok(html.clas.clas.edit(clas, env.clas.forms.edit(clas))).fuccess
|
||||
|
|
|
@ -184,7 +184,7 @@ trait UserHelper { self: I18nHelper with StringHelper with NumberHelper =>
|
|||
withTitle: Boolean = true,
|
||||
withBestRating: Boolean = false,
|
||||
withPerfRating: Option[PerfType] = None,
|
||||
text: Option[String] = None
|
||||
name: Option[Frag] = None
|
||||
): Frag =
|
||||
span(
|
||||
cls := userClass(user.id, cssClass, withOnline, withPowerTip),
|
||||
|
@ -192,7 +192,7 @@ trait UserHelper { self: I18nHelper with StringHelper with NumberHelper =>
|
|||
)(
|
||||
withOnline ?? lineIcon(user),
|
||||
withTitle option titleTag(user.title),
|
||||
text | user.username,
|
||||
name | user.username,
|
||||
userRating(user, withPerfRating, withBestRating)
|
||||
)
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package views.html.clas
|
||||
|
||||
import controllers.routes
|
||||
import lila.api.Context
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.clas.{ Clas, Student }
|
||||
import controllers.routes
|
||||
|
||||
object bits {
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ object studentDashboard {
|
|||
students: List[Student.WithUser]
|
||||
)(implicit ctx: Context) =
|
||||
bits.layout(c.name, Left(c withStudents Nil))(
|
||||
cls := "clas-show student-dashboard",
|
||||
cls := "clas-show dashboard dashboard-student",
|
||||
div(cls := "box__top")(
|
||||
h1(dataIcon := "f", cls := "text")(c.name)
|
||||
),
|
||||
|
|
|
@ -2,6 +2,7 @@ package views.html.clas
|
|||
|
||||
import controllers.routes
|
||||
import lila.api.Context
|
||||
import lila.rating.PerfType
|
||||
import lila.app.templating.Environment._
|
||||
import lila.app.ui.ScalatagsTemplate._
|
||||
import lila.clas.{ Clas, Student }
|
||||
|
@ -9,12 +10,13 @@ import lila.common.String.html.richText
|
|||
|
||||
object teacherDashboard {
|
||||
|
||||
def apply(
|
||||
private def dashboard(
|
||||
c: Clas,
|
||||
students: List[Student.WithUser]
|
||||
)(implicit ctx: Context) =
|
||||
students: List[Student.WithUser],
|
||||
active: String
|
||||
)(content: Frag)(implicit ctx: Context) =
|
||||
bits.layout(c.name, Left(c withStudents students.map(_.student)))(
|
||||
cls := "clas-show teacher-dashboard",
|
||||
cls := "clas-show dashboard dashboard-teacher",
|
||||
div(cls := "box__top")(
|
||||
h1(dataIcon := "f", cls := "text")(c.name),
|
||||
div(cls := "box__top__actions")(
|
||||
|
@ -45,49 +47,113 @@ object teacherDashboard {
|
|||
},
|
||||
clas.teachers(c)
|
||||
),
|
||||
students.partition(_.student.isArchived) match {
|
||||
case (archived, active) =>
|
||||
frag(
|
||||
div(cls := "students")(studentList(c, active)("Students")),
|
||||
archived.nonEmpty option div(cls := "students students-archived")(
|
||||
studentList(c, archived)("Archived students")
|
||||
)
|
||||
)
|
||||
}
|
||||
st.nav(cls := "dashboard-nav tabs-horiz")(
|
||||
a(cls := active.active("students"), href := routes.Clas.show(c.id.value))("Students"),
|
||||
List(PerfType.Bullet, PerfType.Blitz, PerfType.Rapid, PerfType.Classical, PerfType.Correspondence)
|
||||
.map { pt =>
|
||||
a(cls := active.active(pt.key), href := routes.Clas.perfType(c.id.value, pt.key))(pt.name),
|
||||
},
|
||||
a(cls := active.active("archived"), href := routes.Clas.archived(c.id.value))("Archived")
|
||||
),
|
||||
content
|
||||
)
|
||||
|
||||
def studentList(c: Clas, students: List[Student.WithUser])(title: Frag)(implicit ctx: Context) =
|
||||
def active(
|
||||
c: Clas,
|
||||
students: List[Student.WithUser]
|
||||
)(implicit ctx: Context) =
|
||||
dashboard(c, students, "students")(
|
||||
studentList(c, students)
|
||||
)
|
||||
|
||||
def archived(
|
||||
c: Clas,
|
||||
students: List[Student.WithUser]
|
||||
)(implicit ctx: Context) =
|
||||
dashboard(c, students.filter(_.student.isActive), "archived")(
|
||||
studentList(c, students.filter(_.student.isArchived))
|
||||
)
|
||||
|
||||
def perf(
|
||||
c: Clas,
|
||||
students: List[Student.WithUser],
|
||||
perfType: PerfType
|
||||
)(implicit ctx: Context) =
|
||||
dashboard(c, students, perfType.key)(
|
||||
div(cls := "students")(
|
||||
table(cls := "slist slist-pad sortable")(
|
||||
thead(
|
||||
tr(
|
||||
th(attr("data-sort-default") := "1")(),
|
||||
sortNumberTh("Rating"),
|
||||
sortNumberTh("Games"),
|
||||
sortNumberTh("Progress")
|
||||
)
|
||||
),
|
||||
tbody(
|
||||
students.sortBy(_.user.username).map {
|
||||
case s @ Student.WithUser(_, user) =>
|
||||
tr(
|
||||
studentTd(c, s),
|
||||
td(dataSort := user.perfs(perfType).intRating, cls := "rating")(
|
||||
user.perfs(perfType).showRatingProvisional
|
||||
),
|
||||
td(user.perfs(perfType).nb),
|
||||
td(dataSort := user.perfs(perfType).progress)(user.perfs(perfType).progress)
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
private def studentList(c: Clas, students: List[Student.WithUser])(implicit ctx: Context) =
|
||||
if (students.isEmpty)
|
||||
frag(hr, p(cls := "box__pad students__empty")("No students in the class, yet."))
|
||||
else
|
||||
table(cls := "slist slist-pad sortable")(
|
||||
thead(
|
||||
tr(
|
||||
th(attr("data-sort-default") := "1")(title),
|
||||
th("Real name"),
|
||||
sortNumberTh("Rating"),
|
||||
sortNumberTh("Games"),
|
||||
sortNumberTh("Active")
|
||||
div(cls := "students")(
|
||||
table(cls := "slist slist-pad sortable")(
|
||||
thead(
|
||||
tr(
|
||||
th(attr("data-sort-default") := "1")("Student"),
|
||||
th("Real name"),
|
||||
sortNumberTh("Rating"),
|
||||
sortNumberTh("Games"),
|
||||
sortNumberTh("Puzzles"),
|
||||
sortNumberTh("Active")
|
||||
)
|
||||
),
|
||||
tbody(
|
||||
students.sortBy(_.user.username).map {
|
||||
case s @ Student.WithUser(student, user) =>
|
||||
tr(
|
||||
studentTd(c, s),
|
||||
td(student.realName),
|
||||
td(dataSort := user.perfs.bestRating, cls := "rating")(user.best3Perfs.map {
|
||||
showPerfRating(user, _)
|
||||
}),
|
||||
td(user.count.game.localize),
|
||||
td(user.perfs.puzzle.nb),
|
||||
td(dataSort := user.seenAt.map(_.getMillis.toString))(user.seenAt.map(momentFromNowOnce))
|
||||
)
|
||||
}
|
||||
)
|
||||
),
|
||||
tbody(
|
||||
students.sortBy(_.user.username).map {
|
||||
case Student.WithUser(student, user) =>
|
||||
tr(
|
||||
td(
|
||||
a(href := routes.Clas.studentShow(c.id.value, user.username))(
|
||||
userSpan(user)
|
||||
)
|
||||
),
|
||||
td(student.realName),
|
||||
td(user.perfs.bestRating),
|
||||
td(user.count.game.localize),
|
||||
td(dataSort := user.seenAt.map(_.getMillis.toString))(user.seenAt.map(momentFromNowOnce))
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
private def studentTd(c: Clas, s: Student.WithUser) =
|
||||
td(
|
||||
a(href := routes.Clas.studentShow(c.id.value, s.user.username))(
|
||||
userSpan(
|
||||
s.user,
|
||||
name = span(
|
||||
strong(s.user.username),
|
||||
em(s.student.realName)
|
||||
).some
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
private val sortNumberTh = th(attr("data-sort-method") := "number")
|
||||
private val dataSort = attr("data-sort")
|
||||
}
|
||||
|
|
|
@ -445,6 +445,8 @@ GET /class/$id<\w{8}> controllers.Clas.show(id: String)
|
|||
GET /class/$id<\w{8}>/edit controllers.Clas.edit(id: String)
|
||||
POST /class/$id<\w{8}>/edit controllers.Clas.update(id: String)
|
||||
POST /class/$id<\w{8}>/archive controllers.Clas.archive(id: String, v: Boolean)
|
||||
GET /class/$id<\w{8}>/archived controllers.Clas.archived(id: String)
|
||||
GET /class/$id<\w{8}>/perf/:pt controllers.Clas.perfType(id: String, pt: String)
|
||||
GET /class/$id<\w{8}>/student/add controllers.Clas.studentForm(id: String)
|
||||
POST /class/$id<\w{8}>/student/new controllers.Clas.studentCreate(id: String)
|
||||
POST /class/$id<\w{8}>/student/invite controllers.Clas.studentInvite(id: String)
|
||||
|
|
|
@ -87,9 +87,9 @@ final class ClasApi(
|
|||
def activeOf(clas: Clas): Fu[List[Student]] =
|
||||
of($doc("clasId" -> clas.id, "archived" $exists false))
|
||||
|
||||
def allOfWithUsers(clas: Clas): Fu[List[Student.WithUser]] =
|
||||
def allWithUsers(clas: Clas): Fu[List[Student.WithUser]] =
|
||||
of($doc("clasId" -> clas.id)) flatMap withUsers
|
||||
def activeOfWithUsers(clas: Clas): Fu[List[Student.WithUser]] =
|
||||
def activeWithUsers(clas: Clas): Fu[List[Student.WithUser]] =
|
||||
of($doc("clasId" -> clas.id, "archived" $exists false)) flatMap withUsers
|
||||
|
||||
private def of(selector: Bdoc): Fu[List[Student]] =
|
||||
|
|
|
@ -69,6 +69,8 @@ case class Perf(
|
|||
def rankable(variant: chess.variant.Variant) = glicko.rankable(variant)
|
||||
def provisional = glicko.provisional
|
||||
def established = glicko.established
|
||||
|
||||
def showRatingProvisional = s"$intRating${provisional ?? "?"}"
|
||||
}
|
||||
|
||||
case object Perf {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
@include breakpoint($mq-not-xx-small) {
|
||||
font-size: .9em;
|
||||
}
|
||||
span {
|
||||
span, > a {
|
||||
@extend %roboto;
|
||||
flex: 1 1 auto;
|
||||
text-align: center;
|
||||
|
|
|
@ -101,44 +101,47 @@ $clas-color: rgb(127, 90, 240);
|
|||
}
|
||||
}
|
||||
|
||||
.student-dashboard {
|
||||
.dashboard {
|
||||
|
||||
.students {
|
||||
.user-link {
|
||||
@extend %flex-center;
|
||||
.line {
|
||||
font-size: 2em;
|
||||
}
|
||||
em {
|
||||
@extend %roboto;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.rating span {
|
||||
&::before {
|
||||
margin: 0;
|
||||
}
|
||||
margin: 0 .3em;
|
||||
}
|
||||
.button {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
&-student {
|
||||
.students {
|
||||
.button {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.teacher-dashboard {
|
||||
&-dashboard {
|
||||
|
||||
.students {
|
||||
&-archived {
|
||||
margin-top: 2em;
|
||||
}
|
||||
&__empty {
|
||||
margin-bottom: 8em;
|
||||
}
|
||||
td:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
td:nth-child(2) {
|
||||
color: $c-font-dim;
|
||||
.students {
|
||||
.user-link {
|
||||
@extend %flex-center;
|
||||
.line {
|
||||
font-size: 2em;
|
||||
}
|
||||
em {
|
||||
@extend %roboto;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
&__empty {
|
||||
margin-bottom: 8em;
|
||||
}
|
||||
td:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
td:nth-child(2) {
|
||||
color: $c-font-dim;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
@import '../../../common/css/form/form3';
|
||||
@import '../../../common/css/component/slist';
|
||||
@import '../../../common/css/component/tablesort';
|
||||
@import '../../../common/css/component/tabs-horiz';
|
||||
@import '../user/activity';
|
||||
@import '../clas';
|
||||
|
|
Loading…
Reference in New Issue