diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala index bb74946..42dbda9 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala @@ -8,17 +8,21 @@ import scala.scalajs.js // enum is not working with Waypoints' SplitRender collectStatic -sealed abstract class Page(val title: String) +sealed abstract class Page(val title: String, val parent: Option[Page]) object Page: - case object Directory extends Page("Directory") - case object Dashboard extends Page("Dashboard") - case class Detail(osobniCislo: String) extends Page("Detail") - case class NotFound(url: String) extends Page("404") + case object Directory extends Page("Directory", None) + case object Dashboard extends Page("Dashboard", Some(Directory)) + case class Detail(osobniCislo: String, name: Option[String] = None) + extends Page(name.getOrElse("Detail"), Some(Directory)) + object Detail { + def apply(o: Osoba): Detail = Detail(o.osobniCislo, Some(o.jmeno)) + } + case class NotFound(url: String) extends Page("404", Some(Directory)) case class UnhandledError( errorName: Option[String], errorMessage: Option[String] - ) extends Page("Unexpected error") + ) extends Page("Unexpected error", Some(Directory)) object Routes: given JsonEncoder[Page] = DeriveJsonEncoder.gen[Page] diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala index bb74946..42dbda9 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala @@ -8,17 +8,21 @@ import scala.scalajs.js // enum is not working with Waypoints' SplitRender collectStatic -sealed abstract class Page(val title: String) +sealed abstract class Page(val title: String, val parent: Option[Page]) object Page: - case object Directory extends Page("Directory") - case object Dashboard extends Page("Dashboard") - case class Detail(osobniCislo: String) extends Page("Detail") - case class NotFound(url: String) extends Page("404") + case object Directory extends Page("Directory", None) + case object Dashboard extends Page("Dashboard", Some(Directory)) + case class Detail(osobniCislo: String, name: Option[String] = None) + extends Page(name.getOrElse("Detail"), Some(Directory)) + object Detail { + def apply(o: Osoba): Detail = Detail(o.osobniCislo, Some(o.jmeno)) + } + case class NotFound(url: String) extends Page("404", Some(Directory)) case class UnhandledError( errorName: Option[String], errorMessage: Option[String] - ) extends Page("Unexpected error") + ) extends Page("Unexpected error", Some(Directory)) object Routes: given JsonEncoder[Page] = DeriveJsonEncoder.gen[Page] diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Breadcrumbs.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Breadcrumbs.scala new file mode 100644 index 0000000..08a9b3e --- /dev/null +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Breadcrumbs.scala @@ -0,0 +1,85 @@ +package cz.e_bs.cmi.mdr.pdb.app.components + +import com.raquo.laminar.api.L.{*, given} +import cz.e_bs.cmi.mdr.pdb.app.Page +import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec +import com.raquo.domtypes.generic.codecs.StringAsIsCodec +import com.raquo.waypoint.Router +import cz.e_bs.cmi.mdr.pdb.app.Routes + +def Breadcrumbs(using router: Router[Page]): HtmlElement = + + def renderFull(page: Page): HtmlElement = + div( + cls := "hidden sm:block", + ol( + role := "list", + cls := "flex items-center space-x-4", + renderItems(page) + ) + ) + + def renderShort(page: Page): HtmlElement = + div( + cls := "flex sm:hidden", + page.parent match { + case None => renderHome(page) + case Some(p) => + a( + href := router.absoluteUrlForPage(p), + Routes.navigateTo(p), + cls := "group inline-flex space-x-3 text-sm font-medium text-gray-500 hover:text-gray-700", + Icons.solid.`arrow-narrow-left`, + span(p.title) + ) + } + ) + + def renderItems(page: Page): Seq[HtmlElement] = + page.parent match { + case None => Seq(li(div(renderHome(page)))) + case Some(p) => + renderItems(p) :+ li( + div( + cls := "flex items-center", + slash, + a( + href := "#", + cls := "ml-4 text-sm font-medium text-gray-500 hover:text-gray-700", + page.title + ) + ) + ) + } + + def renderHome(page: Page) = + a( + href := router.absoluteUrlForPage(page), + Routes.navigateTo(page), + cls := "text-gray-400 hover:text-gray-500", + Icons.solid.home, + span(cls := "sr-only", "Home") + ) + + def slash = { + import svg.{*, given} + svg( + cls := "flex-shrink-0 h-5 w-5 text-gray-300", + xmlns := "http://www.w3.org/2000/svg", + fill := "currentColor", + viewBox := "0 0 20 20", + customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) := true, + path( + d := "M5.555 17.776l8-16 .894.448-8 16-.894-.448z" + ) + ) + } + + val $p = router.$currentPage + + nav( + cls := "flex", + aria.label := "Breadcrumb", + child <-- $p.map(renderShort), + child <-- $p.map(renderFull) + ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala index bb74946..42dbda9 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala @@ -8,17 +8,21 @@ import scala.scalajs.js // enum is not working with Waypoints' SplitRender collectStatic -sealed abstract class Page(val title: String) +sealed abstract class Page(val title: String, val parent: Option[Page]) object Page: - case object Directory extends Page("Directory") - case object Dashboard extends Page("Dashboard") - case class Detail(osobniCislo: String) extends Page("Detail") - case class NotFound(url: String) extends Page("404") + case object Directory extends Page("Directory", None) + case object Dashboard extends Page("Dashboard", Some(Directory)) + case class Detail(osobniCislo: String, name: Option[String] = None) + extends Page(name.getOrElse("Detail"), Some(Directory)) + object Detail { + def apply(o: Osoba): Detail = Detail(o.osobniCislo, Some(o.jmeno)) + } + case class NotFound(url: String) extends Page("404", Some(Directory)) case class UnhandledError( errorName: Option[String], errorMessage: Option[String] - ) extends Page("Unexpected error") + ) extends Page("Unexpected error", Some(Directory)) object Routes: given JsonEncoder[Page] = DeriveJsonEncoder.gen[Page] diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Breadcrumbs.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Breadcrumbs.scala new file mode 100644 index 0000000..08a9b3e --- /dev/null +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Breadcrumbs.scala @@ -0,0 +1,85 @@ +package cz.e_bs.cmi.mdr.pdb.app.components + +import com.raquo.laminar.api.L.{*, given} +import cz.e_bs.cmi.mdr.pdb.app.Page +import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec +import com.raquo.domtypes.generic.codecs.StringAsIsCodec +import com.raquo.waypoint.Router +import cz.e_bs.cmi.mdr.pdb.app.Routes + +def Breadcrumbs(using router: Router[Page]): HtmlElement = + + def renderFull(page: Page): HtmlElement = + div( + cls := "hidden sm:block", + ol( + role := "list", + cls := "flex items-center space-x-4", + renderItems(page) + ) + ) + + def renderShort(page: Page): HtmlElement = + div( + cls := "flex sm:hidden", + page.parent match { + case None => renderHome(page) + case Some(p) => + a( + href := router.absoluteUrlForPage(p), + Routes.navigateTo(p), + cls := "group inline-flex space-x-3 text-sm font-medium text-gray-500 hover:text-gray-700", + Icons.solid.`arrow-narrow-left`, + span(p.title) + ) + } + ) + + def renderItems(page: Page): Seq[HtmlElement] = + page.parent match { + case None => Seq(li(div(renderHome(page)))) + case Some(p) => + renderItems(p) :+ li( + div( + cls := "flex items-center", + slash, + a( + href := "#", + cls := "ml-4 text-sm font-medium text-gray-500 hover:text-gray-700", + page.title + ) + ) + ) + } + + def renderHome(page: Page) = + a( + href := router.absoluteUrlForPage(page), + Routes.navigateTo(page), + cls := "text-gray-400 hover:text-gray-500", + Icons.solid.home, + span(cls := "sr-only", "Home") + ) + + def slash = { + import svg.{*, given} + svg( + cls := "flex-shrink-0 h-5 w-5 text-gray-300", + xmlns := "http://www.w3.org/2000/svg", + fill := "currentColor", + viewBox := "0 0 20 20", + customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) := true, + path( + d := "M5.555 17.776l8-16 .894.448-8 16-.894-.448z" + ) + ) + } + + val $p = router.$currentPage + + nav( + cls := "flex", + aria.label := "Breadcrumb", + child <-- $p.map(renderShort), + child <-- $p.map(renderFull) + ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala index 34f3eab..cc6c192 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala @@ -6,10 +6,12 @@ import com.raquo.laminar.builders.SvgBuilders import com.raquo.laminar.keys.ReactiveSvgAttr +// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site object Icons: val defaultSize: Int = 6 - // TODO: remove aria-hidden from here, move to call sites, it has no reason to be here + // TODO: remove aria-hidden from here, move to call sites, it has no reason to be here. or does it? + // Who decides whether the icon should be hidden? Or should the icon be hidden always? object aria: val hidden = customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) @@ -161,5 +163,32 @@ clipRule := "evenodd" ) ) + + def `arrow-narrow-left` = + svg( + cls := "flex-shrink-0 h-5 w-5 text-gray-400 group-hover:text-gray-600", + xmlns := "http://www.w3.org/2000/svg", + viewBox := "0 0 20 20", + fill := "currentColor", + aria.hidden := true, + path( + fillRule := "evenodd", + d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", + clipRule := "evenodd" + ) + ) + + def home = + svg( + cls := "flex-shrink-0 h-5 w-5", + xmlns := "http://www.w3.org/2000/svg", + viewBox := "0 0 20 20", + fill := "currentColor", + aria.hidden := true, + path( + d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" + ) + ) + end solid end Icons diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala index bb74946..42dbda9 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala @@ -8,17 +8,21 @@ import scala.scalajs.js // enum is not working with Waypoints' SplitRender collectStatic -sealed abstract class Page(val title: String) +sealed abstract class Page(val title: String, val parent: Option[Page]) object Page: - case object Directory extends Page("Directory") - case object Dashboard extends Page("Dashboard") - case class Detail(osobniCislo: String) extends Page("Detail") - case class NotFound(url: String) extends Page("404") + case object Directory extends Page("Directory", None) + case object Dashboard extends Page("Dashboard", Some(Directory)) + case class Detail(osobniCislo: String, name: Option[String] = None) + extends Page(name.getOrElse("Detail"), Some(Directory)) + object Detail { + def apply(o: Osoba): Detail = Detail(o.osobniCislo, Some(o.jmeno)) + } + case class NotFound(url: String) extends Page("404", Some(Directory)) case class UnhandledError( errorName: Option[String], errorMessage: Option[String] - ) extends Page("Unexpected error") + ) extends Page("Unexpected error", Some(Directory)) object Routes: given JsonEncoder[Page] = DeriveJsonEncoder.gen[Page] diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Breadcrumbs.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Breadcrumbs.scala new file mode 100644 index 0000000..08a9b3e --- /dev/null +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Breadcrumbs.scala @@ -0,0 +1,85 @@ +package cz.e_bs.cmi.mdr.pdb.app.components + +import com.raquo.laminar.api.L.{*, given} +import cz.e_bs.cmi.mdr.pdb.app.Page +import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec +import com.raquo.domtypes.generic.codecs.StringAsIsCodec +import com.raquo.waypoint.Router +import cz.e_bs.cmi.mdr.pdb.app.Routes + +def Breadcrumbs(using router: Router[Page]): HtmlElement = + + def renderFull(page: Page): HtmlElement = + div( + cls := "hidden sm:block", + ol( + role := "list", + cls := "flex items-center space-x-4", + renderItems(page) + ) + ) + + def renderShort(page: Page): HtmlElement = + div( + cls := "flex sm:hidden", + page.parent match { + case None => renderHome(page) + case Some(p) => + a( + href := router.absoluteUrlForPage(p), + Routes.navigateTo(p), + cls := "group inline-flex space-x-3 text-sm font-medium text-gray-500 hover:text-gray-700", + Icons.solid.`arrow-narrow-left`, + span(p.title) + ) + } + ) + + def renderItems(page: Page): Seq[HtmlElement] = + page.parent match { + case None => Seq(li(div(renderHome(page)))) + case Some(p) => + renderItems(p) :+ li( + div( + cls := "flex items-center", + slash, + a( + href := "#", + cls := "ml-4 text-sm font-medium text-gray-500 hover:text-gray-700", + page.title + ) + ) + ) + } + + def renderHome(page: Page) = + a( + href := router.absoluteUrlForPage(page), + Routes.navigateTo(page), + cls := "text-gray-400 hover:text-gray-500", + Icons.solid.home, + span(cls := "sr-only", "Home") + ) + + def slash = { + import svg.{*, given} + svg( + cls := "flex-shrink-0 h-5 w-5 text-gray-300", + xmlns := "http://www.w3.org/2000/svg", + fill := "currentColor", + viewBox := "0 0 20 20", + customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) := true, + path( + d := "M5.555 17.776l8-16 .894.448-8 16-.894-.448z" + ) + ) + } + + val $p = router.$currentPage + + nav( + cls := "flex", + aria.label := "Breadcrumb", + child <-- $p.map(renderShort), + child <-- $p.map(renderFull) + ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala index 34f3eab..cc6c192 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala @@ -6,10 +6,12 @@ import com.raquo.laminar.builders.SvgBuilders import com.raquo.laminar.keys.ReactiveSvgAttr +// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site object Icons: val defaultSize: Int = 6 - // TODO: remove aria-hidden from here, move to call sites, it has no reason to be here + // TODO: remove aria-hidden from here, move to call sites, it has no reason to be here. or does it? + // Who decides whether the icon should be hidden? Or should the icon be hidden always? object aria: val hidden = customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) @@ -161,5 +163,32 @@ clipRule := "evenodd" ) ) + + def `arrow-narrow-left` = + svg( + cls := "flex-shrink-0 h-5 w-5 text-gray-400 group-hover:text-gray-600", + xmlns := "http://www.w3.org/2000/svg", + viewBox := "0 0 20 20", + fill := "currentColor", + aria.hidden := true, + path( + fillRule := "evenodd", + d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", + clipRule := "evenodd" + ) + ) + + def home = + svg( + cls := "flex-shrink-0 h-5 w-5", + xmlns := "http://www.w3.org/2000/svg", + viewBox := "0 0 20 20", + fill := "currentColor", + aria.hidden := true, + path( + d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" + ) + ) + end solid end Icons diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Layout.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Layout.scala index 172d5b5..7e8312e 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Layout.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Layout.scala @@ -5,14 +5,14 @@ import cz.e_bs.cmi.mdr.pdb.app.{Page, UserProfile} import com.raquo.waypoint.Router -def PageHeader(currentPage: Signal[Page]): HtmlElement = +def PageHeader(using router: Router[Page]): HtmlElement = header( cls := "bg-white shadow-sm", div( cls := "max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8", h1( cls := "text-lg leading-6 font-semibold text-gray-900", - child.text <-- currentPage.map(_.title) + Breadcrumbs ) ) ) @@ -35,6 +35,6 @@ pages, userMenu ), - PageHeader(router.$currentPage), + PageHeader, content ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala index bb74946..42dbda9 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/Routes.scala @@ -8,17 +8,21 @@ import scala.scalajs.js // enum is not working with Waypoints' SplitRender collectStatic -sealed abstract class Page(val title: String) +sealed abstract class Page(val title: String, val parent: Option[Page]) object Page: - case object Directory extends Page("Directory") - case object Dashboard extends Page("Dashboard") - case class Detail(osobniCislo: String) extends Page("Detail") - case class NotFound(url: String) extends Page("404") + case object Directory extends Page("Directory", None) + case object Dashboard extends Page("Dashboard", Some(Directory)) + case class Detail(osobniCislo: String, name: Option[String] = None) + extends Page(name.getOrElse("Detail"), Some(Directory)) + object Detail { + def apply(o: Osoba): Detail = Detail(o.osobniCislo, Some(o.jmeno)) + } + case class NotFound(url: String) extends Page("404", Some(Directory)) case class UnhandledError( errorName: Option[String], errorMessage: Option[String] - ) extends Page("Unexpected error") + ) extends Page("Unexpected error", Some(Directory)) object Routes: given JsonEncoder[Page] = DeriveJsonEncoder.gen[Page] diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Breadcrumbs.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Breadcrumbs.scala new file mode 100644 index 0000000..08a9b3e --- /dev/null +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Breadcrumbs.scala @@ -0,0 +1,85 @@ +package cz.e_bs.cmi.mdr.pdb.app.components + +import com.raquo.laminar.api.L.{*, given} +import cz.e_bs.cmi.mdr.pdb.app.Page +import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec +import com.raquo.domtypes.generic.codecs.StringAsIsCodec +import com.raquo.waypoint.Router +import cz.e_bs.cmi.mdr.pdb.app.Routes + +def Breadcrumbs(using router: Router[Page]): HtmlElement = + + def renderFull(page: Page): HtmlElement = + div( + cls := "hidden sm:block", + ol( + role := "list", + cls := "flex items-center space-x-4", + renderItems(page) + ) + ) + + def renderShort(page: Page): HtmlElement = + div( + cls := "flex sm:hidden", + page.parent match { + case None => renderHome(page) + case Some(p) => + a( + href := router.absoluteUrlForPage(p), + Routes.navigateTo(p), + cls := "group inline-flex space-x-3 text-sm font-medium text-gray-500 hover:text-gray-700", + Icons.solid.`arrow-narrow-left`, + span(p.title) + ) + } + ) + + def renderItems(page: Page): Seq[HtmlElement] = + page.parent match { + case None => Seq(li(div(renderHome(page)))) + case Some(p) => + renderItems(p) :+ li( + div( + cls := "flex items-center", + slash, + a( + href := "#", + cls := "ml-4 text-sm font-medium text-gray-500 hover:text-gray-700", + page.title + ) + ) + ) + } + + def renderHome(page: Page) = + a( + href := router.absoluteUrlForPage(page), + Routes.navigateTo(page), + cls := "text-gray-400 hover:text-gray-500", + Icons.solid.home, + span(cls := "sr-only", "Home") + ) + + def slash = { + import svg.{*, given} + svg( + cls := "flex-shrink-0 h-5 w-5 text-gray-300", + xmlns := "http://www.w3.org/2000/svg", + fill := "currentColor", + viewBox := "0 0 20 20", + customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) := true, + path( + d := "M5.555 17.776l8-16 .894.448-8 16-.894-.448z" + ) + ) + } + + val $p = router.$currentPage + + nav( + cls := "flex", + aria.label := "Breadcrumb", + child <-- $p.map(renderShort), + child <-- $p.map(renderFull) + ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala index 34f3eab..cc6c192 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Icons.scala @@ -6,10 +6,12 @@ import com.raquo.laminar.builders.SvgBuilders import com.raquo.laminar.keys.ReactiveSvgAttr +// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site object Icons: val defaultSize: Int = 6 - // TODO: remove aria-hidden from here, move to call sites, it has no reason to be here + // TODO: remove aria-hidden from here, move to call sites, it has no reason to be here. or does it? + // Who decides whether the icon should be hidden? Or should the icon be hidden always? object aria: val hidden = customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) @@ -161,5 +163,32 @@ clipRule := "evenodd" ) ) + + def `arrow-narrow-left` = + svg( + cls := "flex-shrink-0 h-5 w-5 text-gray-400 group-hover:text-gray-600", + xmlns := "http://www.w3.org/2000/svg", + viewBox := "0 0 20 20", + fill := "currentColor", + aria.hidden := true, + path( + fillRule := "evenodd", + d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", + clipRule := "evenodd" + ) + ) + + def home = + svg( + cls := "flex-shrink-0 h-5 w-5", + xmlns := "http://www.w3.org/2000/svg", + viewBox := "0 0 20 20", + fill := "currentColor", + aria.hidden := true, + path( + d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" + ) + ) + end solid end Icons diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Layout.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Layout.scala index 172d5b5..7e8312e 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Layout.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/components/Layout.scala @@ -5,14 +5,14 @@ import cz.e_bs.cmi.mdr.pdb.app.{Page, UserProfile} import com.raquo.waypoint.Router -def PageHeader(currentPage: Signal[Page]): HtmlElement = +def PageHeader(using router: Router[Page]): HtmlElement = header( cls := "bg-white shadow-sm", div( cls := "max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8", h1( cls := "text-lg leading-6 font-semibold text-gray-900", - child.text <-- currentPage.map(_.title) + Breadcrumbs ) ) ) @@ -35,6 +35,6 @@ pages, userMenu ), - PageHeader(router.$currentPage), + PageHeader, content ) diff --git a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/pages/DetailPage.scala b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/pages/DetailPage.scala index bc8f32b..d299408 100644 --- a/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/pages/DetailPage.scala +++ b/app/src/main/scala/cz/e_bs/cmi/mdr/pdb/app/pages/DetailPage.scala @@ -8,12 +8,13 @@ import cz.e_bs.cmi.mdr.pdb.app.Page import cz.e_bs.cmi.mdr.pdb.app.services.DataFetcher import com.raquo.airstream.core.EventStream +import com.raquo.waypoint.Router val datetime = customHtmlAttr("datetime", StringAsIsCodec) def DetailPage(fetch: String => EventStream[Osoba])( $page: Signal[Page.Detail] -): HtmlElement = +)(using router: Router[Page]): HtmlElement = // TODO: proper loader val loading = div( @@ -24,11 +25,15 @@ ) ) val data = Var[Option[Osoba]](None) - val maybeOsobaSignal = data.signal.split(_ => ())((_, _, s) => OsobaView(s)) + val $maybeOsoba = data.signal.split(_ => ())((_, _, s) => OsobaView(s)) + val $fetchedData = $page.splitOne(_.osobniCislo)((osc, _, _) => osc) + .flatMap(fetch) + .debugLog() div( cls := "max-w-7xl mx-auto px-4 py-6 sm:px-6 lg:px-8", - $page.flatMap(pd => fetch(pd.osobniCislo)) --> data.writer.contramapSome, - child <-- maybeOsobaSignal.map(_.getOrElse(loading)) + $fetchedData --> data.writer.contramapSome, + $fetchedData --> (o => router.replaceState(Page.Detail(o))), + child <-- $maybeOsoba.map(_.getOrElse(loading)) ) def OsobaView($osoba: Signal[Osoba]): HtmlElement = @@ -57,7 +62,7 @@ ) div( - cls := "flex flex-col gap-4", + cls := "flex flex-col space-y-4", div( cls := "md:flex md:items-center md:justify-between md:space-x-5", div( @@ -67,7 +72,6 @@ Avatar($osoba.map(_.img), 16) ), div( - cls := "pt-1.5", h1( cls := "text-2xl font-bold text-gray-900", child.text <-- $osoba.map(_.jmeno) @@ -87,38 +91,42 @@ href := "#", cls := "block hover:bg-gray-50", div( - cls := "px-4 py-4 sm:px-6", + cls := "px-4 py-4 sm:px-6 items-center flex", div( - cls := "flex items-center justify-between", - p( - cls := "text-sm font-medium text-indigo-600 truncate", - "Komise pro pověřování pracovníků" - ), + cls := "min-w-0 flex-1 pr-4", div( - cls := "ml-2 flex-shrink-0 flex", + cls := "flex items-center justify-between", p( - cls := "px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800", - """Splněno""" + cls := "text-sm font-medium text-indigo-600 truncate", + "Komise pro pověřování pracovníků" ), div( - cls := "ml-5 flex-shrink-0", - Icons.solid.`chevron-right` + cls := "ml-2 flex-shrink-0 flex", + p( + cls := "px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800", + """Splněno""" + ) + ) + ), + div( + cls := "mt-2 sm:flex sm:justify-between", + div(), + div( + cls := "mt-2 flex items-center text-sm text-gray-500 sm:mt-0", + Icons.solid.calendar, + p( + """do """, + time( + datetime := "2020-01-07", + "01.07.2020" + ) + ) ) ) ), div( - cls := "mt-2 sm:flex sm:justify-between", - div( - cls := "mt-2 flex items-center text-sm text-gray-500 sm:mt-0", - Icons.solid.calendar, - p( - """do """, - time( - datetime := "2020-01-07", - "01.07.2020" - ) - ) - ) + cls := "flex-shrink-0", + Icons.solid.`chevron-right` ) ) )