/*
* 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.model.ReadOnlyField
import fis.base.ui._
import fis.cl.model._
import fis.crm.model._
import fis.fs.model._
import fis.fs.ui._
import fis.geo.model._
import fis.pm.model._
import net.liftweb.common._
import net.liftweb.http._
import net.liftweb.record.field._
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 ProjectSnippet extends ProjectCrud with EntitySnippet[Project] {
val prefix = "project"
private val listPre = Menu("project.list", l10n("Projects")) / prefix >>
Title(_ => i18n("Projects")) >>
locTpl("project/list") >> Snippet("list", list)
private val createPre = Menu("project.create", l10n("Create")) / prefix / ADD >>
Title(_ => i18n("Create project")) >> IfLoggedIn.test >>
locTpl("project/form") >> Snippet("form", form) >> Hidden
private val viewPre = Menu.param[Project]("project.view", l10n("Project"), parse,
encode) / prefix / * >> Title(p => i18n("Project %s", p.linkName)) >>
locTpl("project/view") >> Snippet("panel", panel) >>
Snippet("tasks", tasks) >> Snippet("attachments", attachmentsTable) >>
Hidden
private val editPre = Menu.param[Project]("project.edit", l10n("Edit"), parse,
encode) / prefix / * / EDIT >>
Title(p => i18n("Edit project %s", p.linkName)) >> IfLoggedIn.testVal >>
locTpl("project/form") >> Snippet("form", form) >> Hidden
private val deletePre = Menu.param[Project]("project.delete", l10n("Delete"),
parse, encode) / prefix / * / DELETE >>
Title(p => i18n("Delete project %s", p.linkName)) >> IfLoggedIn.testVal >>
locTpl("entity/delete") >> Snippet("form", deleteF) >> Hidden
private val createTaskPre = Menu.param[Project]("project.create-task",
l10n("Create task"), parse, encode) / prefix / * / "create-task" >>
Title(p => i18n("Create task for project %s", p.linkName)) >>
IfLoggedIn.testVal >>
locTpl("entity/form") >> Snippet("form", taskF) >> Hidden
private val locationsPre = Menu.param[Project]("project.locations",
l10n("Edit locations"), parse, encode) / prefix / * / "locations" >>
Title(p => i18n("Edit locations of project %s", p.linkName)) >>
IfLoggedIn.testVal >>
locTpl("entity/form") >> Snippet("form", locationsF) >> Hidden
private val listM = listPre >> SecNav(createPre).build
private val createM = createPre >> SecNav(listPre).build
private val viewM = viewPre >> (SecNav(editPre) + deletePre +
locationsPre + createTaskPre + attachments.addPre).build
private val editM = editPre >> SecNav(viewPre).build
private val deleteM = deletePre >> SecNav(viewPre).build
private val createTaskM = createTaskPre >> SecNav(viewPre).build
private val locationsM = locationsPre >> SecNav(viewPre).build
private lazy val viewLoc = viewM.toLoc
private lazy val editLoc = editM.toLoc
private lazy val deleteLoc = deleteM.toLoc
val menu = listM submenus(viewM, editM, createM, deleteM, createTaskM,
locationsM, attachments.menu)
private def cur = viewLoc.currentValue or editLoc.currentValue or
deleteLoc.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) "projects.all" else "projects.unfinished")}</a></li>
(b == allQp).box(html % ("class" -> "active")) openOr html
}) &
".content *" #> ProjectTable(allQp.box(PmSchema.projects) openOr
from(PmSchema.projectT, CodeListSchema.cli)((p, i) =>
where(p.stateFld === i.id and ProjectState.unfinishedClause(i))
select(p) orderBy(p.deadline asc)))
}
private def panel: CssTr = "*" #> cur.map { p => ViewPanel(fields(p)) }
private def tasks: CssTr = "*" #> cur.map { p => TaskTable(ProjectTasks(p)) }
private def attachmentsTable: CssTr = "*" #> cur.map { p =>
attachments.table(p)(ProjectAttachments(p)) }
object url {
def view: Project => Box[String] = (viewLoc.calcHref _) andThen (Box !! _)
def list: String = listM.loc.calcDefaultHref
}
private def fields(p: Project) = List(p.name, p.identS, p.phaseFld,
p.stateFld, p.createdBy, p.createdAt, p.deadline, p.responsible,
p.productLine, ProjectCompanyField(p), ProjectLocationsField(p),
p.description, p.note)
private case class ProjectLink(c: Project) extends EntityLink[Project](c, url.view)
EntityLink.register[Project](ProjectLink(_))
private case class ProjectCompanyField(p: Project) extends OptionalLongField(p)
with CompanyField with FieldLabel {
override def name = "company"
override def defaultValueBox =
PmSchema.projectCompany.left(p).headOption.map(_.id)
}
private case class ProjectLocationsField(p: Project) extends ReadOnlyField(
"locations", l10n("project.locations"), NodeSeq.Empty, Empty) {
override def asHtml = <span>{
((List(p.locationA, p.locationB) filterNot(_.get.isEmpty) map(_.asHtml)) ++
(ProjectLocations(p) flatMap(EntityLink(_)) map(_.asHtml))) map(x =>
<div>{x}</div>)
}</span>
}
private object form extends HorizontalScreen with CancelButton with
SaveButton {
private object project extends ScreenVar[Project](Project.createRecord)
private object company extends ScreenVar[ProjectCompanyField](
ProjectCompanyField(project))
private object companyRec extends ScreenVar[Company](Company.createRecord)
private object address extends ScreenVar[Address](Address.createRecord)
private val createNewCompany = builder(l10n("Create new company?"), false,
FormFieldId("create_new_company")).
help(i18n("Create new company instead of choosing an existing one.")).make
private def formFields(p: Project) = {
val pFlds = List[FieldContainer](p.name, p.identS, p.phaseFld, p.stateFld,
p.deadline, p.responsible, p.productLine, p.locationA, p.locationB,
p.description, p.note)
val companySelect = field(company.get, FormFieldId("company_select"))
p.id == p.idField.defaultValue match {
case false => pFlds :+ companySelect
case true =>
val c = companyRec.get
val cFlds = field(c.name, FormFieldId("comp_name")) ::
field(c.ico, FormFieldId("comp_ico")) ::
field(c.dic, FormFieldId("comp_dic")) ::
field(ReadOnlyField.labelOnly(c.address), FormFieldId("comp_addr")) ::
(address.formFields map { f => field(f,
FormFieldId(f.uniqueFieldId.map("comp_" + _) openOr nextFuncName)) })
(pFlds :+ createNewCompany :+ companySelect) ::: cFlds
}
}
override def screenFields = formFields(project) flatMap(_.allFields)
protected override def decorateLine(f: ScreenFieldInfo): CssTr =
(f.field.uniqueFieldId.filter { _.startsWith("comp_") } map { _ =>
".control-group [class+]" #> "company-field" }) or
(f.field.uniqueFieldId.filter { _.startsWith("company_select") } map { _ =>
".control-group [class+]" #> "company-select" }) openOr PassThru
override def localSetup() {
cur.foreach { p => project(p); company(ProjectCompanyField(p)) }
}
def finish() {
save(project) foreach { p =>
val fk = PmSchema.projectCompany.left(p)
createNewCompany.get match {
case false => company.vend match {
case Full(c) if fk.exists(_.id == c.id) => // empty, no update
case Full(c) =>
fk.dissociateAll
fk.associate(c)
case _ => fk.dissociateAll
}
case true => AddressCrud.save(address).foreach { a =>
CompanyCrud.save(companyRec.address(a.id)).foreach { c =>
S notice l10n("Company %s saved.", c.linkName)
fk.dissociateAll
fk.associate(c)
}
}
}
S notice l10n("Project %s saved.", p.linkName)
S.redirectTo(viewLoc.calcHref(p))
}
}
}
private object deleteF extends HorizontalScreen with CancelButton with
DeleteButton {
val confirm = field(l10n("Really delete this project?"), false)
def finish() {
for {
c <- deleteLoc.currentValue if confirm
r <- delete(c)
n <- r.box(c.linkName)
} {
S notice l10n("Project %s deleted.", n)
S redirectTo listM.loc.calcDefaultHref
}
}
}
private object taskF extends TaskForm {
override def localSetup() {
createTaskM.currentValue.foreach { p => task.project(p.id) }
}
protected def onSuccess(t: Task) {
TaskSnippet.url.view(t).foreach { u => S redirectTo u }
}
}
private object locationsF extends HorizontalScreen with CancelButton with
SaveButton {
private class V(iv: Boolean, val l: Location) { var v: Boolean = iv }
private object locs extends ScreenVar[Iterable[V]](Nil)
def v2r(v: V): Box[List[NodeSeq]] = for {
ll <- EntityLink(v.l)
a <- v.l.address.vend
c <- a.city.vend
cl <- EntityLink(c)
} yield {
List(SHtml.checkbox(v.v, v.v = _), ll.asHtml, v.l.address.asHtml,
cl.asHtml)
}
def locsTbl =
(new DataTable(List(l10n("Check"), l10n("Location"),
Location.address.displayName, l10n("City")),
locs flatMap { v2r _ } toList))(NodeSeq.Empty)
addFields(() => new ReadOnlyField("locations", l10n("project.locations"),
NodeSeq.Empty, Full(locsTbl)))
override def localSetup() {
locationsM.toLoc.currentValue.foreach { p =>
val ids = ProjectLocations(p) map(_.id) toSet
val ls = Location.locations() map { l => new V(ids contains l.id, l) }
locs(ls)
}
}
def finish() { locationsM.toLoc.currentValue.foreach { p =>
val fk = PmSchema.projectLocation.left(p)
val curIds = fk map(_.id) toSet
val newLocs = locs filter(_.v) map(_.l)
val newIds = newLocs map(_.id) toSet
fk filterNot(newIds contains _.id) foreach { l => fk.dissociate(l) }
newLocs filterNot(curIds contains _.id) foreach { l => fk.associate(l) }
S notice l10n("Locations saved.")
S redirectTo viewLoc.calcHref(p)
}}
}
/** Project attachments. */
private object attachments {
val addPre = Menu.param[Project]("project.add-attachment",
l10n("Add attachment"), parse, encode) / prefix / * / "attachment" / ADD >>
Title(_ => i18n("Add attachment")) >> IfLoggedIn.testVal >>
locTpl("entity/form") >> Snippet("form", attachmentF) >> Hidden
type PA = (Project, Attachment)
private object paMemo extends RequestMemoize[List[String], Box[PA]]()
private def parsePA(ids: List[String]): Box[PA] = paMemo(ids, ids match {
case AsLong(pid) :: AsLong(aid) :: Nil => for {
p <- get(pid)
a <- AttachmentCrud.get(aid)
} yield (p, a)
case _ => Empty
})
private def encodePA(pa: PA) = List(pa._1, pa._2) map(_.id.toString)
private val downloadPre = Menu.params[PA]("project.attachment",
LinkText(pa => Text(pa._2.linkName)),
parsePA, encodePA) / prefix / * / "attachment" / * >>
EarlyResponse(download _)
private val deleteM = Menu.params[PA]("project.delete-attachment",
l10n("Delete"), parsePA, encodePA) / prefix / * / "attachment" / * / DELETE >>
Title(pa => i18n("Delete attachment %s", pa._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 {
(p, 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 { p =>
PmSchema.projectAttachment.left(p).associate(a)
S notice l10n("Attachment %s saved.", a.name.get)
S redirectTo viewLoc.calcHref(p)
}
}
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 { p =>
S redirectTo viewLoc.calcHref(p) }
}
}
def table(p: Project) = AttachmentTable(
{ a => NavLink(downloadLoc)((p, a)) },
{ a => NavLink(deleteM.toLoc)((p, a)) }) _
}
/** Project notifications. */
private object notifications extends ProjectNotification {
lazy val viewLoc = ProjectSnippet.this.viewLoc
def panel(p: Project) = ViewPanel(fields(p))
}
override protected def afterSave(orig: Box[Project], proj: Box[Project]) =
proj $ { _ => notifications(orig, proj) }
}
// vim: set ts=2 sw=2 et: