--- 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
-->