Snooze works
This commit is contained in:
parent
f971b2cbf4
commit
3d098bea84
@ -161,6 +161,12 @@ let journalByUserId userId (db : LiteDatabase) = backgroundTask {
|
|||||||
|> List.ofSeq
|
|> 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 -> Ticks.toLong r.snoozedUntil > Ticks.toLong now)
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieve a request by its ID and user ID (without notes and history)
|
/// Retrieve a request by its ID and user ID (without notes and history)
|
||||||
let tryRequestById reqId userId db = backgroundTask {
|
let tryRequestById reqId userId db = backgroundTask {
|
||||||
let! req = tryFullRequestById reqId userId db
|
let! req = tryFullRequestById reqId userId db
|
||||||
|
@ -95,9 +95,13 @@ module private Helpers =
|
|||||||
let seeOther (url : string) =
|
let seeOther (url : string) =
|
||||||
noResponseCaching >=> setStatusCode 303 >=> setHttpHeader "Location" url
|
noResponseCaching >=> setStatusCode 303 >=> setHttpHeader "Location" url
|
||||||
|
|
||||||
|
/// Convert a date/time to JS-style ticks
|
||||||
|
let toJs (date : DateTime) =
|
||||||
|
date.Subtract(DateTime (1970, 1, 1, 0, 0, 0)).TotalSeconds |> (int64 >> ( * ) 1_000L >> Ticks)
|
||||||
|
|
||||||
/// The "now" time in JavaScript as Ticks
|
/// The "now" time in JavaScript as Ticks
|
||||||
let jsNow () =
|
let jsNow () =
|
||||||
DateTime.UtcNow.Subtract(DateTime (1970, 1, 1, 0, 0, 0)).TotalSeconds |> (int64 >> ( * ) 1_000L >> Ticks)
|
toJs DateTime.UtcNow
|
||||||
|
|
||||||
/// Render a component result
|
/// Render a component result
|
||||||
let renderComponent nodes : HttpHandler =
|
let renderComponent nodes : HttpHandler =
|
||||||
@ -106,13 +110,22 @@ module private Helpers =
|
|||||||
return! ctx.WriteHtmlStringAsync (ViewEngine.RenderView.AsString.htmlNodes nodes)
|
return! ctx.WriteHtmlStringAsync (ViewEngine.RenderView.AsString.htmlNodes nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open Views.Layout
|
||||||
|
|
||||||
/// Create a page rendering context
|
/// Create a page rendering context
|
||||||
let pageContext (ctx : HttpContext) pageTitle content : Views.Layout.PageRenderContext =
|
let pageContext (ctx : HttpContext) pageTitle content = backgroundTask {
|
||||||
{ isAuthenticated = (user >> Option.isSome) ctx
|
let! hasSnoozed = backgroundTask {
|
||||||
hasSnoozed = false
|
match user ctx with
|
||||||
|
| Some _ -> return! Data.hasSnoozed (userId ctx) (jsNow ()) (db ctx)
|
||||||
|
| None -> return false
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
isAuthenticated = (user >> Option.isSome) ctx
|
||||||
|
hasSnoozed = hasSnoozed
|
||||||
currentUrl = ctx.Request.Path.Value
|
currentUrl = ctx.Request.Path.Value
|
||||||
pageTitle = pageTitle
|
pageTitle = pageTitle
|
||||||
content = content
|
content = content
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Composable handler to write a view to the output
|
/// Composable handler to write a view to the output
|
||||||
@ -146,18 +159,19 @@ module private Helpers =
|
|||||||
|
|
||||||
/// Send a partial result if this is not a full page load (does not append no-cache headers)
|
/// Send a partial result if this is not a full page load (does not append no-cache headers)
|
||||||
let partialStatic (pageTitle : string) content : HttpHandler =
|
let partialStatic (pageTitle : string) content : HttpHandler =
|
||||||
fun next ctx ->
|
fun next ctx -> backgroundTask {
|
||||||
let isPartial = ctx.Request.IsHtmx && not ctx.Request.IsHtmxRefresh
|
let isPartial = ctx.Request.IsHtmx && not ctx.Request.IsHtmxRefresh
|
||||||
let view =
|
let! pageCtx = pageContext ctx pageTitle content
|
||||||
pageContext ctx pageTitle content
|
let view = (match isPartial with true -> partial | false -> view) pageCtx
|
||||||
|> match isPartial with true -> Views.Layout.partial | false -> Views.Layout.view
|
return!
|
||||||
(next, ctx)
|
(next, ctx)
|
||||||
||> match user ctx with
|
||> match user ctx with
|
||||||
| Some u ->
|
| Some u ->
|
||||||
match Messages.pop u with
|
match Messages.pop u with
|
||||||
| Some (msg, url) -> setHttpHeader "X-Toast" msg >=> withHxPush url >=> writeView view
|
| Some (msg, url) -> setHttpHeader "X-Toast" msg >=> withHxPush url >=> writeView view
|
||||||
| None -> writeView view
|
| None -> writeView view
|
||||||
| None -> writeView view
|
| None -> writeView view
|
||||||
|
}
|
||||||
|
|
||||||
/// Send an explicitly non-cached result, rendering as a partial if this is not a full page load
|
/// Send an explicitly non-cached result, rendering as a partial if this is not a full page load
|
||||||
let partial pageTitle content =
|
let partial pageTitle content =
|
||||||
@ -201,11 +215,11 @@ module Models =
|
|||||||
recurInterval : string option
|
recurInterval : string option
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The time until which a request should not appear in the journal
|
/// The date until which a request should not appear in the journal
|
||||||
[<CLIMutable; NoComparison; NoEquality>]
|
[<CLIMutable; NoComparison; NoEquality>]
|
||||||
type SnoozeUntil = {
|
type SnoozeUntil = {
|
||||||
/// The time at which the request should reappear
|
/// The date (YYYY-MM-DD) at which the request should reappear
|
||||||
until : int64
|
until : string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -233,12 +247,12 @@ module Components =
|
|||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// GET /components/request/[req-id]/add-notes
|
// GET /components/request/[req-id]/add-notes
|
||||||
let addNotes requestId : HttpHandler =
|
let addNotes requestId : HttpHandler =
|
||||||
requiresAuthentication Error.notAuthorized
|
requiresAuthentication Error.notAuthorized
|
||||||
>=> renderComponent (Views.Journal.notesEdit (RequestId.ofString requestId))
|
>=> renderComponent (Views.Journal.notesEdit (RequestId.ofString requestId))
|
||||||
|
|
||||||
/// GET /components/request/[req-id]/notes
|
// GET /components/request/[req-id]/notes
|
||||||
let notes requestId : HttpHandler =
|
let notes requestId : HttpHandler =
|
||||||
requiresAuthentication Error.notAuthorized
|
requiresAuthentication Error.notAuthorized
|
||||||
>=> fun next ctx -> backgroundTask {
|
>=> fun next ctx -> backgroundTask {
|
||||||
@ -246,6 +260,11 @@ module Components =
|
|||||||
return! renderComponent (Views.Request.notes notes) next ctx
|
return! renderComponent (Views.Request.notes notes) next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET /components/request/[req-id]/snooze
|
||||||
|
let snooze requestId : HttpHandler =
|
||||||
|
requiresAuthentication Error.notAuthorized
|
||||||
|
>=> renderComponent [ RequestId.ofString requestId |> Views.Journal.snooze ]
|
||||||
|
|
||||||
|
|
||||||
/// / URL
|
/// / URL
|
||||||
module Home =
|
module Home =
|
||||||
@ -369,7 +388,7 @@ module Request =
|
|||||||
return! partial "Answered Requests" (Views.Request.answered reqs) next ctx
|
return! partial "Answered Requests" (Views.Request.answered reqs) next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// GET /api/request/[req-id]
|
// GET /api/request/[req-id]
|
||||||
let get requestId : HttpHandler =
|
let get requestId : HttpHandler =
|
||||||
requiresAuthentication Error.notAuthorized
|
requiresAuthentication Error.notAuthorized
|
||||||
>=> fun next ctx -> backgroundTask {
|
>=> fun next ctx -> backgroundTask {
|
||||||
@ -402,7 +421,7 @@ module Request =
|
|||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PATCH /api/request/[req-id]/snooze
|
// PATCH /request/[req-id]/snooze
|
||||||
let snooze requestId : HttpHandler =
|
let snooze requestId : HttpHandler =
|
||||||
requiresAuthentication Error.notAuthorized
|
requiresAuthentication Error.notAuthorized
|
||||||
>=> fun next ctx -> backgroundTask {
|
>=> fun next ctx -> backgroundTask {
|
||||||
@ -411,10 +430,14 @@ module Request =
|
|||||||
let reqId = RequestId.ofString 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.BindFormAsync<Models.SnoozeUntil> ()
|
||||||
do! Data.updateSnoozed reqId usrId (Ticks until.until) db
|
let date = sprintf "%s 00:00:00" until.until |> DateTime.Parse
|
||||||
|
do! Data.updateSnoozed reqId usrId (toJs date) db
|
||||||
do! db.saveChanges ()
|
do! db.saveChanges ()
|
||||||
return! setStatusCode 204 next ctx
|
return!
|
||||||
|
(withSuccessMessage $"Request snoozed until {until.until}"
|
||||||
|
>=> hideModal "snooze"
|
||||||
|
>=> Components.journalItems) next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,6 +555,7 @@ let routes =
|
|||||||
routef "request/%s/add-notes" Components.addNotes
|
routef "request/%s/add-notes" Components.addNotes
|
||||||
routef "request/%s/item" Components.requestItem
|
routef "request/%s/item" Components.requestItem
|
||||||
routef "request/%s/notes" Components.notes
|
routef "request/%s/notes" Components.notes
|
||||||
|
routef "request/%s/snooze" Components.snooze
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
GET_HEAD [ route "/journal" Journal.journal ]
|
GET_HEAD [ route "/journal" Journal.journal ]
|
||||||
@ -554,6 +578,7 @@ let routes =
|
|||||||
routef "/%s/cancel-snooze" Request.cancelSnooze
|
routef "/%s/cancel-snooze" Request.cancelSnooze
|
||||||
routef "/%s/prayed" Request.prayed
|
routef "/%s/prayed" Request.prayed
|
||||||
routef "/%s/show" Request.show
|
routef "/%s/show" Request.show
|
||||||
|
routef "/%s/snooze" Request.snooze
|
||||||
]
|
]
|
||||||
POST [
|
POST [
|
||||||
route "" Request.add
|
route "" Request.add
|
||||||
@ -566,16 +591,4 @@ let routes =
|
|||||||
route "log-on" User.logOn
|
route "log-on" User.logOn
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
subRoute "/api/" [
|
|
||||||
GET [
|
|
||||||
subRoute "request" [
|
|
||||||
routef "/%s" Request.get
|
|
||||||
]
|
|
||||||
]
|
|
||||||
PATCH [
|
|
||||||
subRoute "request" [
|
|
||||||
routef "/%s/snooze" Request.snooze
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
|
@ -26,10 +26,16 @@ let journalCard req =
|
|||||||
_hxSwap HxSwap.InnerHtml
|
_hxSwap HxSwap.InnerHtml
|
||||||
] [ icon "comment" ]
|
] [ icon "comment" ]
|
||||||
spacer
|
spacer
|
||||||
// md-button(@click.stop='snooze()').md-icon-button.md-raised
|
button [
|
||||||
// md-icon schedule
|
_type "button"
|
||||||
// md-tooltip(md-direction='top'
|
_class "btn btn-secondary"
|
||||||
// md-delay=1000) Snooze Request
|
_title "Snooze Request"
|
||||||
|
_data "bs-toggle" "modal"
|
||||||
|
_data "bs-target" "#snoozeModal"
|
||||||
|
_hxGet $"/components/request/{reqId}/snooze"
|
||||||
|
_hxTarget "#snoozeBody"
|
||||||
|
_hxSwap HxSwap.InnerHtml
|
||||||
|
] [ icon "schedule" ]
|
||||||
div [ _class "flex-grow-1" ] []
|
div [ _class "flex-grow-1" ] []
|
||||||
button [
|
button [
|
||||||
_type "button"
|
_type "button"
|
||||||
@ -82,6 +88,28 @@ let journal user = article [ _class "container-fluid mt-3" ] [
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
div [
|
||||||
|
_id "snoozeModal"
|
||||||
|
_class "modal fade"
|
||||||
|
_tabindex "-1"
|
||||||
|
_ariaLabelledBy "snoozeModalLabel"
|
||||||
|
_ariaHidden "true"
|
||||||
|
] [
|
||||||
|
div [ _class "modal-dialog modal-sm" ] [
|
||||||
|
div [ _class "modal-content" ] [
|
||||||
|
div [ _class "modal-header" ] [
|
||||||
|
h5 [ _class "modal-title"; _id "snoozeModalLabel" ] [ str "Snooze Prayer Request" ]
|
||||||
|
button [ _type "button"; _class "btn-close"; _data "bs-dismiss" "modal"; _ariaLabel "Close" ] []
|
||||||
|
]
|
||||||
|
div [ _class "modal-body"; _id "snoozeBody" ] [ ]
|
||||||
|
div [ _class "modal-footer" ] [
|
||||||
|
button [ _type "button"; _id "snoozeDismiss"; _class "btn btn-secondary"; _data "bs-dismiss" "modal" ] [
|
||||||
|
str "Close"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
/// The journal items
|
/// The journal items
|
||||||
@ -96,6 +124,7 @@ let journalItems items =
|
|||||||
items
|
items
|
||||||
|> List.map journalCard
|
|> List.map journalCard
|
||||||
|> section [
|
|> section [
|
||||||
|
_id "journalItems"
|
||||||
_class "row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-3"
|
_class "row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-3"
|
||||||
_hxTarget "this"
|
_hxTarget "this"
|
||||||
_hxSwap HxSwap.OuterHtml
|
_hxSwap HxSwap.OuterHtml
|
||||||
@ -131,3 +160,18 @@ let notesEdit requestId =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/// The snooze edit form
|
||||||
|
let snooze requestId =
|
||||||
|
let today = System.DateTime.Today.ToString "yyyy-MM-dd"
|
||||||
|
form [
|
||||||
|
_hxPatch $"/request/{RequestId.toString requestId}/snooze"
|
||||||
|
_hxTarget "#journalItems"
|
||||||
|
_hxSwap HxSwap.OuterHtml
|
||||||
|
] [
|
||||||
|
div [ _class "form-floating pb-3" ] [
|
||||||
|
input [ _type "date"; _id "until"; _name "until"; _class "form-control"; _min today ]
|
||||||
|
label [ _for "until" ] [ str "Until" ]
|
||||||
|
]
|
||||||
|
p [ _class "text-end mb-0" ] [ button [ _type "submit"; _class "btn btn-primary" ] [ str "Snooze" ] ]
|
||||||
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user