Comment
authorTomas Zeman <tzeman@volny.cz>
Fri, 27 Apr 2012 14:23:34 +0200
changeset 83 f7553032b302
parent 82 ad2fe2f9bac7
child 84 43674362ff5e
Comment
db/db-schema.sql
src/main/scala/fis/pm/model/Comment.scala
src/main/scala/fis/pm/model/CommentCrud.scala
src/main/scala/fis/pm/model/PmSchema.scala
src/main/scala/fis/pm/ui/CommentTable.scala
src/main/scala/fis/pm/ui/TaskSnippet.scala
src/main/webapp/css/comment.css
src/main/webapp/task/view.html
src/main/webapp/templates-hidden/_resources.html
src/main/webapp/templates-hidden/_resources_cs.html
--- a/db/db-schema.sql	Fri Apr 27 14:15:51 2012 +0200
+++ b/db/db-schema.sql	Fri Apr 27 14:23:34 2012 +0200
@@ -190,6 +190,17 @@
     "updated_by" bigint
   );
 create sequence "task_id_seq";
+create table "comment" (
+    "name" varchar(100) not null,
+    "updated_at" timestamp not null,
+    "task_id" bigint not null,
+    "id" bigint primary key not null,
+    "note" varchar(10240),
+    "created_at" timestamp not null,
+    "created_by" bigint,
+    "updated_by" bigint
+  );
+create sequence "comment_id_seq";
 -- foreign key constraints :
 alter table "address" add constraint "addressFK1" foreign key ("city_id") references "city"("id");
 alter table "city" add constraint "cityFK2" foreign key ("country_id") references "country"("id");
@@ -204,6 +215,7 @@
 alter table "task" add constraint "taskFK15" foreign key ("task_type") references "code_list_item"("id");
 alter table "task" add constraint "taskFK16" foreign key ("state") references "code_list_item"("id");
 alter table "task" add constraint "taskFK17" foreign key ("project_id") references "project"("id");
+alter table "comment" add constraint "commentFK18" foreign key ("task_id") references "task"("id") on delete cascade;
 alter table "company_contact" add constraint "company_contactFK7" foreign key ("entity") references "company"("id") on delete cascade;
 alter table "company_contact" add constraint "company_contactFK8" foreign key ("contact") references "contact"("id") on delete cascade;
 alter table "user_contact" add constraint "user_contactFK9" foreign key ("entity") references "user"("id") on delete cascade;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/pm/model/Comment.scala	Fri Apr 27 14:23:34 2012 +0200
@@ -0,0 +1,39 @@
+/*
+ * 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.model
+
+import fis.aaa.model._
+import fis.base.model.Entity
+import fis.cl.model._
+import net.liftweb.common._
+import net.liftweb.record.{MetaRecord, Record}
+import net.liftweb.record.field._
+import net.liftweb.util._
+import net.tz.lift.model._
+import net.tz.lift.model.{FieldLabel => FL}
+import org.squeryl.annotations.Column
+import scala.xml.Text
+
+class Comment private() extends Record[Comment] with Entity[Comment] {
+  def meta = Comment
+
+  @Column("task_id")
+  val task = new LongField(this)
+}
+
+object Comment extends Comment with MetaRecord[Comment]
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/pm/model/CommentCrud.scala	Fri Apr 27 14:23:34 2012 +0200
@@ -0,0 +1,26 @@
+/*
+ * 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.model
+
+import fis.base.model.RecordCrud
+
+trait CommentCrud extends RecordCrud[Comment] {
+  val table = PmSchema.commentT
+}
+
+object CommentCrud extends CommentCrud
+
+// vim: set ts=2 sw=2 et:
--- a/src/main/scala/fis/pm/model/PmSchema.scala	Fri Apr 27 14:15:51 2012 +0200
+++ b/src/main/scala/fis/pm/model/PmSchema.scala	Fri Apr 27 14:23:34 2012 +0200
@@ -57,6 +57,12 @@
 
   Task.users.default.set(activeUsersF)
 
+  /* comment */
+  val commentT = tableWithSeq[Comment]
+
+  val commentTask = oneToManyRelation(taskT, commentT).
+    via((t, c) => (t.id === c.task))
+  commentTask.foreignKeyDeclaration.constrainReference(onDelete cascade)
 }
 
 object PmSchema extends PmSchema
@@ -64,7 +70,11 @@
 object ProjectTasks {
   def apply(p: Project): Iterable[Task] =
     from(PmSchema.taskProject.left(p))(t => select(t) orderBy(t.deadline asc))
-    
+}
+
+object TaskComments {
+  def apply(t: Task): Iterable[Comment] =
+    from(PmSchema.commentTask.left(t))(c => select(c) orderBy(c.createdAt asc))
 }
 
 // vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/pm/ui/CommentTable.scala	Fri Apr 27 14:23:34 2012 +0200
@@ -0,0 +1,46 @@
+/*
+ * 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.pm.model.Comment
+import net.liftweb.http.Templates
+import net.liftweb.util.Helpers._ // CSS transforms
+import scala.xml.NodeSeq
+
+object CommentTable {
+
+  protected def load: NodeSeq =
+    Templates(List("templates-hidden", "comment")) openOr NodeSeq.Empty
+
+  protected def build(comments: Iterable[Comment]): NodeSeq =
+    (".comments" #> (comments map { renderComment _ }))(load)
+
+  protected def renderComment(c: Comment) =
+    ".comment-author *" #> c.createdBy.asHtml &
+    ".comment-body *" #> c.note.asHtml &
+    ".comment-footer *" #> c.createdAt.asHtml
+
+  def apply(comments: Iterable[Comment]): NodeSeq = {
+    val content = comments flatMap { c =>
+      <dt class="comment-author">{c.createdBy.asHtml}</dt>
+      <dd class="comment-body">{c.note.asHtml}</dd>
+      <dd class="comment-footer">{c.createdAt.asHtml}</dd>
+    }
+    <dl>{content}</dl>
+  }
+}
+
+// vim: set ts=2 sw=2 et:
--- a/src/main/scala/fis/pm/ui/TaskSnippet.scala	Fri Apr 27 14:15:51 2012 +0200
+++ b/src/main/scala/fis/pm/ui/TaskSnippet.scala	Fri Apr 27 14:23:34 2012 +0200
@@ -36,7 +36,8 @@
 
   private val viewPre = Menu.param[Task]("task.view", l10n("Task"), parse,
     encode) / prefix / * >> Title(t => i18n("Task %s", t.linkName)) >>
-    locTpl("entity/view") >> Snippet("panel", panel) >> Hidden
+    locTpl("task/view") >> Snippet("panel", panel) >>
+    Snippet("comments", comments) >> Hidden
 
   private val editPre = Menu.param[Task]("task.edit", l10n("Edit"), parse,
     encode) / prefix / * / EDIT >>
@@ -48,20 +49,32 @@
     Title(t => i18n("Delete task %s", t.linkName)) >>
     locTpl("entity/delete") >> Snippet("form", deleteF) >> Hidden
 
-  private val viewM = viewPre >> (SecNav(editPre) + deletePre).build
+  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)) >>
+    locTpl("task/view") >> Snippet("panel", postComment) >>
+    Snippet("comments", comments) >> Hidden
+
+  private val viewM = viewPre >> (SecNav(editPre) + deletePre +
+    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)
+  val menu = listM submenus(viewM, editM, deleteM, postCommentM)
 
   private def cur = viewLoc.currentValue or editLoc.currentValue or
-    deleteLoc.currentValue
+    deleteLoc.currentValue or postCommentLoc.currentValue
 
-  private def panel: CssTr = "*" #> cur.map { c => ViewPanel(fields(c)) }
+  private def panel: CssTr = "*" #> cur.map { t => ViewPanel(fields(t)) }
+
+  private def comments: CssTr = "*" #> cur.map { t =>
+    CommentTable(TaskComments(t)) }
 
   object url {
     def view: Task => Box[String] = (viewLoc.calcHref _) andThen (Box !! _)
@@ -104,6 +117,26 @@
     }
   }
 
+  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)
+    }}
+  }
+
 }
 
 // vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/css/comment.css	Fri Apr 27 14:23:34 2012 +0200
@@ -0,0 +1,10 @@
+.comment-author:first-child {
+  border-top: medium none;
+  padding-top: 0;
+}
+
+.comment-author {
+  background-position: 0 1.5em;
+  border-top: 1px solid #eeeeee;
+  padding-top: 1.5em;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/task/view.html	Fri Apr 27 14:23:34 2012 +0200
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+    <title>Entity View</title>
+  </head>
+  <body class="lift:content_id=main">
+    <div id="main" class="lift:surround?with=default;at=content">
+      <head_merge>
+        <link rel="stylesheet" href="/css/comment.css" type="text/css" media="screen, projection"/>
+      </head_merge>
+      <div class="row">
+        <div class="span12">
+          <span class="lift:panel"></span>
+        </div>
+      </div> <!-- /row -->
+      <div class="row section">
+        <div class="span12">
+          <h3><span class="lift:loc?locid=task.comments"></span></h3>
+          <div class="comments">
+            <span class="lift:comments"></span>
+          </div>
+        </div>
+      </div> <!-- /row -->
+    </div>
+  </body>
+</html>
+
+
--- a/src/main/webapp/templates-hidden/_resources.html	Fri Apr 27 14:15:51 2012 +0200
+++ b/src/main/webapp/templates-hidden/_resources.html	Fri Apr 27 14:23:34 2012 +0200
@@ -230,12 +230,21 @@
   <res name="task.deadline" lang="en" default="true">Deadline</res>
   <res name="task.responsible" lang="en" default="true">Responsible</res>
   <res name="task.stateFld" lang="en" default="true">State [%%]</res>
+  <res name="task.comments" lang="en" default="true">Comments</res>
   <!-- task types:
     Task type 1
     Task type 2
   -->
 
 
+  <!-- comment
+    Post comment
+    Post comment for task %s
+    Comment saved.
+  -->
+  <res name="comment.note" lang="en" default="true">Comment</res>
+
+
 <!--
   vim: et sw=2 ts=2
 -->
--- a/src/main/webapp/templates-hidden/_resources_cs.html	Fri Apr 27 14:15:51 2012 +0200
+++ b/src/main/webapp/templates-hidden/_resources_cs.html	Fri Apr 27 14:23:34 2012 +0200
@@ -218,11 +218,21 @@
   <res name="task.deadline" lang="cs">Termín</res>
   <res name="task.responsible" lang="cs">Odpovědný</res>
   <res name="task.stateFld" lang="cs">Stav [%%]</res>
+  <res name="task.comments" lang="cs">Komentáře</res>
   <!-- task types: -->
   <res name="Task type 1" lang="cs">Typ 1</res>
   <res name="Task type 2" lang="cs">Typ 2</res>
 
 
+  <!-- comment -->
+  <res name="Comment" lang="cs">Komentář</res>
+  <res name="Comments" lang="cs">Komentáře</res>
+  <res name="Post comment" lang="cs">Přidat komentář</res>
+  <res name="Post comment for task %s" lang="cs">Přidat komentář k úkolu %s</res>
+  <res name="Comment saved." lang="cs">Komentář uložen.</res>
+  <res name="comment.note" lang="cs">Komentář</res>
+
+
 <!--
   vim: et sw=2 ts=2
 -->