Version 3.1 #71

Merged
danieljsummers merged 9 commits from v3.1 into main 2022-07-30 21:02:58 +00:00
6 changed files with 281 additions and 258 deletions
Showing only changes of commit 9b85ac2412 - Show all commits

View File

@ -1,42 +1,11 @@
open MyPrayerJournal.Domain
open NodaTime
/// Request is the identifying record for a prayer request
[<CLIMutable; NoComparison; NoEquality>]
type OldRequest =
{ /// The ID of the request
id : RequestId
/// The time this request was initially entered
enteredOn : Instant
/// The ID of the user to whom this request belongs ("sub" from the JWT)
userId : UserId
/// The time at which this request should reappear in the user's journal by manual user choice
snoozedUntil : Instant
/// The time at which this request should reappear in the user's journal by recurrence
showAfter : Instant
/// The type of recurrence for this request
recurType : string
/// How many of the recurrence intervals should occur between appearances in the journal
recurCount : int16
/// The history entries for this request
history : History array
/// The notes for this request
notes : Note array
}
/// The old definition of the history entry
[<CLIMutable; NoComparison; NoEquality>]
type OldHistory =
{ /// The time when this history entry was made
asOf : Instant
asOf : int64
/// The status for this history entry
status : RequestAction
/// The text of the update, if applicable
@ -47,12 +16,43 @@ type OldHistory =
[<CLIMutable; NoComparison; NoEquality>]
type OldNote =
{ /// The time when this note was made
asOf : Instant
asOf : int64
/// The text of the notes
notes : string
}
/// Request is the identifying record for a prayer request
[<CLIMutable; NoComparison; NoEquality>]
type OldRequest =
{ /// The ID of the request
id : RequestId
/// The time this request was initially entered
enteredOn : int64
/// The ID of the user to whom this request belongs ("sub" from the JWT)
userId : UserId
/// The time at which this request should reappear in the user's journal by manual user choice
snoozedUntil : int64
/// The time at which this request should reappear in the user's journal by recurrence
showAfter : int64
/// The type of recurrence for this request
recurType : string
/// How many of the recurrence intervals should occur between appearances in the journal
recurCount : int16
/// The history entries for this request
history : OldHistory[]
/// The notes for this request
notes : OldNote[]
}
open LiteDB
open MyPrayerJournal.Data
@ -68,27 +68,41 @@ let mapRecurrence old =
| "Weeks" -> Weeks old.recurCount
| _ -> Immediate
/// Convert an old history entry to the new form
let convertHistory (old : OldHistory) =
{ AsOf = Instant.FromUnixTimeMilliseconds old.asOf
Status = old.status
Text = old.text
}
/// Convert an old note to the new form
let convertNote (old : OldNote) =
{ AsOf = Instant.FromUnixTimeMilliseconds old.asOf
Notes = old.notes
}
/// Map the old request to the new request
let convert old =
{ id = old.id
enteredOn = old.enteredOn
userId = old.userId
snoozedUntil = old.snoozedUntil
showAfter = old.showAfter
recurrence = mapRecurrence old
history = Array.toList old.history
notes = Array.toList old.notes
{ Id = old.id
EnteredOn = Instant.FromUnixTimeMilliseconds old.enteredOn
UserId = old.userId
SnoozedUntil = Instant.FromUnixTimeMilliseconds old.snoozedUntil
ShowAfter = Instant.FromUnixTimeMilliseconds old.showAfter
Recurrence = mapRecurrence old
History = old.history |> Array.map convertHistory |> List.ofArray
Notes = old.notes |> Array.map convertNote |> List.ofArray
}
/// Remove the old request, add the converted one (removes recurType / recurCount fields)
let replace (req : Request) =
db.requests.Delete(Mapping.RequestId.toBson req.id) |> ignore
db.requests.Insert(req) |> ignore
db.Checkpoint()
db.requests.Delete (Mapping.RequestId.toBson req.Id) |> ignore
db.requests.Insert req |> ignore
db.Checkpoint ()
db.GetCollection<OldRequest>("request").FindAll()
db.GetCollection<OldRequest>("request").FindAll ()
|> Seq.map convert
|> Seq.iter replace
|> List.ofSeq
|> List.iter replace
// For more information see https://aka.ms/fsharp-console-apps
printfn "Done"

View File

@ -4,6 +4,7 @@ open LiteDB
open MyPrayerJournal
open NodaTime
open System.Threading.Tasks
open NodaTime.Text
// fsharplint:disable MemberNames
@ -28,11 +29,14 @@ module Extensions =
// It does mapping, but since we're so DU-heavy, this gives us control over the JSON representation
[<RequireQualifiedAccess>]
module Mapping =
/// A NodaTime instant pattern to use for parsing instants from the database
let instantPattern = InstantPattern.CreateWithInvariantCulture "g"
/// Mapping for NodaTime's Instant type
module Instant =
let fromBson (value : BsonValue) = Instant.FromUnixTimeMilliseconds value.AsInt64
let toBson (value : Instant) : BsonValue = value.ToUnixTimeMilliseconds ()
let fromBson (value : BsonValue) = (instantPattern.Parse value.AsString).Value
let toBson (value : Instant) : BsonValue = value.ToString ("g", null)
/// Mapping for option types
module Option =
@ -73,7 +77,7 @@ module Startup =
/// Ensure the database is set up
let ensureDb (db : LiteDatabase) =
db.requests.EnsureIndex (fun it -> it.userId) |> ignore
db.requests.EnsureIndex (fun it -> it.UserId) |> ignore
Mapping.register ()
@ -100,20 +104,20 @@ module private Helpers =
/// Retrieve a request, including its history and notes, by its ID and user ID
let tryFullRequestById reqId userId (db : LiteDatabase) = backgroundTask {
let! req = db.requests.Find (Query.EQ ("_id", RequestId.toString reqId)) |> firstAsync
return match box req with null -> None | _ when req.userId = userId -> Some req | _ -> None
return match box req with null -> None | _ when req.UserId = userId -> Some req | _ -> None
}
/// Add a history entry
let addHistory reqId userId hist db = backgroundTask {
match! tryFullRequestById reqId userId db with
| Some req -> do! doUpdate db { req with history = hist :: req.history }
| Some req -> do! doUpdate db { req with History = hist :: req.History }
| None -> invalidOp $"{RequestId.toString reqId} not found"
}
/// Add a note
let addNote reqId userId note db = backgroundTask {
match! tryFullRequestById reqId userId db with
| Some req -> do! doUpdate db { req with notes = note :: req.notes }
| Some req -> do! doUpdate db { req with Notes = note :: req.Notes }
| None -> invalidOp $"{RequestId.toString reqId} not found"
}
@ -129,8 +133,8 @@ let answeredRequests userId (db : LiteDatabase) = backgroundTask {
return
reqs
|> Seq.map JournalRequest.ofRequestFull
|> Seq.filter (fun it -> it.lastStatus = Answered)
|> Seq.sortByDescending (fun it -> it.asOf)
|> Seq.filter (fun it -> it.LastStatus = Answered)
|> Seq.sortByDescending (fun it -> it.AsOf)
|> List.ofSeq
}
@ -140,26 +144,26 @@ let journalByUserId userId (db : LiteDatabase) = backgroundTask {
return
jrnl
|> Seq.map JournalRequest.ofRequestLite
|> Seq.filter (fun it -> it.lastStatus <> Answered)
|> Seq.sortBy (fun it -> it.asOf)
|> Seq.filter (fun it -> it.LastStatus <> Answered)
|> Seq.sortBy (fun it -> it.AsOf)
|> List.ofSeq
}
/// Does the user have any snoozed requests?
let hasSnoozed userId now (db : LiteDatabase) = backgroundTask {
let! jrnl = journalByUserId userId db
return jrnl |> List.exists (fun r -> r.snoozedUntil > now)
return jrnl |> List.exists (fun r -> r.SnoozedUntil > now)
}
/// Retrieve a request by its ID and user ID (without notes and history)
let tryRequestById reqId userId db = backgroundTask {
let! req = tryFullRequestById reqId userId db
return req |> Option.map (fun r -> { r with history = []; notes = [] })
return req |> Option.map (fun r -> { r with History = []; Notes = [] })
}
/// Retrieve notes for a request by its ID and user ID
let notesById reqId userId (db : LiteDatabase) = backgroundTask {
match! tryFullRequestById reqId userId db with | Some req -> return req.notes | None -> return []
match! tryFullRequestById reqId userId db with | Some req -> return req.Notes | None -> return []
}
/// Retrieve a journal request by its ID and user ID
@ -171,20 +175,20 @@ let tryJournalById reqId userId (db : LiteDatabase) = backgroundTask {
/// Update the recurrence for a request
let updateRecurrence reqId userId recurType db = backgroundTask {
match! tryFullRequestById reqId userId db with
| Some req -> do! doUpdate db { req with recurrence = recurType }
| Some req -> do! doUpdate db { req with Recurrence = recurType }
| None -> invalidOp $"{RequestId.toString reqId} not found"
}
/// Update a snoozed request
let updateSnoozed reqId userId until db = backgroundTask {
match! tryFullRequestById reqId userId db with
| Some req -> do! doUpdate db { req with snoozedUntil = until; showAfter = until }
| Some req -> do! doUpdate db { req with SnoozedUntil = until; ShowAfter = until }
| None -> invalidOp $"{RequestId.toString reqId} not found"
}
/// Update the "show after" timestamp for a request
let updateShowAfter reqId userId showAfter db = backgroundTask {
match! tryFullRequestById reqId userId db with
| Some req -> do! doUpdate db { req with showAfter = showAfter }
| Some req -> do! doUpdate db { req with ShowAfter = showAfter }
| None -> invalidOp $"{RequestId.toString reqId} not found"
}

View File

@ -2,8 +2,6 @@
[<AutoOpen>]
module MyPrayerJournal.Domain
// fsharplint:disable RecordFieldNames
open System
open Cuid
open NodaTime
@ -33,12 +31,16 @@ module UserId =
/// How frequently a request should reappear after it is marked "Prayed"
type Recurrence =
/// A request should reappear immediately at the bottom of the list
| Immediate
/// A request should reappear in the given number of hours
| Hours of int16
/// A request should reappear in the given number of days
| Days of int16
/// A request should reappear in the given number of weeks (7-day increments)
| Weeks of int16
@ -86,142 +88,6 @@ type RequestAction =
| Updated
| Answered
/// History is a record of action taken on a prayer request, including updates to its text
[<CLIMutable; NoComparison; NoEquality>]
type History =
{ /// The time when this history entry was made
asOf : Instant
/// The status for this history entry
status : RequestAction
/// The text of the update, if applicable
text : string option
}
/// Note is a note regarding a prayer request that does not result in an update to its text
[<CLIMutable; NoComparison; NoEquality>]
type Note =
{ /// The time when this note was made
asOf : Instant
/// The text of the notes
notes : string
}
/// Request is the identifying record for a prayer request
[<CLIMutable; NoComparison; NoEquality>]
type Request =
{ /// The ID of the request
id : RequestId
/// The time this request was initially entered
enteredOn : Instant
/// The ID of the user to whom this request belongs ("sub" from the JWT)
userId : UserId
/// The time at which this request should reappear in the user's journal by manual user choice
snoozedUntil : Instant
/// The time at which this request should reappear in the user's journal by recurrence
showAfter : Instant
/// The recurrence for this request
recurrence : Recurrence
/// The history entries for this request
history : History list
/// The notes for this request
notes : Note list
}
/// Functions to support requests
module Request =
/// An empty request
let empty =
{ id = Cuid.generate () |> RequestId
enteredOn = Instant.MinValue
userId = UserId ""
snoozedUntil = Instant.MinValue
showAfter = Instant.MinValue
recurrence = Immediate
history = []
notes = []
}
/// JournalRequest is the form of a prayer request returned for the request journal display. It also contains
/// properties that may be filled for history and notes.
[<NoComparison; NoEquality>]
type JournalRequest =
{ /// The ID of the request (just the CUID part)
requestId : RequestId
/// The ID of the user to whom the request belongs
userId : UserId
/// The current text of the request
text : string
/// The last time action was taken on the request
asOf : Instant
/// The last status for the request
lastStatus : RequestAction
/// The time that this request should reappear in the user's journal
snoozedUntil : Instant
/// The time after which this request should reappear in the user's journal by configured recurrence
showAfter : Instant
/// The recurrence for this request
recurrence : Recurrence
/// History entries for the request
history : History list
/// Note entries for the request
notes : Note list
}
/// Functions to manipulate journal requests
module JournalRequest =
/// Convert a request to the form used for the journal (precomputed values, no notes or history)
let ofRequestLite (req : Request) =
let hist = req.history |> List.sortByDescending (fun it -> it.asOf) |> List.tryHead
{ requestId = req.id
userId = req.userId
text = req.history
|> List.filter (fun it -> Option.isSome it.text)
|> List.sortByDescending (fun it -> it.asOf)
|> List.tryHead
|> Option.map (fun h -> Option.get h.text)
|> Option.defaultValue ""
asOf = match hist with Some h -> h.asOf | None -> Instant.MinValue
lastStatus = match hist with Some h -> h.status | None -> Created
snoozedUntil = req.snoozedUntil
showAfter = req.showAfter
recurrence = req.recurrence
history = []
notes = []
}
/// Same as `ofRequestLite`, but with notes and history
let ofRequestFull req =
{ ofRequestLite req with
history = req.history
notes = req.notes
}
/// Functions to manipulate request actions
module RequestAction =
@ -242,11 +108,150 @@ module RequestAction =
| "Answered" -> Answered
| it -> invalidOp $"Bad request action {it}"
/// History is a record of action taken on a prayer request, including updates to its text
[<CLIMutable; NoComparison; NoEquality>]
type History =
{ /// The time when this history entry was made
AsOf : Instant
/// The status for this history entry
Status : RequestAction
/// The text of the update, if applicable
Text : string option
}
/// Functions to manipulate history entries
module History =
/// Determine if a history's status is `Created`
let isCreated hist = hist.status = Created
let isCreated hist = hist.Status = Created
/// Determine if a history's status is `Prayed`
let isPrayed hist = hist.status = Prayed
let isPrayed hist = hist.Status = Prayed
/// Determine if a history's status is `Answered`
let isAnswered hist = hist.status = Answered
let isAnswered hist = hist.Status = Answered
/// Note is a note regarding a prayer request that does not result in an update to its text
[<CLIMutable; NoComparison; NoEquality>]
type Note =
{ /// The time when this note was made
AsOf : Instant
/// The text of the notes
Notes : string
}
/// Request is the identifying record for a prayer request
[<CLIMutable; NoComparison; NoEquality>]
type Request =
{ /// The ID of the request
Id : RequestId
/// The time this request was initially entered
EnteredOn : Instant
/// The ID of the user to whom this request belongs ("sub" from the JWT)
UserId : UserId
/// The time at which this request should reappear in the user's journal by manual user choice
SnoozedUntil : Instant
/// The time at which this request should reappear in the user's journal by recurrence
ShowAfter : Instant
/// The recurrence for this request
Recurrence : Recurrence
/// The history entries for this request
History : History list
/// The notes for this request
Notes : Note list
}
/// Functions to support requests
module Request =
/// An empty request
let empty =
{ Id = Cuid.generate () |> RequestId
EnteredOn = Instant.MinValue
UserId = UserId ""
SnoozedUntil = Instant.MinValue
ShowAfter = Instant.MinValue
Recurrence = Immediate
History = []
Notes = []
}
/// JournalRequest is the form of a prayer request returned for the request journal display. It also contains
/// properties that may be filled for history and notes.
[<NoComparison; NoEquality>]
type JournalRequest =
{ /// The ID of the request (just the CUID part)
RequestId : RequestId
/// The ID of the user to whom the request belongs
UserId : UserId
/// The current text of the request
Text : string
/// The last time action was taken on the request
AsOf : Instant
/// The last status for the request
LastStatus : RequestAction
/// The time that this request should reappear in the user's journal
SnoozedUntil : Instant
/// The time after which this request should reappear in the user's journal by configured recurrence
ShowAfter : Instant
/// The recurrence for this request
Recurrence : Recurrence
/// History entries for the request
History : History list
/// Note entries for the request
Notes : Note list
}
/// Functions to manipulate journal requests
module JournalRequest =
/// Convert a request to the form used for the journal (precomputed values, no notes or history)
let ofRequestLite (req : Request) =
let hist = req.History |> List.sortByDescending (fun it -> it.AsOf) |> List.tryHead
{ RequestId = req.Id
UserId = req.UserId
Text = req.History
|> List.filter (fun it -> Option.isSome it.Text)
|> List.sortByDescending (fun it -> it.AsOf)
|> List.tryHead
|> Option.map (fun h -> Option.get h.Text)
|> Option.defaultValue ""
AsOf = match hist with Some h -> h.AsOf | None -> Instant.MinValue
LastStatus = match hist with Some h -> h.Status | None -> Created
SnoozedUntil = req.SnoozedUntil
ShowAfter = req.ShowAfter
Recurrence = req.Recurrence
History = []
Notes = []
}
/// Same as `ofRequestLite`, but with notes and history
let ofRequestFull req =
{ ofRequestLite req with
History = req.History
Notes = req.Notes
}

View File

@ -237,7 +237,7 @@ module Components =
let journalItems : HttpHandler = requiresAuthentication Error.notAuthorized >=> fun next ctx -> backgroundTask {
let now = now ctx
let! jrnl = Data.journalByUserId (userId ctx) (db ctx)
let shown = jrnl |> List.filter (fun it -> now > it.snoozedUntil && now > it.showAfter)
let shown = jrnl |> List.filter (fun it -> now > it.SnoozedUntil && now > it.ShowAfter)
return! renderComponent [ Views.Journal.journalItems now shown ] next ctx
}
@ -332,9 +332,9 @@ module Request =
match! Data.tryRequestById reqId usrId db with
| Some req ->
let now = now ctx
do! Data.addHistory reqId usrId { asOf = now; status = Prayed; text = None } db
do! Data.addHistory reqId usrId { AsOf = now; Status = Prayed; Text = None } db
let nextShow =
match Recurrence.duration req.recurrence with
match Recurrence.duration req.Recurrence with
| 0L -> Instant.MinValue
| duration -> now.Plus (Duration.FromSeconds duration)
do! Data.updateShowAfter reqId usrId nextShow db
@ -351,7 +351,7 @@ module Request =
match! Data.tryRequestById reqId usrId db with
| Some _ ->
let! notes = ctx.BindFormAsync<Models.NoteEntry> ()
do! Data.addNote reqId usrId { asOf = now ctx; notes = notes.notes } db
do! Data.addNote reqId usrId { AsOf = now ctx; Notes = notes.notes } db
do! db.saveChanges ()
return! (withSuccessMessage "Added Notes" >=> hideModal "notes" >=> created) next ctx
| None -> return! Error.notFound next ctx
@ -367,7 +367,7 @@ module Request =
let snoozed : HttpHandler = requiresAuthentication Error.notAuthorized >=> fun next ctx -> backgroundTask {
let! reqs = Data.journalByUserId (userId ctx) (db ctx)
let now = now ctx
let snoozed = reqs |> List.filter (fun it -> it.snoozedUntil > now)
let snoozed = reqs |> List.filter (fun it -> it.SnoozedUntil > now)
return! partial "Active Requests" (Views.Request.snoozed now snoozed) next ctx
}
@ -444,14 +444,14 @@ module Request =
let now = now ctx
let req =
{ Request.empty with
userId = usrId
enteredOn = now
showAfter = Instant.MinValue
recurrence = parseRecurrence form
history = [
{ asOf = now
status = Created
text = Some form.requestText
UserId = usrId
EnteredOn = now
ShowAfter = Instant.MinValue
Recurrence = parseRecurrence form
History = [
{ AsOf = now
Status = Created
Text = Some form.requestText
}
]
}
@ -470,18 +470,18 @@ module Request =
| Some req ->
// update recurrence if changed
let recur = parseRecurrence form
match recur = req.recurrence with
match recur = req.Recurrence with
| true -> ()
| false ->
do! Data.updateRecurrence req.requestId usrId recur db
do! Data.updateRecurrence req.RequestId usrId recur db
match recur with
| Immediate -> do! Data.updateShowAfter req.requestId usrId Instant.MinValue db
| Immediate -> do! Data.updateShowAfter req.RequestId usrId Instant.MinValue db
| _ -> ()
// append history
let upd8Text = form.requestText.Trim ()
let text = match upd8Text = req.text with true -> None | false -> Some upd8Text
do! Data.addHistory req.requestId usrId
{ asOf = now ctx; status = (Option.get >> RequestAction.ofString) form.status; text = text } db
let text = match upd8Text = req.Text with true -> None | false -> Some upd8Text
do! Data.addHistory req.RequestId usrId
{ AsOf = now ctx; Status = (Option.get >> RequestAction.ofString) form.status; Text = text } db
do! db.saveChanges ()
let nextUrl =
match form.returnTo with

View File

@ -8,7 +8,7 @@ open MyPrayerJournal
/// Display a card for this prayer request
let journalCard now req =
let reqId = RequestId.toString req.requestId
let reqId = RequestId.toString req.RequestId
let spacer = span [] [ rawText "&nbsp;" ]
div [ _class "col" ] [
div [ _class "card h-100" ] [
@ -45,10 +45,10 @@ let journalCard now req =
]
]
div [ _class "card-body" ] [
p [ _class "request-text" ] [ str req.text ]
p [ _class "request-text" ] [ str req.Text ]
]
div [ _class "card-footer text-end text-muted px-1 py-0" ] [
em [] [ str "last activity "; relativeDate req.asOf now ]
em [] [ str "last activity "; relativeDate req.AsOf now ]
]
]
]

View File

@ -8,10 +8,10 @@ open NodaTime
/// Create a request within the list
let reqListItem now req =
let reqId = RequestId.toString req.requestId
let isAnswered = req.lastStatus = Answered
let isSnoozed = req.snoozedUntil > now
let isPending = (not isSnoozed) && req.showAfter > now
let reqId = RequestId.toString req.RequestId
let isAnswered = req.LastStatus = Answered
let isSnoozed = req.SnoozedUntil > now
let isPending = (not isSnoozed) && req.ShowAfter > now
let btnClass = _class "btn btn-light mx-2"
let restoreBtn (link : string) title =
button [ btnClass; _hxPatch $"/request/{reqId}/{link}"; _title title ] [ icon "restore" ]
@ -23,13 +23,13 @@ let reqListItem now req =
if isSnoozed then restoreBtn "cancel-snooze" "Cancel Snooze"
elif isPending then restoreBtn "show" "Show Now"
p [ _class "request-text mb-0" ] [
str req.text
str req.Text
if isSnoozed || isPending || isAnswered then
br []
small [ _class "text-muted" ] [
if isSnoozed then [ str "Snooze expires "; relativeDate req.snoozedUntil now ]
elif isPending then [ str "Request appears next "; relativeDate req.showAfter now ]
else (* isAnswered *) [ str "Answered "; relativeDate req.asOf now ]
if isSnoozed then [ str "Snooze expires "; relativeDate req.SnoozedUntil now ]
elif isPending then [ str "Request appears next "; relativeDate req.ShowAfter now ]
else (* isAnswered *) [ str "Answered "; relativeDate req.AsOf now ]
|> em []
]
]
@ -74,27 +74,27 @@ let snoozed now reqs =
let full (clock : IClock) (req : Request) =
let now = clock.GetCurrentInstant ()
let answered =
req.history
|> List.filter RequestAction.isAnswered
req.History
|> List.filter History.isAnswered
|> List.tryHead
|> Option.map (fun x -> x.asOf)
let prayed = (req.history |> List.filter RequestAction.isPrayed |> List.length).ToString "N0"
|> Option.map (fun x -> x.AsOf)
let prayed = (req.History |> List.filter History.isPrayed |> List.length).ToString "N0"
let daysOpen =
let asOf = defaultArg answered now
((asOf - (req.history |> List.filter RequestAction.isCreated |> List.head).asOf).TotalDays |> int).ToString "N0"
((asOf - (req.History |> List.filter History.isCreated |> List.head).AsOf).TotalDays |> int).ToString "N0"
let lastText =
req.history
|> List.filter (fun h -> Option.isSome h.text)
|> List.sortByDescending (fun h -> h.asOf)
|> List.map (fun h -> Option.get h.text)
req.History
|> List.filter (fun h -> Option.isSome h.Text)
|> List.sortByDescending (fun h -> h.AsOf)
|> List.map (fun h -> Option.get h.Text)
|> List.head
// The history log including notes (and excluding the final entry for answered requests)
let log =
let toDisp (h : History) = {| asOf = h.asOf; text = h.text; status = RequestAction.toString h.status |}
let toDisp (h : History) = {| asOf = h.AsOf; text = h.Text; status = RequestAction.toString h.Status |}
let all =
req.notes
|> List.map (fun n -> {| asOf = n.asOf; text = Some n.notes; status = "Notes" |})
|> List.append (req.history |> List.map toDisp)
req.Notes
|> List.map (fun n -> {| asOf = n.AsOf; text = Some n.Notes; status = "Notes" |})
|> List.append (req.History |> List.map toDisp)
|> List.sortByDescending (fun it -> it.asOf)
// Skip the first entry for answered requests; that info is already displayed
match answered with Some _ -> all |> List.skip 1 | None -> all
@ -139,7 +139,7 @@ let edit (req : JournalRequest) returnTo isNew =
| "snoozed" -> "/requests/snoozed"
| _ (* "journal" *) -> "/journal"
let recurCount =
match req.recurrence with
match req.Recurrence with
| Immediate -> None
| Hours h -> Some h
| Days d -> Some d
@ -154,7 +154,7 @@ let edit (req : JournalRequest) returnTo isNew =
"/request" |> match isNew with true -> _hxPost | false -> _hxPatch ] [
input [ _type "hidden"
_name "requestId"
_value (match isNew with true -> "new" | false -> RequestId.toString req.requestId) ]
_value (match isNew with true -> "new" | false -> RequestId.toString req.RequestId) ]
input [ _type "hidden"; _name "returnTo"; _value returnTo ]
div [ _class "form-floating pb-3" ] [
textarea [ _id "requestText"
@ -162,7 +162,7 @@ let edit (req : JournalRequest) returnTo isNew =
_class "form-control"
_style "min-height: 8rem;"
_placeholder "Enter the text of the request"
_autofocus; _required ] [ str req.text ]
_autofocus; _required ] [ str req.Text ]
label [ _for "requestText" ] [ str "Prayer Request" ]
]
br []
@ -202,7 +202,7 @@ let edit (req : JournalRequest) returnTo isNew =
_name "recurType"
_value "Immediate"
_onclick "mpj.edit.toggleRecurrence(event)"
match req.recurrence with Immediate -> _checked | _ -> () ]
match req.Recurrence with Immediate -> _checked | _ -> () ]
label [ _for "rI" ] [ str "Immediately" ]
]
div [ _class "form-check mx-2"] [
@ -212,7 +212,7 @@ let edit (req : JournalRequest) returnTo isNew =
_name "recurType"
_value "Other"
_onclick "mpj.edit.toggleRecurrence(event)"
match req.recurrence with Immediate -> () | _ -> _checked ]
match req.Recurrence with Immediate -> () | _ -> _checked ]
label [ _for "rO" ] [ rawText "Every&hellip;" ]
]
div [ _class "form-floating mx-2"] [
@ -224,7 +224,7 @@ let edit (req : JournalRequest) returnTo isNew =
_value recurCount
_style "width:6rem;"
_required
match req.recurrence with Immediate -> _disabled | _ -> () ]
match req.Recurrence with Immediate -> _disabled | _ -> () ]
label [ _for "recurCount" ] [ str "Count" ]
]
div [ _class "form-floating mx-2" ] [
@ -233,14 +233,14 @@ let edit (req : JournalRequest) returnTo isNew =
_name "recurInterval"
_style "width:6rem;"
_required
match req.recurrence with Immediate -> _disabled | _ -> () ] [
option [ _value "Hours"; match req.recurrence with Hours _ -> _selected | _ -> () ] [
match req.Recurrence with Immediate -> _disabled | _ -> () ] [
option [ _value "Hours"; match req.Recurrence with Hours _ -> _selected | _ -> () ] [
str "hours"
]
option [ _value "Days"; match req.recurrence with Days _ -> _selected | _ -> () ] [
option [ _value "Days"; match req.Recurrence with Days _ -> _selected | _ -> () ] [
str "days"
]
option [ _value "Weeks"; match req.recurrence with Weeks _ -> _selected | _ -> () ] [
option [ _value "Weeks"; match req.Recurrence with Weeks _ -> _selected | _ -> () ] [
str "weeks"
]
]
@ -259,7 +259,7 @@ let edit (req : JournalRequest) returnTo isNew =
/// Display a list of notes for a request
let notes now notes =
let toItem (note : Note) =
p [] [ small [ _class "text-muted" ] [ relativeDate note.asOf now ]; br []; str note.notes ]
p [] [ small [ _class "text-muted" ] [ relativeDate note.AsOf now ]; br []; str note.Notes ]
[ p [ _class "text-center" ] [ strong [] [ str "Prior Notes for This Request" ] ]
match notes with
| [] -> p [ _class "text-center text-muted" ] [ str "There are no prior notes for this request" ]