Snooze works

This commit is contained in:
Daniel J. Summers 2021-10-22 23:30:15 -04:00
parent f971b2cbf4
commit 3d098bea84
3 changed files with 106 additions and 43 deletions

View File

@ -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

View File

@ -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
]
]
]
] ]

View File

@ -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" ] ]
]