src/main/scala/fis/pm/ui/TaskSnippet.scala
author Tomas Zeman <tzeman@volny.cz>
Tue, 23 Apr 2013 10:36:04 +0200
changeset 108 ef4e3e0ef83f
parent 105 66aeb6b3a3a7
permissions -rw-r--r--
84a94fa29a67504b Task/Project notifications

/*
 * Copyright 2012 Tomas Zeman <tzeman@volny.cz>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package fis.pm.ui

import fis.aaa.ui.IfLoggedIn
import fis.base.ui._
import fis.cl.model._
import fis.fs.model._
import fis.fs.ui._
import fis.pm.model._
import net.liftweb.common._
import net.liftweb.http._
import net.liftweb.sitemap._
import net.liftweb.sitemap.Loc._
import net.liftweb.squerylrecord.RecordTypeMode._
import net.liftweb.util._
import net.liftweb.util.Helpers._
import net.tz.lift.model._
import net.tz.lift.snippet._
import scala.xml.{Elem, NodeSeq, Text}

object TaskSnippet extends TaskCrud with EntitySnippet[Task] {
  val prefix = "task"

  private val listM = Menu("task.list", l10n("Tasks")) / prefix >>
    Title(_ => i18n("Tasks")) >>
    locTpl("task/list") >> Snippet("list", list)

  private val viewPre = Menu.param[Task]("task.view", l10n("Task"), parse,
    encode) / prefix / * >> Title(t => i18n("Task %s", t.linkName)) >>
    locTpl("task/view") >> Snippet("panel", panel) >>
    Snippet("attachments", attachmentsTable) >>
    Snippet("comments", comments) >> Hidden

  private val editPre = Menu.param[Task]("task.edit", l10n("Edit"), parse,
    encode) / prefix / * / EDIT >>
    Title(t => i18n("Edit task %s", t.linkName)) >> IfLoggedIn.testVal >>
    locTpl("entity/form") >> Snippet("form", form) >> Hidden

  private val deletePre = Menu.param[Task]("task.delete", l10n("Delete"),
    parse, encode) / prefix / * / DELETE >>
    Title(t => i18n("Delete task %s", t.linkName)) >> IfLoggedIn.testVal >>
    locTpl("entity/delete") >> Snippet("form", deleteF) >> Hidden

  private val postCommentPre = Menu.param[Task]("task.post-comment",
    l10n("Post comment"), parse, encode) / prefix / * / "post-comment" >>
    Title(t => i18n("Post comment for task %s", t.linkName)) >>
    IfLoggedIn.testVal >>
    locTpl("task/view") >> Snippet("panel", postComment) >>
    Snippet("comments", comments) >> Hidden

  private val viewM = viewPre >> (SecNav(editPre) + deletePre +
    attachments.addPre + postCommentPre).build
  private val editM = editPre >> SecNav(viewPre).build
  private val deleteM = deletePre >> SecNav(viewPre).build
  private val postCommentM = postCommentPre >> SecNav(viewPre).build

  private lazy val viewLoc = viewM.toLoc
  private lazy val editLoc = editM.toLoc
  private lazy val deleteLoc = deleteM.toLoc
  private lazy val postCommentLoc = postCommentM.toLoc

  val menu = listM submenus(viewM, editM, deleteM, postCommentM,
    attachments.menu)

  private def cur = viewLoc.currentValue or editLoc.currentValue or
    deleteLoc.currentValue or postCommentLoc.currentValue

  private def allQp: Boolean = (S param "all") flatMap(asBoolean(_)) openOr false
  private def allQp(url: String, all: Boolean) =
    all.box(appendQueryParameters(url, List("all" -> "1"))) openOr url

  private def list: CssTr = {
    "li" #> (List(false, true) map { b =>
      val html = <li><a href={allQp(url.list, b)}>{
        l10n(if (b) "tasks.all" else "tasks.unfinished")}</a></li>
      (b == allQp).box(html % ("class" -> "active")) openOr html
    }) &
    ".content *" #> _TaskTable(allQp.box(PmSchema.tasks) openOr
      from(PmSchema.taskT, CodeListSchema.cli)((t, i) =>
      where(t.stateFld === i.id and TaskState.unfinishedClause(i))
      select(t) orderBy(t.deadline asc)))
  }

  private def panel: CssTr = "*" #> cur.map { t => ViewPanel(fields(t)) }

  private def comments: CssTr = "*" #> cur.map { t =>
    CommentTable(TaskComments(t)) }

  private def attachmentsTable: CssTr = "*" #> cur.map { t =>
    attachments.table(t)(TaskAttachments(t)) }

  object url {
    def view: Task => Box[String] = (viewLoc.calcHref _) andThen (Box !! _)
    def list: String = listM.loc.calcDefaultHref
  }

  private def fields(t: Task) = List(t.ident, t.name, t.project, t.taskType,
    t.stateFld, t.createdBy, t.createdAt, t.deadline, t.responsible, t.note)

  private case class TaskLink(t: Task) extends EntityLink[Task](t, url.view)

  EntityLink.register[Task](TaskLink(_))

  private object _TaskTable extends TaskTable {
    override def fields(t: Task) = (Seq(identField(t)) ++ EntityLink(t) ++
      Seq(t.project, t.stateFld, t.deadline, t.responsible, t.note)
      ) map Delayed(t)
    import net.datatables.DataTables._
    override protected def dataTableOpts =
      Options col(ColumnDef dtype(TYPE_EU_DATE) target(4))
  }

  private class TaskReport(f: () => Iterable[Task]) extends LocInfo[Iterable[Task]] {
    def apply() = Full(f)
    def tasks = f()
  }

  private object form extends TaskForm {

    override def localSetup() {
      cur.foreach(task(_))
    }

    protected def onSuccess(t: Task) {
      S.redirectTo(viewLoc.calcHref(t))
    }
  }

  private object deleteF extends HorizontalScreen with CancelButton with
    DeleteButton {

    val confirm = field(l10n("Really delete this task?"), false)

    def finish() {
      for {
        t <- deleteLoc.currentValue if confirm
        p <- t.project.vend
        u <- ProjectSnippet.url.view(p)
        r <- delete(t)
        n <- r.box(t.linkName)
      } {
        S notice l10n("Task %s deleted.", n)
        S redirectTo u
      }
    }
  }

  private object postComment extends HorizontalScreen with CancelButton with
    SaveButton {

    private object comment extends ScreenVar[Comment](Comment.createRecord)

    addFields(() => comment.note)

    override def localSetup() {
      postCommentM.toLoc.currentValue.foreach { t => comment.task(t.id) }
    }

    def finish() { for {
      c <- CommentCrud.save(comment)
      t <- postCommentM.toLoc.currentValue
    } {
      S notice l10n("Comment saved.")
      S redirectTo viewLoc.calcHref(t)
    }}
  }

  /** Task attachments. */
  private object attachments {

    val addPre = Menu.param[Task]("task.add-attachment",
      l10n("Add attachment"), parse, encode) / prefix / * / "attachment" / ADD >>
      Title(_ => i18n("Add attachment")) >> IfLoggedIn.testVal >>
      locTpl("entity/form") >> Snippet("form", attachmentF) >> Hidden

    type TA = (Task, Attachment)
    private object taMemo extends RequestMemoize[List[String], Box[TA]]()
    private def parseTA(ids: List[String]): Box[TA] = taMemo(ids, ids match {
      case AsLong(tid) :: AsLong(aid) :: Nil => for {
        t <- get(tid)
        a <- AttachmentCrud.get(aid)
      } yield (t, a)
      case _ => Empty
    })
    private def encodeTA(ta: TA) = List(ta._1, ta._2) map(_.id.toString)

    private val downloadPre = Menu.params[TA]("task.attachment",
      LinkText(ta => Text(ta._2.linkName)),
      parseTA, encodeTA) / prefix / * / "attachment" / * >>
      EarlyResponse(download _)

    private val deleteM = Menu.params[TA]("task.delete-attachment",
      l10n("Delete"), parseTA, encodeTA) / prefix / * / "attachment" / * / DELETE >>
      Title(ta => i18n("Delete attachment %s", ta._2.linkName)) >>
      IfLoggedIn.testVal >> locTpl("entity/form") >>
      Snippet("form", attachmentD) >> Hidden

    private val addM = addPre >> SecNav(viewPre).build

    val downloadM = downloadPre submenus(addM, deleteM)
    val menu = downloadM

    private lazy val downloadLoc = downloadM.toLoc

    private def download(): Box[LiftResponse] = for {
      (t, a) <- downloadLoc.currentValue
      is <- a.getContent
    } yield
      StreamingResponse(is, () => is.close, a.contentSize, a.headers, Nil, 200)

    private object attachmentF extends AttachmentForm {

      override protected def onSuccess(a: Attachment) =
        addM.toLoc.currentValue.foreach { t =>
          PmSchema.taskAttachment.left(t).associate(a)
          S notice l10n("Attachment %s saved.", a.name.get)
          S redirectTo viewLoc.calcHref(t)
        }
    }

    private object attachmentD extends DeleteAttachmentForm {
      protected def getAttachment = deleteM.toLoc.currentValue map(_._2)
      protected def onSuccess(d: Boolean, rm: Boolean) {
        S notice l10n("Attachment deleted.")
        deleteM.toLoc.currentValue map(_._1) foreach { t =>
          S redirectTo viewLoc.calcHref(t) }
      }
    }

    def table(t: Task) = AttachmentTable(
      { a => NavLink(downloadLoc)((t, a)) },
      { a => NavLink(deleteM.toLoc)((t, a)) }) _
  }

  /** Task notifications. */
  private object notifications extends TaskNotification {
    lazy val viewLoc = TaskSnippet.this.viewLoc
    def panel(t: Task) = ViewPanel(fields(t))
    def hook(orig: Box[Task], t: Box[Task]): Box[Task] = {
      apply(orig, t)
      t
    }
  }

  TaskCrud.afterSave = Full(notifications.hook _)
}

// vim: set ts=2 sw=2 et: