Skip to content

Commit

Permalink
Merge pull request #4054 from gemini-hlsw/sc-3199-display-cfp-deadline
Browse files Browse the repository at this point in the history
Show the deadline if a proposal can be retracted
  • Loading branch information
cquiroz authored Aug 12, 2024
2 parents 29a05d1 + f556da1 commit 2723a78
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 79 deletions.
12 changes: 12 additions & 0 deletions common/src/main/scala/explore/model/RootModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import lucuma.core.model.ServiceUser
import lucuma.core.model.StandardUser
import lucuma.core.model.Target
import lucuma.core.model.User
import lucuma.core.util.Timestamp
import lucuma.ui.sso.UserVault
import monocle.Focus
import monocle.Lens
Expand All @@ -35,6 +36,7 @@ case class RootModel(
programSummaries: Option[ProgramSummaries] = none,
userPreferences: Option[UserPreferences] = none,
spectroscopyModes: Option[SpectroscopyModesMatrix] = none,
cfps: Option[List[CallForProposal]] = none,
undoStacks: UndoStacks[IO, ProgramSummaries] = UndoStacks.empty[IO, ProgramSummaries],
otherUndoStacks: ModelUndoStacks[IO] = ModelUndoStacks[IO]()
) derives Eq {
Expand All @@ -43,6 +45,12 @@ case class RootModel(
.getOption(this)
.map(_.label)
.orElse(RootModel.proposalReference.getOption(this).map(_.label))

val deadline: Option[Timestamp] =
(RootModel.proposal.getOption(this).flatten, RootModel.cfps.get(this))
.mapN(_.deadline(_))
.flatten

}

object RootModel:
Expand All @@ -54,6 +62,7 @@ object RootModel:
val programSummaries = Focus[RootModel](_.programSummaries)
val userPreferences = Focus[RootModel](_.userPreferences)
val spectroscopyModes = Focus[RootModel](_.spectroscopyModes)
val cfps = Focus[RootModel](_.cfps)
val undoStacks = Focus[RootModel](_.undoStacks)
val otherUndoStacks = Focus[RootModel](_.otherUndoStacks)

Expand Down Expand Up @@ -83,3 +92,6 @@ object RootModel:
ProgramDetails.proposal.some.andThen(Proposal.reference.some)
)
)

val proposal: Optional[RootModel, Option[Proposal]] =
programSummaries.some.andThen(ProgramSummaries.proposal)
12 changes: 10 additions & 2 deletions explore/src/main/scala/explore/ExploreLayout.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import clue.data.syntax.*
import crystal.react.*
import crystal.react.hooks.*
import eu.timepit.refined.types.string.NonEmptyString
import explore.cache.CfpCache
import explore.cache.ModesCache
import explore.cache.PreferencesCache
import explore.cache.ProgramCache
Expand All @@ -29,6 +30,7 @@ import japgolly.scalajs.react.extra.router.SetRouteVia
import japgolly.scalajs.react.vdom.html_<^.*
import lucuma.core.enums.ProgramType
import lucuma.core.util.Display
import lucuma.core.util.Timestamp
import lucuma.react.common.*
import lucuma.react.hotkeys.*
import lucuma.react.hotkeys.hooks.*
Expand Down Expand Up @@ -198,6 +200,11 @@ object ExploreLayout:
}
}

val deadlineStr =
props.view.get.deadline
.map(Proposal.deadlineString)
.orEmpty

React.Fragment(
ConfirmDialog(),
Toast(Toast.Position.BottomRight, baseZIndex = 2000).withRef(toastRef.ref),
Expand All @@ -216,11 +223,12 @@ object ExploreLayout:
)
),
<.div(LayoutStyles.MainGrid)(
ModesCache(props.view.zoom(RootModel.spectroscopyModes).async.set),
CfpCache(props.view.zoom(RootModel.cfps).async.set),
// This might use the `RoutingInfo.dummyProgramId` if the URL had no
// no program id in it. But, that's OK, because the list of user
// programs will still load and they will be redirected to the program
// selection popup.
ModesCache(props.view.zoom(RootModel.spectroscopyModes).async.set),
ProgramCache(
routingInfo.programId,
props.view.zoom(RootModel.user).get.map(_.role.name),
Expand Down Expand Up @@ -275,7 +283,7 @@ object ExploreLayout:
props.resolution.renderP(props.view),
if (isSubmitted)
Message(text =
s"The proposal has been submitted as ${proposalReference.foldMap(_.label)} and may be retracted to allow modifications until the proposal deadline."
s"The proposal has been submitted as ${proposalReference.foldMap(_.label)} and may be retracted until the proposal deadline at ${deadlineStr}."
)
else EmptyVdom
)
Expand Down
1 change: 1 addition & 0 deletions explore/src/main/scala/explore/Routing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ object Routing:
routingInfo.programId,
model.zoom(RootModel.vault).get,
detailsView,
model.zoom(RootModel.cfps).get.orEmpty,
programSummaries.model.get.programTimesPot.map(_.timeEstimateRange),
programSummaries.model.zoom(ProgramSummaries.proposalAttachments),
model.zoom(RootModel.otherUndoStacks).zoom(ModelUndoStacks.forProposal),
Expand Down
42 changes: 42 additions & 0 deletions explore/src/main/scala/explore/cache/CfpCache.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause

package explore.cache

import cats.effect.IO
import cats.effect.kernel.Resource
import cats.syntax.all.*
import clue.StreamingClient
import explore.DefaultErrorPolicy
import explore.model.CallForProposal
import fs2.Stream
import japgolly.scalajs.react.*
import lucuma.react.common.ReactFnProps
import lucuma.schemas.ObservationDB
import queries.common.CallsQueriesGQL.ReadOpenCFPs

case class CfpCache(
setCalls: Option[List[CallForProposal]] => IO[Unit]
)(using client: StreamingClient[IO, ObservationDB])
extends ReactFnProps[CfpCache](CfpCache.component)
with CacheComponent.Props[List[CallForProposal]]:
val setState = setCalls
given StreamingClient[IO, ObservationDB] = client

object CfpCache extends CacheComponent[List[CallForProposal], CfpCache]:

override protected val updateStream
: CfpCache => Resource[IO, Stream[IO, List[CallForProposal] => List[CallForProposal]]] =
_ => Resource.pure(Stream.empty)

given Reusability[CfpCache] = Reusability.always

override protected val initial: CfpCache => IO[
(List[CallForProposal], fs2.Stream[IO, List[CallForProposal] => List[CallForProposal]])
] = props =>
import props.given

ReadOpenCFPs[IO]
.query()
.map(_.map(_.callsForProposals.matches))
.tupleRight(Stream.empty)
66 changes: 18 additions & 48 deletions explore/src/main/scala/explore/proposal/ProposalEditor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ import lucuma.core.model.ZeroTo100
import lucuma.core.syntax.all.*
import lucuma.core.util.Enumerated
import lucuma.core.util.TimeSpan
import lucuma.core.util.Timestamp
import lucuma.core.validation.*
import lucuma.react.common.Css
import lucuma.react.common.ReactFnProps
Expand All @@ -79,7 +78,6 @@ import lucuma.ui.syntax.all.given
import lucuma.ui.syntax.pot.*
import monocle.Iso
import org.typelevel.log4cats.Logger
import queries.common.CallsQueriesGQL.*
import queries.common.ProposalQueriesGQL
import spire.std.any.*

Expand All @@ -93,7 +91,7 @@ case class ProposalEditor(
invitations: View[List[CoIInvitation]],
attachments: View[List[ProposalAttachment]],
authToken: Option[NonEmptyString],
deadline: View[Option[Timestamp]],
cfps: List[CallForProposal],
layout: LayoutsMap,
readonly: Boolean
) extends ReactFnProps(ProposalEditor.component)
Expand Down Expand Up @@ -571,15 +569,9 @@ object ProposalEditor:
ScalaFnComponent
.withHooks[Props]
.useContext(AppContext.ctx)
// cfps
.useEffectResultOnMountBy: (_, ctx) =>
import ctx.given
ReadOpenCFPs[IO]
.query()
.map(_.map(_.callsForProposals.matches))
// total time - we need `Hours` for editing and also to preserve if
// the user switches between classes with and without total time.
.useStateViewBy: (props, _, _) =>
.useStateViewBy: (props, _) =>
props.proposal
.zoom(Proposal.proposalType.some.andThen(ProposalType.totalTime))
.get
Expand All @@ -595,44 +587,23 @@ object ProposalEditor:
.useStateView(Visible.Hidden) // show partner splits modal
.useStateView(List.empty[PartnerSplit]) // partner splits modal
// Update the partner splits when a new callId is set
.useEffectWithDepsBy((props, _, cfps, _, _, _) =>
(props.proposal.get.callId, cfps.toOption.orEmpty)
): (props, _, _, _, _, ps) =>
(callId, cfps) =>
callId.foldMap(cid =>
val currentSplits = Proposal.proposalType.some
.andThen(ProposalType.partnerSplits)
.getOption(props.proposal.get)
val cfpPartners = cfps
.find(_.id === cid)
.foldMap(_.partners.map(_.partner))
val proposalPartners = currentSplits.orEmpty.filter(_._2 > 0).map(_.partner)

if (proposalPartners.nonEmpty && proposalPartners.forall(cfpPartners.contains))
ps.set(currentSplits.orEmpty)
else
ps.set(cfpPartners.map(p => PartnerSplit(p, 0.refined)))
)
// Update the deadline when the call or partner splits change
.useEffectWithDepsBy((props, _, cfps, _, _, splits) =>
(props.proposal.get.callId, cfps.toOption.orEmpty, splits.get)
): (props, _, _, _, _, ps) =>
(callId, cfps, splits) =>
callId.fold(Callback.empty)(cid =>
val proposalPartners = splits.filter(_._2 > 0).map(_.partner)
props.deadline.set(
cfps
.useEffectWithDepsBy((props, _, _, _, _) => (props.proposal.get.callId, props.cfps)):
(props, _, _, _, ps) =>
(callId, cfps) =>
callId.foldMap(cid =>
val currentSplits = Proposal.proposalType.some
.andThen(ProposalType.partnerSplits)
.getOption(props.proposal.get)
val cfpPartners = cfps
.find(_.id === cid)
.flatMap { c =>
val callPartners = c.partners
proposalPartners
.map(p => callPartners.find(_.partner === p).flatMap(_.submissionDeadline))
.minimumOption
.flatten
.orElse(c.submissionDeadlineDefault)
}
.foldMap(_.partners.map(_.partner))
val proposalPartners = currentSplits.orEmpty.filter(_._2 > 0).map(_.partner)

if (proposalPartners.nonEmpty && proposalPartners.forall(cfpPartners.contains))
ps.set(currentSplits.orEmpty)
else
ps.set(cfpPartners.map(p => PartnerSplit(p, 0.refined)))
)
)
// .useEffectWithDepsBy((props, _, _, _, _, _, _, _, _) => props.proposal.get.proposalClass)(
// // Deal with changes to the ProposalClass.
// (props, _, _, totalHours, minPct2, classType, _, _, oldClass) =>
Expand All @@ -659,7 +630,6 @@ object ProposalEditor:
(
props,
ctx,
cfps,
totalHours,
// minPct2,
showDialog,
Expand All @@ -682,7 +652,7 @@ object ProposalEditor:
props.users,
props.invitations,
props.attachments,
cfps.toOption.orEmpty,
props.cfps,
props.authToken,
props.layout,
props.readonly,
Expand Down
Loading

0 comments on commit 2723a78

Please sign in to comment.