student notes

pull/5932/head
Thibault Duplessis 2020-01-17 19:05:54 -06:00
parent 7b2b0e8f82
commit 0ff2435307
7 changed files with 140 additions and 50 deletions

View File

@ -27,8 +27,8 @@ final class Clas(
.bindFromRequest()(ctx.body)
.fold(
err => BadRequest(html.clas.clas.create(err)).fuccess,
setup =>
env.clas.api.clas.create(setup, t.teacher) map { clas =>
data =>
env.clas.api.clas.create(data, t.teacher) map { clas =>
Redirect(routes.Clas.show(clas.id.value))
}
)
@ -69,9 +69,9 @@ final class Clas(
.bindFromRequest()(ctx.body)
.fold(
err => BadRequest(html.clas.clas.edit(clas, err)).fuccess,
setup =>
env.clas.api.clas.update(clas, setup) map { clas =>
Redirect(routes.Clas.show(clas.id.value))
data =>
env.clas.api.clas.update(clas, data) map { clas =>
Redirect(routes.Clas.show(clas.id.value)).flashSuccess
}
)
}
@ -159,20 +159,39 @@ final class Clas(
def studentShow(id: String, username: String) = Secure(_.Teacher) { implicit ctx => me =>
WithClassAndStudents(me, id) { _ => (clas, students) =>
env.user.repo named username flatMap {
_ ?? { user =>
env.clas.api.student.get(clas, user) flatMap {
_ ?? { student =>
env.activity.read.recent(student.user, 14) map { activity =>
views.html.clas.student.show(clas, students, student, activity)
}
}
}
WithStudent(clas, username) { s =>
env.activity.read.recent(s.user, 14) map { activity =>
views.html.clas.student.show(clas, students, s, activity)
}
}
}
}
def studentEdit(id: String, username: String) = Secure(_.Teacher) { implicit ctx => me =>
WithClassAndStudents(me, id) { _ => (clas, students) =>
WithStudent(clas, username) { s =>
Ok(views.html.clas.student.edit(clas, students, s, env.clas.forms.student edit s.student)).fuccess
}
}
}
def studentUpdate(id: String, username: String) = SecureBody(_.Teacher) { implicit ctx => me =>
WithClassAndStudents(me, id) { _ => (clas, students) =>
WithStudent(clas, username) { s =>
env.clas.forms.student
.edit(s.student)
.bindFromRequest()(ctx.body)
.fold(
err => BadRequest(html.clas.student.edit(clas, students, s, err)).fuccess,
data =>
env.clas.api.student.update(s.student, data) map { _ =>
Redirect(routes.Clas.studentShow(clas.id.value, s.user.username)).flashSuccess
}
)
}
}
}
def studentArchive(id: String, username: String, v: Boolean) = Secure(_.Teacher) { _ => me =>
WithClass(me, id) { t => clas =>
WithStudent(clas, username) { s =>

View File

@ -59,6 +59,7 @@ object clas {
)("Add student")
)
),
standardFlash(),
clas.desc.nonEmpty option div(cls := "box__pad clas-desc")(clas.desc),
teachers(clas),
students.partition(_.student.isArchived) match {

View File

@ -7,6 +7,7 @@ import lila.api.Context
import lila.app.templating.Environment._
import lila.app.ui.ScalatagsTemplate._
import lila.clas.{ Clas, Student }
import lila.common.String.html.richText
object student {
@ -18,29 +19,7 @@ object student {
)(implicit ctx: Context) =
bits.layout(s.user.username, Left(clas withStudents students), s.student.some)(
cls := "student-show",
div(cls := "student-show__top")(
h1(dataIcon := "r")(
span(
strong(s.user.username),
em(s.student.realName)
)
),
div(cls := "student-show__top__meta")(
p(
"Invited to ",
a(href := routes.Clas.show(clas.id.value))(clas.name),
" by ",
userIdLink(s.student.created.by.value.some, withOnline = false),
" ",
momentFromNowOnce(s.student.created.at)
),
a(
href := routes.User.show(s.user.username),
cls := "button button-empty",
title := "View full Lichess profile"
)("profile")
)
),
top(clas, s),
div(cls := "box__pad")(
standardFlash(),
s.student.archived map { archived =>
@ -59,6 +38,7 @@ object student {
)
)
},
s.student.notes.nonEmpty option div(cls := "student-show__notes")(richText(s.student.notes)),
s.student.managed option div(cls := "student-show__managed")(
p("This student account is managed"),
div(cls := "student-show__managed__actions")(
@ -91,6 +71,38 @@ object student {
)
)
private def top(clas: Clas, s: Student.WithUser) =
div(cls := "student-show__top")(
h1(dataIcon := "r")(
span(
strong(s.user.username),
em(s.student.realName)
)
),
div(cls := "student-show__top__meta")(
p(
"Invited to ",
a(href := routes.Clas.show(clas.id.value))(clas.name),
" by ",
userIdLink(s.student.created.by.value.some, withOnline = false),
" ",
momentFromNowOnce(s.student.created.at)
),
div(
a(
href := routes.Clas.studentEdit(clas.id.value, s.user.username),
cls := "button button-empty",
title := "Edit student"
)("Edit"),
a(
href := routes.User.show(s.user.username),
cls := "button button-empty",
title := "View full Lichess profile"
)("Profile")
)
)
)
private val sortNumberTh = th(attr("data-sort-method") := "number")
private val dataSort = attr("data-sort")
@ -128,7 +140,7 @@ object student {
)
)
private def realName(form: Form[_])(implicit ctx: Context) =
private def realNameField(form: Form[_])(implicit ctx: Context) =
form3.group(
form("realName"),
frag("Real name"),
@ -181,7 +193,7 @@ object student {
form3.group(invite("username"), frag("Lichess username"))(
form3.input(_, klass = "user-autocomplete")(created.isEmpty option autofocus)(dataTag := "span")
),
realName(invite),
realNameField(invite),
form3.submit("Invite")
)
),
@ -204,9 +216,29 @@ object student {
form3.group(create("username"), frag("Lichess username"))(
form3.input(_)(created.isDefined option autofocus)
),
realName(create),
realNameField(create),
form3.submit(trans.signUp())
)
)
)
def edit(clas: Clas, students: List[Student], s: Student.WithUser, form: Form[_])(implicit ctx: Context) =
bits.layout(s.user.username, Left(clas withStudents students), s.student.some)(
cls := "student-show student-edit",
top(clas, s),
div(cls := "box__pad")(
standardFlash(),
postForm(cls := "form3", action := routes.Clas.studentUpdate(clas.id.value, s.user.username))(
form3.globalError(form),
realNameField(form),
form3.group(form("notes"), raw("Notes"), help = frag("Only visible to the class teachers").some)(
form3.textarea(_)(autofocus, rows := 15)
),
form3.actions(
a(href := routes.Clas.studentShow(clas.id.value, s.user.username))(trans.cancel()),
form3.submit(trans.apply())
)
)
)
)
}

View File

@ -451,6 +451,8 @@ GET /class/$id<\w{8}>/student/:username controllers.Clas.studentShow(id: Strin
POST /class/$id<\w{8}>/student/:username/archive controllers.Clas.studentArchive(id: String, username: String, v: Boolean)
POST /class/$id<\w{8}>/student/:username/reset-password controllers.Clas.studentResetPassword(id: String, username: String)
POST /class/$id<\w{8}>/student/:username/set-kid controllers.Clas.studentSetKid(id: String, username: String, v: Boolean)
GET /class/$id<\w{8}>/student/:username/edit controllers.Clas.studentEdit(id: String, username: String)
POST /class/$id<\w{8}>/student/:username/edit controllers.Clas.studentUpdate(id: String, username: String)
# DB image
GET /image/:id/:hash/:name controllers.Main.image(id: String, hash: String, name: String)

View File

@ -111,6 +111,11 @@ final class ClasApi(
// def isIn(clas: Clas, userId: User.ID): Fu[Boolean] =
// coll.exists($id(Student.id(userId, clas.id)))
def update(from: Student, data: ClasForm.StudentData): Fu[Student] = {
val student = data update from
coll.update.one($id(student.id), student) inject student
}
def create(
clas: Clas,
data: ClasForm.NewStudent,

View File

@ -43,6 +43,14 @@ final class ClasForm(
)(NewStudent.apply)(NewStudent.unapply)
)
def edit(s: Student) =
Form(
mapping(
"realName" -> nonEmptyText,
"notes" -> text(maxLength = 20000)
)(StudentData.apply)(StudentData.unapply)
) fill StudentData(s.realName, s.notes)
private def blockingFetchUser(username: String) =
lightUserAsync(lila.user.User normalize username).await(1 second, "clasInviteUser")
}
@ -64,4 +72,14 @@ object ClasForm {
username: String,
realName: String
)
case class StudentData(
realName: String,
notes: String
) {
def update(c: Student) = c.copy(
realName = realName,
notes = notes
)
}
}

View File

@ -7,18 +7,23 @@ $clas-color: rgb(127, 90, 240);
max-width: 15vw;
}
&.student {
font-weight: bold;
padding: .4rem 2vw .4rem .8rem;
&.active {
color: $clas-color;
}
&::after {
background: $clas-color;
}
em {
@extend %roboto;
font-weight: normal;
display: block;
display: none;
}
@include breakpoint($mq-subnav-side) {
font-weight: bold;
padding: .4rem 2vw .4rem .8rem;
&.active {
color: $clas-color;
}
&::after {
background: $clas-color;
}
em {
font-weight: normal;
display: block;
}
}
}
}
@ -75,6 +80,10 @@ $clas-color: rgb(127, 90, 240);
margin-bottom: 2em;
}
.teachers {
margin: 1em 0 2em 0;
}
.students {
&-archived {
margin-top: 2em;
@ -135,6 +144,10 @@ $clas-color: rgb(127, 90, 240);
}
}
&__notes {
margin-bottom: 2em;
}
&__managed {
@extend %box-radius, %flex-between;
background: fade-out($c-primary, .8);