WIP on journal
This commit is contained in:
parent
765636ee88
commit
b74b4a7e65
@ -37,7 +37,7 @@ module Mapping =
|
|||||||
/// Map a BSON document to a history entry
|
/// Map a BSON document to a history entry
|
||||||
let historyFromBson (doc : BsonValue) =
|
let historyFromBson (doc : BsonValue) =
|
||||||
{ asOf = Ticks doc.["asOf"].AsInt64
|
{ asOf = Ticks doc.["asOf"].AsInt64
|
||||||
status = RequestAction.fromString doc.["status"].AsString
|
status = RequestAction.ofString doc.["status"].AsString
|
||||||
text = match doc.["text"].AsString with "" -> None | txt -> Some txt
|
text = match doc.["text"].AsString with "" -> None | txt -> Some txt
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ module Mapping =
|
|||||||
userId = UserId doc.["userId"].AsString
|
userId = UserId doc.["userId"].AsString
|
||||||
snoozedUntil = Ticks doc.["snoozedUntil"].AsInt64
|
snoozedUntil = Ticks doc.["snoozedUntil"].AsInt64
|
||||||
showAfter = Ticks doc.["showAfter"].AsInt64
|
showAfter = Ticks doc.["showAfter"].AsInt64
|
||||||
recurType = Recurrence.fromString doc.["recurType"].AsString
|
recurType = Recurrence.ofString doc.["recurType"].AsString
|
||||||
recurCount = int16 doc.["recurCount"].AsInt32
|
recurCount = int16 doc.["recurCount"].AsInt32
|
||||||
history = doc.["history"].AsArray |> Seq.map historyFromBson |> List.ofSeq
|
history = doc.["history"].AsArray |> Seq.map historyFromBson |> List.ofSeq
|
||||||
notes = doc.["notes"].AsArray |> Seq.map noteFromBson |> List.ofSeq
|
notes = doc.["notes"].AsArray |> Seq.map noteFromBson |> List.ofSeq
|
||||||
|
@ -24,8 +24,8 @@ let internal locales =
|
|||||||
"en-US", Map.ofList [
|
"en-US", Map.ofList [
|
||||||
LessThanXMinutes, ("less than a minute", format "less than %i minutes")
|
LessThanXMinutes, ("less than a minute", format "less than %i minutes")
|
||||||
XMinutes, ("a minute", format "%i minutes")
|
XMinutes, ("a minute", format "%i minutes")
|
||||||
AboutXHours, ("about an hour", format "about %i hours")
|
AboutXHours, ("about an hour", format "about %i hours")
|
||||||
XHours, ("an hour", format "%i hours")
|
XHours, ("an hour", format "%i hours")
|
||||||
XDays, ("a day", format "%i days")
|
XDays, ("a day", format "%i days")
|
||||||
AboutXWeeks, ("about a week", format "about %i weeks")
|
AboutXWeeks, ("about a week", format "about %i weeks")
|
||||||
XWeeks, ("a week", format "%i weeks")
|
XWeeks, ("a week", format "%i weeks")
|
||||||
@ -55,18 +55,19 @@ let formatDistance (startDate : DateTime) (endDate : DateTime) =
|
|||||||
let round (it : float) = Math.Round it |> int
|
let round (it : float) = Math.Round it |> int
|
||||||
|
|
||||||
let diff = startDate - endDate
|
let diff = startDate - endDate
|
||||||
|
let minutes = Math.Abs diff.TotalMinutes
|
||||||
let formatToken =
|
let formatToken =
|
||||||
let months = diff.TotalMinutes / aMonth |> round
|
let months = minutes / aMonth |> round
|
||||||
let years = months / 12
|
let years = months / 12
|
||||||
match true with
|
match true with
|
||||||
| _ when diff.TotalMinutes = 0. -> LessThanXMinutes, 1
|
| _ when minutes < 1. -> LessThanXMinutes, 1
|
||||||
| _ when diff.TotalMinutes < 45. -> XMinutes, round diff.TotalMinutes
|
| _ when minutes < 45. -> XMinutes, round minutes
|
||||||
| _ when diff.TotalMinutes < 90. -> AboutXHours, 1
|
| _ when minutes < 90. -> AboutXHours, 1
|
||||||
| _ when diff.TotalMinutes < aDay -> AboutXHours, round (diff.TotalMinutes / 60.)
|
| _ when minutes < aDay -> AboutXHours, round (minutes / 60.)
|
||||||
| _ when diff.TotalMinutes < almostTwoDays -> XDays, 1
|
| _ when minutes < almostTwoDays -> XDays, 1
|
||||||
| _ when diff.TotalMinutes < aMonth -> XDays, round (diff.TotalMinutes / aDay)
|
| _ when minutes < aMonth -> XDays, round (minutes / aDay)
|
||||||
| _ when diff.TotalMinutes < twoMonths -> AboutXMonths, round (diff.TotalMinutes / aMonth)
|
| _ when minutes < twoMonths -> AboutXMonths, round (minutes / aMonth)
|
||||||
| _ when months < 12 -> XMonths, round (diff.TotalMinutes / aMonth)
|
| _ when months < 12 -> XMonths, round (minutes / aMonth)
|
||||||
| _ when months % 12 < 3 -> AboutXYears, years
|
| _ when months % 12 < 3 -> AboutXYears, years
|
||||||
| _ when months % 12 < 9 -> OverXYears, years
|
| _ when months % 12 < 9 -> OverXYears, years
|
||||||
| _ -> AlmostXYears, years + 1
|
| _ -> AlmostXYears, years + 1
|
||||||
|
@ -55,7 +55,7 @@ module Recurrence =
|
|||||||
| Days -> "Days"
|
| Days -> "Days"
|
||||||
| Weeks -> "Weeks"
|
| Weeks -> "Weeks"
|
||||||
/// Create a recurrence value from a string
|
/// Create a recurrence value from a string
|
||||||
let fromString =
|
let ofString =
|
||||||
function
|
function
|
||||||
| "Immediate" -> Immediate
|
| "Immediate" -> Immediate
|
||||||
| "Hours" -> Hours
|
| "Hours" -> Hours
|
||||||
@ -207,7 +207,7 @@ module RequestAction =
|
|||||||
| Updated -> "Updated"
|
| Updated -> "Updated"
|
||||||
| Answered -> "Answered"
|
| Answered -> "Answered"
|
||||||
/// Create a RequestAction from a string
|
/// Create a RequestAction from a string
|
||||||
let fromString =
|
let ofString =
|
||||||
function
|
function
|
||||||
| "Created" -> Created
|
| "Created" -> Created
|
||||||
| "Prayed" -> Prayed
|
| "Prayed" -> Prayed
|
||||||
|
@ -27,7 +27,6 @@ module Error =
|
|||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
module private Helpers =
|
module private Helpers =
|
||||||
|
|
||||||
open Cuid
|
|
||||||
open LiteDB
|
open LiteDB
|
||||||
open Microsoft.AspNetCore.Http
|
open Microsoft.AspNetCore.Http
|
||||||
open Microsoft.Extensions.Logging
|
open Microsoft.Extensions.Logging
|
||||||
@ -52,13 +51,6 @@ module private Helpers =
|
|||||||
let userId ctx =
|
let userId ctx =
|
||||||
((user >> Option.get) ctx).Value |> UserId
|
((user >> Option.get) ctx).Value |> UserId
|
||||||
|
|
||||||
/// Create a request ID from a string
|
|
||||||
let toReqId x =
|
|
||||||
match Cuid.ofString x with
|
|
||||||
| Ok cuid -> cuid
|
|
||||||
| Error msg -> invalidOp msg
|
|
||||||
|> RequestId
|
|
||||||
|
|
||||||
/// Return a 201 CREATED response
|
/// Return a 201 CREATED response
|
||||||
let created =
|
let created =
|
||||||
setStatusCode 201
|
setStatusCode 201
|
||||||
@ -107,18 +99,10 @@ module private Helpers =
|
|||||||
let withSuccessMessage : string -> HttpHandler =
|
let withSuccessMessage : string -> HttpHandler =
|
||||||
sprintf "success|||%s" >> setHttpHeader "X-Toast"
|
sprintf "success|||%s" >> setHttpHeader "X-Toast"
|
||||||
|
|
||||||
|
|
||||||
/// Strongly-typed models for post requests
|
/// Strongly-typed models for post requests
|
||||||
module Models =
|
module Models =
|
||||||
|
|
||||||
/// A history entry addition (AKA request update)
|
|
||||||
[<CLIMutable>]
|
|
||||||
type HistoryEntry = {
|
|
||||||
/// The status of the history update
|
|
||||||
status : string
|
|
||||||
/// The text of the update
|
|
||||||
updateText : string option
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An additional note
|
/// An additional note
|
||||||
[<CLIMutable>]
|
[<CLIMutable>]
|
||||||
type NoteEntry = {
|
type NoteEntry = {
|
||||||
@ -126,15 +110,6 @@ module Models =
|
|||||||
notes : string
|
notes : string
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recurrence update
|
|
||||||
[<CLIMutable>]
|
|
||||||
type Recurrence = {
|
|
||||||
/// The recurrence type
|
|
||||||
recurType : string
|
|
||||||
/// The recurrence cound
|
|
||||||
recurCount : int16
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A prayer request
|
/// A prayer request
|
||||||
[<CLIMutable>]
|
[<CLIMutable>]
|
||||||
type Request = {
|
type Request = {
|
||||||
@ -177,8 +152,10 @@ module Components =
|
|||||||
let journalItems : HttpHandler =
|
let journalItems : HttpHandler =
|
||||||
authorize
|
authorize
|
||||||
>=> fun next ctx -> task {
|
>=> fun next ctx -> task {
|
||||||
let! jrnl = Data.journalByUserId (userId ctx) (db ctx)
|
let shouldShow now r = now > Ticks.toLong r.snoozedUntil && now > Ticks.toLong r.showAfter
|
||||||
return! renderComponent [ Views.Journal.journalItems jrnl ] next ctx
|
let! jrnl = Data.journalByUserId (userId ctx) (db ctx)
|
||||||
|
let shown = jrnl |> List.filter (shouldShow ((jsNow >> Ticks.toLong) ()))
|
||||||
|
return! renderComponent [ Views.Journal.journalItems shown ] next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /components/request/[req-id]/edit
|
// GET /components/request/[req-id]/edit
|
||||||
@ -253,33 +230,24 @@ module Ply = FSharp.Control.Tasks.Affine
|
|||||||
/// /api/request and /request(s) URLs
|
/// /api/request and /request(s) URLs
|
||||||
module Request =
|
module Request =
|
||||||
|
|
||||||
/// POST /api/request/[req-id]/history
|
// PATCH /request/[req-id]/prayed
|
||||||
let addHistory requestId : HttpHandler =
|
let prayed requestId : HttpHandler =
|
||||||
authorize
|
authorize
|
||||||
>=> fun next ctx -> FSharp.Control.Tasks.Affine.task {
|
>=> fun next ctx -> task {
|
||||||
let db = db ctx
|
let db = db ctx
|
||||||
let usrId = userId ctx
|
let usrId = userId ctx
|
||||||
let reqId = toReqId requestId
|
let reqId = RequestId.ofString requestId
|
||||||
match! Data.tryRequestById reqId usrId db with
|
match! Data.tryRequestById reqId usrId db with
|
||||||
| Some req ->
|
| Some req ->
|
||||||
let! hist = ctx.BindJsonAsync<Models.HistoryEntry> ()
|
let now = jsNow ()
|
||||||
let now = jsNow ()
|
do! Data.addHistory reqId usrId { asOf = now; status = Prayed; text = None } db
|
||||||
let act = RequestAction.fromString hist.status
|
let nextShow =
|
||||||
do! Data.addHistory reqId usrId
|
match Recurrence.duration req.recurType with
|
||||||
{ asOf = now
|
| 0L -> 0L
|
||||||
status = act
|
| duration -> (Ticks.toLong now) + (duration * int64 req.recurCount)
|
||||||
text = match hist.updateText with None | Some "" -> None | x -> x
|
do! Data.updateShowAfter reqId usrId (Ticks nextShow) db
|
||||||
} db
|
|
||||||
match act with
|
|
||||||
| Prayed ->
|
|
||||||
let nextShow =
|
|
||||||
match Recurrence.duration req.recurType with
|
|
||||||
| 0L -> 0L
|
|
||||||
| duration -> (Ticks.toLong now) + (duration * int64 req.recurCount)
|
|
||||||
do! Data.updateShowAfter reqId usrId (Ticks nextShow) db
|
|
||||||
| _ -> ()
|
|
||||||
do! db.saveChanges ()
|
do! db.saveChanges ()
|
||||||
return! created next ctx
|
return! (withSuccessMessage "Request marked as prayed" >=> Components.journalItems) next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,7 +257,7 @@ module Request =
|
|||||||
>=> fun next ctx -> task {
|
>=> fun next ctx -> task {
|
||||||
let db = db ctx
|
let db = db ctx
|
||||||
let usrId = userId ctx
|
let usrId = userId ctx
|
||||||
let reqId = toReqId requestId
|
let reqId = RequestId.ofString requestId
|
||||||
match! Data.tryRequestById reqId usrId db with
|
match! Data.tryRequestById reqId usrId db with
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
let! notes = ctx.BindJsonAsync<Models.NoteEntry> ()
|
let! notes = ctx.BindJsonAsync<Models.NoteEntry> ()
|
||||||
@ -307,6 +275,16 @@ module Request =
|
|||||||
return! partialIfNotRefresh "Active Requests" (Views.Request.active reqs) next ctx
|
return! partialIfNotRefresh "Active Requests" (Views.Request.active reqs) next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// GET /requests/snoozed
|
||||||
|
let snoozed : HttpHandler =
|
||||||
|
authorize
|
||||||
|
>=> fun next ctx -> task {
|
||||||
|
let! reqs = Data.journalByUserId (userId ctx) (db ctx)
|
||||||
|
let now = (jsNow >> Ticks.toLong) ()
|
||||||
|
let snoozed = reqs |> List.filter (fun r -> Ticks.toLong r.snoozedUntil > now)
|
||||||
|
return! partialIfNotRefresh "Active Requests" (Views.Request.snoozed snoozed) next ctx
|
||||||
|
}
|
||||||
|
|
||||||
/// GET /requests/answered
|
/// GET /requests/answered
|
||||||
let answered : HttpHandler =
|
let answered : HttpHandler =
|
||||||
authorize
|
authorize
|
||||||
@ -319,17 +297,17 @@ module Request =
|
|||||||
let get requestId : HttpHandler =
|
let get requestId : HttpHandler =
|
||||||
authorize
|
authorize
|
||||||
>=> fun next ctx -> task {
|
>=> fun next ctx -> task {
|
||||||
match! Data.tryJournalById (toReqId requestId) (userId ctx) (db ctx) with
|
match! Data.tryJournalById (RequestId.ofString requestId) (userId ctx) (db ctx) with
|
||||||
| Some req -> return! json req next ctx
|
| Some req -> return! json req next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// GET /request/[req-id]/full
|
// GET /request/[req-id]/full
|
||||||
let getFull requestId : HttpHandler =
|
let getFull requestId : HttpHandler =
|
||||||
authorize
|
authorize
|
||||||
>=> fun next ctx -> task {
|
>=> fun next ctx -> task {
|
||||||
match! Data.tryFullRequestById (toReqId requestId) (userId ctx) (db ctx) with
|
match! Data.tryFullRequestById (RequestId.ofString requestId) (userId ctx) (db ctx) with
|
||||||
| Some req -> return! partialIfNotRefresh "Full Prayer Request" (Views.Request.full req) next ctx
|
| Some req -> return! partialIfNotRefresh "Prayer Request" (Views.Request.full req) next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,22 +315,22 @@ module Request =
|
|||||||
let getNotes requestId : HttpHandler =
|
let getNotes requestId : HttpHandler =
|
||||||
authorize
|
authorize
|
||||||
>=> fun next ctx -> task {
|
>=> fun next ctx -> task {
|
||||||
let! notes = Data.notesById (toReqId requestId) (userId ctx) (db ctx)
|
let! notes = Data.notesById (RequestId.ofString requestId) (userId ctx) (db ctx)
|
||||||
return! json notes next ctx
|
return! json notes next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PATCH /api/request/[req-id]/show
|
// PATCH /request/[req-id]/show
|
||||||
let show requestId : HttpHandler =
|
let show requestId : HttpHandler =
|
||||||
authorize
|
authorize
|
||||||
>=> fun next ctx -> task {
|
>=> fun next ctx -> task {
|
||||||
let db = db ctx
|
let db = db ctx
|
||||||
let usrId = userId ctx
|
let usrId = userId ctx
|
||||||
let reqId = toReqId requestId
|
let reqId = RequestId.ofString requestId
|
||||||
match! Data.tryRequestById reqId usrId db with
|
match! Data.tryRequestById reqId usrId db with
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
do! Data.updateShowAfter reqId usrId (Ticks 0L) db
|
do! Data.updateShowAfter reqId usrId (Ticks 0L) db
|
||||||
do! db.saveChanges ()
|
do! db.saveChanges ()
|
||||||
return! setStatusCode 204 next ctx
|
return! (withSuccessMessage "Request now shown" >=> Components.requestItem requestId) next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,7 +340,7 @@ module Request =
|
|||||||
>=> fun next ctx -> task {
|
>=> fun next ctx -> task {
|
||||||
let db = db ctx
|
let db = db ctx
|
||||||
let usrId = userId ctx
|
let usrId = userId ctx
|
||||||
let reqId = toReqId requestId
|
let reqId = RequestId.ofString requestId
|
||||||
match! Data.tryRequestById reqId usrId db with
|
match! Data.tryRequestById reqId usrId db with
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
let! until = ctx.BindJsonAsync<Models.SnoozeUntil> ()
|
let! until = ctx.BindJsonAsync<Models.SnoozeUntil> ()
|
||||||
@ -372,29 +350,24 @@ module Request =
|
|||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PATCH /api/request/[req-id]/recurrence
|
// PATCH /request/[req-id]/cancel-snooze
|
||||||
let updateRecurrence requestId : HttpHandler =
|
let cancelSnooze requestId : HttpHandler =
|
||||||
authorize
|
authorize
|
||||||
>=> fun next ctx -> FSharp.Control.Tasks.Affine.task {
|
>=> fun next ctx -> task {
|
||||||
let db = db ctx
|
let db = db ctx
|
||||||
let usrId = userId ctx
|
let usrId = userId ctx
|
||||||
let reqId = toReqId requestId
|
let reqId = RequestId.ofString requestId
|
||||||
match! Data.tryRequestById reqId usrId db with
|
match! Data.tryRequestById reqId usrId db with
|
||||||
| Some _ ->
|
| Some _ ->
|
||||||
let! recur = ctx.BindJsonAsync<Models.Recurrence> ()
|
do! Data.updateSnoozed reqId usrId (Ticks 0L) db
|
||||||
let recurrence = Recurrence.fromString recur.recurType
|
|
||||||
do! Data.updateRecurrence reqId usrId recurrence recur.recurCount db
|
|
||||||
match recurrence with
|
|
||||||
| Immediate -> do! Data.updateShowAfter reqId usrId (Ticks 0L) db
|
|
||||||
| _ -> ()
|
|
||||||
do! db.saveChanges ()
|
do! db.saveChanges ()
|
||||||
return! setStatusCode 204 next ctx
|
return! (withSuccessMessage "Request unsnoozed" >=> Components.requestItem requestId) next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Derive a recurrence and interval from its primitive representation in the form
|
/// Derive a recurrence and interval from its primitive representation in the form
|
||||||
let private parseRecurrence (form : Models.Request) =
|
let private parseRecurrence (form : Models.Request) =
|
||||||
(Recurrence.fromString (match form.recurInterval with Some x -> x | _ -> "Immediate"),
|
(Recurrence.ofString (match form.recurInterval with Some x -> x | _ -> "Immediate"),
|
||||||
defaultArg form.recurCount (int16 0))
|
defaultArg form.recurCount (int16 0))
|
||||||
|
|
||||||
// POST /request
|
// POST /request
|
||||||
@ -433,7 +406,7 @@ module Request =
|
|||||||
let usrId = userId ctx
|
let usrId = userId ctx
|
||||||
match! Data.tryJournalById (RequestId.ofString form.requestId) usrId db with
|
match! Data.tryJournalById (RequestId.ofString form.requestId) usrId db with
|
||||||
| Some req ->
|
| Some req ->
|
||||||
// step 1 - update recurrence if changed
|
// update recurrence if changed
|
||||||
let (recur, interval) = parseRecurrence form
|
let (recur, interval) = parseRecurrence form
|
||||||
match recur = req.recurType && interval = req.recurCount with
|
match recur = req.recurType && interval = req.recurCount with
|
||||||
| true -> ()
|
| true -> ()
|
||||||
@ -442,20 +415,18 @@ module Request =
|
|||||||
match recur with
|
match recur with
|
||||||
| Immediate -> do! Data.updateShowAfter req.requestId usrId (Ticks 0L) db
|
| Immediate -> do! Data.updateShowAfter req.requestId usrId (Ticks 0L) db
|
||||||
| _ -> ()
|
| _ -> ()
|
||||||
// step 2 - append history
|
// append history
|
||||||
let upd8Text = form.requestText.Trim ()
|
let upd8Text = form.requestText.Trim ()
|
||||||
let text = match upd8Text = req.text with true -> None | false -> Some upd8Text
|
let text = match upd8Text = req.text with true -> None | false -> Some upd8Text
|
||||||
do! Data.addHistory req.requestId usrId
|
do! Data.addHistory req.requestId usrId
|
||||||
{ asOf = jsNow (); status = (Option.get >> RequestAction.fromString) form.status; text = text } db
|
{ asOf = jsNow (); status = (Option.get >> RequestAction.ofString) form.status; text = text } db
|
||||||
do! db.saveChanges ()
|
do! db.saveChanges ()
|
||||||
// step 3 - return updated view
|
|
||||||
return! (withSuccessMessage "Prayer request updated successfully"
|
return! (withSuccessMessage "Prayer request updated successfully"
|
||||||
>=> Components.requestItem (RequestId.toString req.requestId)) next ctx
|
>=> Components.requestItem (RequestId.toString req.requestId)) next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
open Giraffe.EndpointRouting
|
open Giraffe.EndpointRouting
|
||||||
|
|
||||||
/// The routes for myPrayerJournal
|
/// The routes for myPrayerJournal
|
||||||
@ -478,12 +449,16 @@ let routes =
|
|||||||
]
|
]
|
||||||
subRoute "/request" [
|
subRoute "/request" [
|
||||||
GET_HEAD [
|
GET_HEAD [
|
||||||
|
routef "/%s/full" Request.getFull
|
||||||
route "s/active" Request.active
|
route "s/active" Request.active
|
||||||
route "s/answered" Request.answered
|
route "s/answered" Request.answered
|
||||||
routef "/%s/full" Request.getFull
|
route "s/snoozed" Request.snoozed
|
||||||
]
|
]
|
||||||
PATCH [
|
PATCH [
|
||||||
route "" Request.update
|
route "" Request.update
|
||||||
|
routef "/%s/cancel-snooze" Request.cancelSnooze
|
||||||
|
routef "/%s/prayed" Request.prayed
|
||||||
|
routef "/%s/show" Request.show
|
||||||
]
|
]
|
||||||
POST [
|
POST [
|
||||||
route "" Request.add
|
route "" Request.add
|
||||||
@ -499,14 +474,11 @@ let routes =
|
|||||||
]
|
]
|
||||||
PATCH [
|
PATCH [
|
||||||
subRoute "request" [
|
subRoute "request" [
|
||||||
routef "/%s/recurrence" Request.updateRecurrence
|
|
||||||
routef "/%s/show" Request.show
|
|
||||||
routef "/%s/snooze" Request.snooze
|
routef "/%s/snooze" Request.snooze
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
POST [
|
POST [
|
||||||
subRoute "request" [
|
subRoute "request" [
|
||||||
routef "/%s/history" Request.addHistory
|
|
||||||
routef "/%s/note" Request.addNote
|
routef "/%s/note" Request.addNote
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
module MyPrayerJournal.Views
|
module MyPrayerJournal.Views
|
||||||
|
|
||||||
open Giraffe.ViewEngine
|
open Giraffe.ViewEngine
|
||||||
|
open Giraffe.ViewEngine.Accessibility
|
||||||
open Giraffe.ViewEngine.Htmx
|
open Giraffe.ViewEngine.Htmx
|
||||||
open System
|
open System
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ module Helpers =
|
|||||||
/// Create a date with a span tag, displaying the relative date with the full date/time in the tooltip
|
/// Create a date with a span tag, displaying the relative date with the full date/time in the tooltip
|
||||||
let relativeDate jsDate =
|
let relativeDate jsDate =
|
||||||
let date = fromJs jsDate
|
let date = fromJs jsDate
|
||||||
span [ _title (date.ToString "f") ] [ Dates.formatDistance DateTime.Now date |> str ]
|
span [ _title (date.ToString "f") ] [ Dates.formatDistance DateTime.UtcNow date |> str ]
|
||||||
|
|
||||||
|
|
||||||
/// Views for home and log on pages
|
/// Views for home and log on pages
|
||||||
@ -254,6 +255,39 @@ module Navigation =
|
|||||||
/// Views for journal pages and components
|
/// Views for journal pages and components
|
||||||
module Journal =
|
module Journal =
|
||||||
|
|
||||||
|
/// Display a card for this prayer request
|
||||||
|
let journalCard req =
|
||||||
|
div [ _class "col" ] [
|
||||||
|
div [ _class "card h-100" ] [
|
||||||
|
div [ _class "card-header p-0 text-end"; _roleToolBar ] [
|
||||||
|
button [
|
||||||
|
_class "btn btn-success"
|
||||||
|
_hxPatch $"/request/{RequestId.toString req.requestId}/prayed"
|
||||||
|
_title "Mark as Prayed"
|
||||||
|
] [ icon "done" ]
|
||||||
|
// span
|
||||||
|
// md-button(@click.stop='showEdit()').md-icon-button.md-raised
|
||||||
|
// md-icon edit
|
||||||
|
// md-tooltip(md-direction='top'
|
||||||
|
// md-delay=1000) Edit Request
|
||||||
|
// md-button(@click.stop='showNotes()').md-icon-button.md-raised
|
||||||
|
// md-icon comment
|
||||||
|
// md-tooltip(md-direction='top'
|
||||||
|
// md-delay=1000) Add Notes
|
||||||
|
// md-button(@click.stop='snooze()').md-icon-button.md-raised
|
||||||
|
// md-icon schedule
|
||||||
|
// md-tooltip(md-direction='top'
|
||||||
|
// md-delay=1000) Snooze Request
|
||||||
|
]
|
||||||
|
div [ _class "card-body" ] [
|
||||||
|
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 ]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
/// The journal loading page
|
/// The journal loading page
|
||||||
let journal user = article [ _class "container-fluid mt-3" ] [
|
let journal user = article [ _class "container-fluid mt-3" ] [
|
||||||
h2 [ _class "pb-3" ] [ str user; rawText "’s Prayer Journal" ]
|
h2 [ _class "pb-3" ] [ str user; rawText "’s Prayer Journal" ]
|
||||||
@ -272,7 +306,15 @@ module Journal =
|
|||||||
rawText "You have no requests to be shown; see the “Active” link above for snoozed or "
|
rawText "You have no requests to be shown; see the “Active” link above for snoozed or "
|
||||||
rawText "deferred requests, and the “Answered” link for answered requests"
|
rawText "deferred requests, and the “Answered” link for answered requests"
|
||||||
]
|
]
|
||||||
| false -> p [] [ str "There are requests" ]
|
| false ->
|
||||||
|
items
|
||||||
|
|> List.map journalCard
|
||||||
|
|> section [
|
||||||
|
_class "row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-3"
|
||||||
|
_hxTarget "this"
|
||||||
|
_hxSwap HxSwap.OuterHtml
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Views for request pages and components
|
/// Views for request pages and components
|
||||||
@ -292,30 +334,29 @@ module Request =
|
|||||||
_hxSwap HxSwap.OuterHtml
|
_hxSwap HxSwap.OuterHtml
|
||||||
] [
|
] [
|
||||||
pageLink $"/request/{reqId}/full" [ btnClass; _title "View Full Request" ] [ icon "description" ]
|
pageLink $"/request/{reqId}/full" [ btnClass; _title "View Full Request" ] [ icon "description" ]
|
||||||
if not isAnswered then
|
match isAnswered with
|
||||||
button [ btnClass; _hxGet $"/components/request/{reqId}/edit"; _title "Edit Request" ] [ icon "edit" ]
|
| true -> ()
|
||||||
// TODO: these next two should use hx-patch, targeting replacement of this tr when complete
|
| false ->
|
||||||
if isSnoozed then
|
button [ btnClass; _hxGet $"/components/request/{reqId}/edit"; _title "Edit Request" ] [ icon "edit" ]
|
||||||
pageLink $"/request/{reqId}/cancel-snooze" [ btnClass; _title "Cancel Snooze" ] [ icon "restore" ]
|
match () with
|
||||||
if isPending then
|
| _ when isSnoozed ->
|
||||||
pageLink $"/request/{reqId}/show-now" [ btnClass; _title "Show Now" ] [ icon "restore" ]
|
button [ btnClass; _hxPatch $"/request/{reqId}/cancel-snooze"; _title "Cancel Snooze" ] [ icon "restore" ]
|
||||||
p [ _class "mpj-request-text mb-0" ] [
|
| _ when isPending ->
|
||||||
|
button [ btnClass; _hxPatch $"/request/{reqId}/show"; _title "Show Now" ] [ icon "restore" ]
|
||||||
|
| _ -> ()
|
||||||
|
p [ _class "request-text mb-0" ] [
|
||||||
str req.text
|
str req.text
|
||||||
if isSnoozed || isPending || isAnswered then
|
match isSnoozed || isPending || isAnswered with
|
||||||
br []
|
| true ->
|
||||||
small [ _class "text-muted" ] [
|
br []
|
||||||
em [] [
|
small [ _class "text-muted" ] [
|
||||||
if isSnoozed then
|
match () with
|
||||||
str "Snooze expires "
|
| _ when isSnoozed -> [ str "Snooze expires "; relativeDate req.snoozedUntil ]
|
||||||
relativeDate req.snoozedUntil
|
| _ when isPending -> [ str "Request appears next "; relativeDate req.showAfter ]
|
||||||
if isPending then
|
| _ (* isAnswered *) -> [ str "Answered "; relativeDate req.asOf ]
|
||||||
str "Request appears next "
|
|> em []
|
||||||
relativeDate req.showAfter
|
|
||||||
if isAnswered then
|
|
||||||
str "Answered "
|
|
||||||
relativeDate req.asOf
|
|
||||||
]
|
]
|
||||||
]
|
| false -> ()
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -524,11 +565,10 @@ module Request =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
/// Layout views
|
/// Layout views
|
||||||
module Layout =
|
module Layout =
|
||||||
|
|
||||||
open Giraffe.ViewEngine.Accessibility
|
|
||||||
|
|
||||||
/// The HTML `head` element
|
/// The HTML `head` element
|
||||||
let htmlHead pageTitle =
|
let htmlHead pageTitle =
|
||||||
head [] [
|
head [] [
|
||||||
|
@ -41,6 +41,9 @@ form {
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
.request-text {
|
||||||
|
white-space: pre-line
|
||||||
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
border-top: solid 1px lightgray;
|
border-top: solid 1px lightgray;
|
||||||
|
Loading…
Reference in New Issue
Block a user