Task
authorTomas Zeman <tzeman@volny.cz>
Fri, 27 Apr 2012 12:04:35 +0200
changeset 81 c7d21399c726
parent 80 196896cc3f58
child 82 ad2fe2f9bac7
Task
db/db-data.sql
db/db-schema.sql
src/main/scala/bootstrap/liftweb/Boot.scala
src/main/scala/fis/pm/model/PmSchema.scala
src/main/scala/fis/pm/model/ProjectField.scala
src/main/scala/fis/pm/model/Task.scala
src/main/scala/fis/pm/model/TaskCrud.scala
src/main/scala/fis/pm/ui/ProjectSnippet.scala
src/main/scala/fis/pm/ui/TaskForm.scala
src/main/scala/fis/pm/ui/TaskSnippet.scala
src/main/scala/fis/pm/ui/TaskTable.scala
src/main/webapp/project/view.html
src/main/webapp/templates-hidden/_resources.html
src/main/webapp/templates-hidden/_resources_cs.html
--- a/db/db-data.sql	Tue Apr 24 20:36:06 2012 +0200
+++ b/db/db-data.sql	Fri Apr 27 12:04:35 2012 +0200
@@ -35,3 +35,85 @@
 VALUES
 ('project_state', nextval('code_list_item_id_seq'), true, 'project.state.realized', 40, false, 1,
 0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+
+-- Task states
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('task_state', nextval('code_list_item_id_seq'), false, '', 0, false, 0,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('task_state', nextval('code_list_item_id_seq'), false, '', 10, false, 10,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('task_state', nextval('code_list_item_id_seq'), false, '', 20, false, 20,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('task_state', nextval('code_list_item_id_seq'), false, '', 30, false, 30,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('task_state', nextval('code_list_item_id_seq'), false, '', 40, false, 40,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('task_state', nextval('code_list_item_id_seq'), false, '', 50, false, 50,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('task_state', nextval('code_list_item_id_seq'), false, '', 60, false, 60,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('task_state', nextval('code_list_item_id_seq'), false, '', 70, false, 70,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('task_state', nextval('code_list_item_id_seq'), false, '', 80, false, 80,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('task_state', nextval('code_list_item_id_seq'), false, '', 90, false, 90,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('task_state', nextval('code_list_item_id_seq'), false, '', 100, false, 100,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+
+-- Task types
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('task_type', nextval('code_list_item_id_seq'), true, 'Task type 1', 1, false, 0,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
+INSERT INTO code_list_item
+(code_list, id, i18n, name, rank, dflt, i1,
+i2, i3, l1, l2, l3, s1, s2, s3, created_at, updated_at, deleted)
+VALUES
+('task_type', nextval('code_list_item_id_seq'), true, 'Task type 2', 2, false, 0,
+0, 0, 0, 0, 0, '', '', '', current_timestamp, current_timestamp, false);
--- a/db/db-schema.sql	Tue Apr 24 20:36:06 2012 +0200
+++ b/db/db-schema.sql	Fri Apr 27 12:04:35 2012 +0200
@@ -175,6 +175,21 @@
     "updated_by" bigint
   );
 create sequence "project_id_seq";
+create table "task" (
+    "name" varchar(100) not null,
+    "updated_at" timestamp not null,
+    "id" bigint primary key not null,
+    "project_id" bigint not null,
+    "responsible" bigint not null,
+    "deadline" timestamp not null,
+    "state" bigint not null,
+    "note" varchar(10240),
+    "created_at" timestamp not null,
+    "created_by" bigint,
+    "task_type" bigint,
+    "updated_by" bigint
+  );
+create sequence "task_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");
@@ -185,6 +200,10 @@
 alter table "project" add constraint "projectFK11" foreign key ("responsible") references "user"("id");
 alter table "project" add constraint "projectFK12" foreign key ("product_line") references "code_list_item"("id") on delete set null;
 alter table "project" add constraint "projectFK13" foreign key ("state") references "code_list_item"("id");
+alter table "task" add constraint "taskFK14" foreign key ("responsible") references "user"("id");
+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 "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;
--- a/src/main/scala/bootstrap/liftweb/Boot.scala	Tue Apr 24 20:36:06 2012 +0200
+++ b/src/main/scala/bootstrap/liftweb/Boot.scala	Fri Apr 27 12:04:35 2012 +0200
@@ -60,7 +60,8 @@
       CountrySnippet.menu,
       LocationSnippet.menu,
       CitySnippet.menu,
-      ProjectSnippet.menu
+      ProjectSnippet.menu,
+      TaskSnippet.menu
     )
 
     LiftRules.setSiteMap(SiteMap(menus:_*))
--- a/src/main/scala/fis/pm/model/PmSchema.scala	Tue Apr 24 20:36:06 2012 +0200
+++ b/src/main/scala/fis/pm/model/PmSchema.scala	Fri Apr 27 12:04:35 2012 +0200
@@ -22,6 +22,7 @@
 
 trait PmSchema extends BaseSchema with AaaSchema with CodeListSchema {
 
+  /* project */
   val projectT = tableWithSeq[Project]
 
   val projectResponsible = oneToManyRelation(userT, projectT).
@@ -38,8 +39,32 @@
 
   def projects: Iterable[Project] = from(projectT)(p =>
     select(p) orderBy(p.name asc))
+
+  /* task */
+  val taskT = tableWithSeq[Task]
+
+  val taskResponsible = oneToManyRelation(userT, taskT).
+    via((u, t) => (u.id === t.responsible))
+
+  val taskType = oneToManyRelation(cli, taskT).
+    via((i, t) => (i.id === t.taskType))
+
+  val taskState = oneToManyRelation(cli, taskT).
+    via((i, t) => (i.id === t.stateFld))
+
+  val taskProject = oneToManyRelation(projectT, taskT).
+    via((p, t) => (p.id === t.project))
+
+  Task.users.default.set(activeUsersF)
+
 }
 
 object PmSchema extends PmSchema
 
+object ProjectTasks {
+  def apply(p: Project): Iterable[Task] =
+    from(PmSchema.taskProject.left(p))(t => select(t) orderBy(t.deadline asc))
+    
+}
+
 // vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/pm/model/ProjectField.scala	Fri Apr 27 12:04:35 2012 +0200
@@ -0,0 +1,36 @@
+/*
+ * 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.ui.EntityLink
+import net.liftweb.common._
+import net.liftweb.record._
+import net.liftweb.util._
+
+trait ProjectField extends TypedField[Long] with Vendor[Box[Project]] {
+
+  override def asHtml = (for {
+    p <- vend
+    l <- EntityLink(p)
+  } yield l.asHtml) openOr super.asHtml
+
+  override def toForm = Full(<span class="uneditable-input">{asHtml}</span>)
+
+  def vend = valueBox flatMap(ProjectCrud.get _)
+  def make = Full(vend)
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/pm/model/Task.scala	Fri Apr 27 12:04:35 2012 +0200
@@ -0,0 +1,69 @@
+/*
+ * 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 Task private() extends Record[Task] with Entity[Task] {
+  def meta = Task
+
+  @Column("project_id")
+  val project = new LongField(this) with ProjectField with FL
+  val deadline = new JodaDateMidnightField(this) with FL
+  val taskType = new OptionalCodeListItemField(this, 'task_type) with FL
+  val responsible = new UserField(this, meta.users, getUserVendor) with
+    DefaultCurUser with FL
+
+  @Column("state")
+  protected[pm] val stateFld = new CodeListItemField(this,
+    CodeList('task_state), _.i1.toString, { v => Text(v.i1.toString)}) with FL
+
+  def state: TaskState =
+    stateFld.item.map { TaskState(_) } openOr EmptyTaskState
+}
+
+object Task extends Task with MetaRecord[Task] with SimpleInjector {
+  object users extends Inject[Iterable[User]](() => Nil)
+}
+
+abstract sealed class TaskState {
+  def id: Long
+  def finishedPercent: Int
+  def complete: Boolean
+}
+case class TaskStateItem(id: Long, finishedPercent: Int, complete: Boolean)
+  extends TaskState
+case object EmptyTaskState extends TaskState {
+  def id = 0
+  def finishedPercent = 0
+  def complete = false
+}
+object TaskState {
+  def apply(i: CodeListItem): TaskState =
+    TaskStateItem(i.id, i.i1.get, i.i1.get == 100)
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/pm/model/TaskCrud.scala	Fri Apr 27 12:04:35 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 TaskCrud extends RecordCrud[Task] {
+  val table = PmSchema.taskT
+}
+
+object TaskCrud extends TaskCrud
+
+// vim: set ts=2 sw=2 et:
--- a/src/main/scala/fis/pm/ui/ProjectSnippet.scala	Tue Apr 24 20:36:06 2012 +0200
+++ b/src/main/scala/fis/pm/ui/ProjectSnippet.scala	Fri Apr 27 12:04:35 2012 +0200
@@ -40,7 +40,8 @@
 
   private val viewPre = Menu.param[Project]("project.view", l10n("Project"), parse,
     encode) / prefix / * >> Title(p => i18n("Project %s", p.linkName)) >>
-    locTpl("entity/view") >> Snippet("panel", panel) >> Hidden
+    locTpl("project/view") >> Snippet("panel", panel) >>
+    Snippet("tasks", tasks) >> Hidden
 
   private val editPre = Menu.param[Project]("project.edit", l10n("Edit"), parse,
     encode) / prefix / * / EDIT >>
@@ -52,24 +53,33 @@
     Title(p => i18n("Delete project %s", p.linkName)) >>
     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)) >>
+    locTpl("entity/form") >> Snippet("form", taskF) >> Hidden
+
   private val listM = listPre >> SecNav(createPre).build
   private val createM = createPre >> SecNav(listPre).build
-  private val viewM = viewPre >> (SecNav(editPre) + deletePre).build
+  private val viewM = viewPre >> (SecNav(editPre) + deletePre +
+    createTaskPre).build
   private val editM = editPre >> SecNav(viewPre).build
   private val deleteM = deletePre >> SecNav(viewPre).build
+  private val createTaskM = createTaskPre >> 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)
+  val menu = listM submenus(viewM, editM, createM, deleteM, createTaskM)
 
   private def cur = viewLoc.currentValue or editLoc.currentValue or
     deleteLoc.currentValue
 
   private def list: CssTr = { _ => ProjectTable(PmSchema.projects) }
 
-  private def panel: CssTr = "*" #> cur.map { c => ViewPanel(fields(c)) }
+  private def panel: CssTr = "*" #> cur.map { p => ViewPanel(fields(p)) }
+
+  private def tasks: CssTr = "*" #> cur.map { p => TaskTable(ProjectTasks(p)) }
 
   object url {
     def view: Project => Box[String] = (viewLoc.calcHref _) andThen (Box !! _)
@@ -124,6 +134,17 @@
     }
   }
 
+  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 }
+    }
+  }
+
 }
 
 // vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/pm/ui/TaskForm.scala	Fri Apr 27 12:04:35 2012 +0200
@@ -0,0 +1,47 @@
+/*
+ * 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.base.ui._
+import fis.pm.model._
+import net.liftweb.http._
+import net.liftweb.util._
+import net.tz.lift.model._
+
+abstract class TaskForm extends HorizontalScreen with CancelButton with
+  SaveButton {
+
+  protected object task extends ScreenVar[Task](Task.createRecord)
+
+  protected def fields(t: Task): List[BaseField] = List(t.name, t.project,
+    t.taskType, t.stateFld, t.deadline, t.responsible, t.note)
+
+  override def screenFields = fields(task)
+
+  protected def onSuccess(t: Task): Unit
+
+  def finish() {
+    for {
+      t <- TaskCrud.save(task)
+    } {
+      S notice l10n("Task %s saved.", t.linkName)
+      onSuccess(t)
+    }
+  }
+
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/pm/ui/TaskSnippet.scala	Fri Apr 27 12:04:35 2012 +0200
@@ -0,0 +1,109 @@
+/*
+ * 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.base.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.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("entity/list") >> Snippet("list", ClearNodes) >> Hidden
+
+  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
+
+  private val editPre = Menu.param[Task]("task.edit", l10n("Edit"), parse,
+    encode) / prefix / * / EDIT >>
+    Title(t => i18n("Edit task %s", t.linkName)) >>
+    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)) >>
+    locTpl("entity/delete") >> Snippet("form", deleteF) >> Hidden
+
+  private val viewM = viewPre >> (SecNav(editPre) + deletePre).build
+  private val editM = editPre >> SecNav(viewPre).build
+  private val deleteM = deletePre >> 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, deleteM)
+
+  private def cur = viewLoc.currentValue or editLoc.currentValue or
+    deleteLoc.currentValue
+
+  private def panel: CssTr = "*" #> cur.map { c => ViewPanel(fields(c)) }
+
+  object url {
+    def view: Task => Box[String] = (viewLoc.calcHref _) andThen (Box !! _)
+  }
+
+  private def fields(t: Task) = List(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 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
+      }
+    }
+  }
+
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/fis/pm/ui/TaskTable.scala	Fri Apr 27 12:04:35 2012 +0200
@@ -0,0 +1,27 @@
+/*
+ * 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._
+import fis.base.ui._
+
+object TaskTable extends FieldTable[Task] {
+  def fields(t: Task) = EntityLink(t) ++ Seq(t.deadline, t.responsible,
+    t.note)
+  def apply(l: Iterable[Task]) = build(Task, l)
+}
+
+// vim: set ts=2 sw=2 et:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/webapp/project/view.html	Fri Apr 27 12:04:35 2012 +0200
@@ -0,0 +1,24 @@
+<!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">
+      <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=project.tasks"></span></h3>
+          <span class="lift:tasks"></span>
+        </div>
+      </div> <!-- /row -->
+    </div>
+  </body>
+</html>
+
+
--- a/src/main/webapp/templates-hidden/_resources.html	Tue Apr 24 20:36:06 2012 +0200
+++ b/src/main/webapp/templates-hidden/_resources.html	Fri Apr 27 12:04:35 2012 +0200
@@ -199,6 +199,7 @@
   <res name="project.productLine" lang="en" default="true">Product line</res>
   <res name="project.responsible" lang="en" default="true">Responsible</res>
   <res name="project.stateFld" lang="en" default="true">State</res>
+  <res name="project.tasks" lang="en" default="true">Tasks</res>
   <!-- project states -->
   <res name="project.state.assigned" lang="en" default="true">Assigned</res>
   <res name="project.state.paused" lang="en" default="true">Paused</res>
@@ -208,6 +209,33 @@
   <res name="project.state.realized" lang="en" default="true">Realized</res>
 
 
+  <!-- task
+    default strings:
+      Task
+      Task %s
+      Tasks
+      Create task
+      Create task for project %s
+      Edit task %s
+      Delete task %s
+      Really delete this task?
+      Task %s deleted.
+      Task %s saved.
+  -->
+  <!-- task fields -->
+  <res name="task.name" lang="en" default="true">Name</res>
+  <res name="task.note" lang="en" default="true">Description</res>
+  <res name="task.project" lang="en" default="true">Project</res>
+  <res name="task.taskType" lang="en" default="true">Type</res>
+  <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>
+  <!-- task types:
+    Task type 1
+    Task type 2
+  -->
+
+
 <!--
   vim: et sw=2 ts=2
 -->
--- a/src/main/webapp/templates-hidden/_resources_cs.html	Tue Apr 24 20:36:06 2012 +0200
+++ b/src/main/webapp/templates-hidden/_resources_cs.html	Fri Apr 27 12:04:35 2012 +0200
@@ -189,6 +189,7 @@
   <res name="project.productLine" lang="cs">Produktová řada</res>
   <res name="project.responsible" lang="cs">Odpovědný</res>
   <res name="project.stateFld" lang="cs">Stav</res>
+  <res name="project.tasks" lang="cs">Úkoly</res>
   <!-- project states -->
   <res name="project.state.assigned" lang="cs">Přidělen</res>
   <res name="project.state.paused" lang="cs">Pozastaven</res>
@@ -198,6 +199,30 @@
   <res name="project.state.realized" lang="cs">Realizován</res>
 
 
+  <!-- task -->
+  <res name="Task" lang="cs">Úkol</res>
+  <res name="Task %s" lang="cs">Úkol %s</res>
+  <res name="Tasks" lang="cs">Úkoly</res>
+  <res name="Create task" lang="cs">Vytvořit úkol</res>
+  <res name="Create task for project %s" lang="cs">Vytvořit úkol pro projekt %s</res>
+  <res name="Edit task %s" lang="cs">Upravit úkol %s</res>
+  <res name="Delete task %s" lang="cs">Smazat úkol %s</res>
+  <res name="Really delete this task?" lang="cs">Skutečně smazat úkol?</res>
+  <res name="Task %s deleted." lang="cs">Úkol %s smazán.</res>
+  <res name="Task %s saved." lang="cs">Úkol %s uložen.</res>
+  <!-- task fields -->
+  <res name="task.name" lang="cs">Název</res>
+  <res name="task.note" lang="cs">Popis</res>
+  <res name="task.project" lang="cs">Projekt</res>
+  <res name="task.taskType" lang="cs">Typ úkolu</res>
+  <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>
+  <!-- task types: -->
+  <res name="Task type 1" lang="cs">Typ 1</res>
+  <res name="Task type 2" lang="cs">Typ 2</res>
+
+
 <!--
   vim: et sw=2 ts=2
 -->