Search, Paging, and "As of" Date (#10)
Issues Fixed: * Added request search capability (#2) * Added pagination to search / inactive request lists (#3) * Added "as of" date display option for requests (#9) * Updated documentation to reflect the new options and their behavior Also Fixed (w/o issue numbers): * Fixed a verbiage error with the confirmation prompts * Split the I18N for the maintain requests page into its own localized view * Modified many "magic strings" in the code to use F# discriminated unions instead (stored as single-character codes in the database)
This commit was merged in pull request #10.
This commit is contained in:
committed by
GitHub
parent
6a6b403216
commit
43b6b6d8e0
@@ -8,6 +8,22 @@ open PrayerTracker.Entities
|
||||
open System.Collections.Generic
|
||||
open System.Linq
|
||||
|
||||
[<AutoOpen>]
|
||||
module private Helpers =
|
||||
|
||||
/// Central place to append sort criteria for prayer request queries
|
||||
let reqSort sort (query : IQueryable<PrayerRequest>) =
|
||||
match sort with
|
||||
| SortByDate ->
|
||||
query.OrderByDescending(fun pr -> pr.updatedDate)
|
||||
.ThenByDescending(fun pr -> pr.enteredDate)
|
||||
.ThenBy(fun pr -> pr.requestor)
|
||||
| SortByRequestor ->
|
||||
query.OrderBy(fun pr -> pr.requestor)
|
||||
.ThenByDescending(fun pr -> pr.updatedDate)
|
||||
.ThenByDescending(fun pr -> pr.enteredDate)
|
||||
|
||||
|
||||
type AppDbContext with
|
||||
|
||||
(*-- DISCONNECTED DATA EXTENSIONS --*)
|
||||
@@ -64,7 +80,7 @@ type AppDbContext with
|
||||
member this.CountMembersForSmallGroup gId =
|
||||
this.Members.CountAsync (fun m -> m.smallGroupId = gId)
|
||||
|
||||
(*-- PRAYER REQUEST EXTENSIONS *)
|
||||
(*-- PRAYER REQUEST EXTENSIONS --*)
|
||||
|
||||
/// Get a prayer request by its Id
|
||||
member this.TryRequestById reqId =
|
||||
@@ -74,32 +90,27 @@ type AppDbContext with
|
||||
}
|
||||
|
||||
/// Get all (or active) requests for a small group as of now or the specified date
|
||||
member this.AllRequestsForSmallGroup (grp : SmallGroup) clock listDate activeOnly : PrayerRequest seq =
|
||||
member this.AllRequestsForSmallGroup (grp : SmallGroup) clock listDate activeOnly pageNbr : PrayerRequest seq =
|
||||
let theDate = match listDate with Some dt -> dt | _ -> grp.localDateNow clock
|
||||
upcast (
|
||||
this.PrayerRequests.AsNoTracking().Where(fun pr -> pr.smallGroupId = grp.smallGroupId)
|
||||
|> function
|
||||
// Filter
|
||||
| query when activeOnly ->
|
||||
let asOf = theDate.AddDays(-(float grp.preferences.daysToExpire)).Date
|
||||
query.Where(fun pr ->
|
||||
(pr.updatedDate > asOf
|
||||
|| pr.doNotExpire
|
||||
|| RequestType.Recurring = pr.requestType
|
||||
|| RequestType.Expecting = pr.requestType)
|
||||
&& not pr.isManuallyExpired)
|
||||
( pr.updatedDate > asOf
|
||||
|| pr.expiration = Manual
|
||||
|| pr.requestType = LongTermRequest
|
||||
|| pr.requestType = Expecting)
|
||||
&& pr.expiration <> Forced)
|
||||
| query -> query
|
||||
|> reqSort grp.preferences.requestSort
|
||||
|> function
|
||||
// Sort
|
||||
| query when grp.preferences.requestSort = "D" ->
|
||||
query.OrderByDescending(fun pr -> pr.updatedDate)
|
||||
.ThenByDescending(fun pr -> pr.enteredDate)
|
||||
.ThenBy(fun pr -> pr.requestor)
|
||||
| query ->
|
||||
query.OrderBy(fun pr -> pr.requestor)
|
||||
.ThenByDescending(fun pr -> pr.updatedDate)
|
||||
.ThenByDescending(fun pr -> pr.enteredDate))
|
||||
|
||||
match activeOnly with
|
||||
| true -> query.Skip 0
|
||||
| false -> query.Skip((pageNbr - 1) * grp.preferences.pageSize).Take grp.preferences.pageSize)
|
||||
|
||||
/// Count prayer requests for the given small group Id
|
||||
member this.CountRequestsBySmallGroup gId =
|
||||
this.PrayerRequests.CountAsync (fun pr -> pr.smallGroupId = gId)
|
||||
@@ -108,6 +119,21 @@ type AppDbContext with
|
||||
member this.CountRequestsByChurch cId =
|
||||
this.PrayerRequests.CountAsync (fun pr -> pr.smallGroup.churchId = cId)
|
||||
|
||||
/// Get all (or active) requests for a small group as of now or the specified date
|
||||
member this.SearchRequestsForSmallGroup (grp : SmallGroup) (searchTerm : string) pageNbr : PrayerRequest seq =
|
||||
let pgSz = grp.preferences.pageSize
|
||||
let skip = (pageNbr - 1) * pgSz
|
||||
let sql =
|
||||
""" SELECT * FROM pt."PrayerRequest" WHERE "SmallGroupId" = {0} AND "Text" ILIKE {1}
|
||||
UNION
|
||||
SELECT * FROM pt."PrayerRequest" WHERE "SmallGroupId" = {0} AND COALESCE("Requestor", '') ILIKE {1}"""
|
||||
|> RawSqlString
|
||||
let like = sprintf "%%%s%%"
|
||||
upcast (
|
||||
this.PrayerRequests.FromSql(sql, grp.smallGroupId, like searchTerm).AsNoTracking ()
|
||||
|> reqSort grp.preferences.requestSort
|
||||
|> function query -> (query.Skip skip).Take pgSz)
|
||||
|
||||
(*-- SMALL GROUP EXTENSIONS --*)
|
||||
|
||||
/// Find a small group by its Id
|
||||
|
||||
@@ -6,37 +6,204 @@ open NodaTime
|
||||
open System
|
||||
open System.Collections.Generic
|
||||
|
||||
(*-- CONSTANTS --*)
|
||||
|
||||
/// Constants to use for the e-mail type parameter
|
||||
[<RequireQualifiedAccess>]
|
||||
module EmailType =
|
||||
/// HTML e-mail
|
||||
[<Literal>]
|
||||
let Html = "Html"
|
||||
/// Plain Text e-mail
|
||||
[<Literal>]
|
||||
let PlainText = "PlainText"
|
||||
/// E-mail with the list as an attached PDF
|
||||
[<Literal>]
|
||||
let AttachedPdf = "AttachedPdf"
|
||||
|
||||
/// These values match those in the RequestType document store
|
||||
[<RequireQualifiedAccess>]
|
||||
module RequestType =
|
||||
/// Current Requests (follow expiration rules)
|
||||
let Current = "Current"
|
||||
/// Long-Term / Recurring Requests (do not automatically expire)
|
||||
let Recurring = "Recurring"
|
||||
/// Praise Reports (follow expiration rules)
|
||||
let Praise = "Praise"
|
||||
/// Expectant Mothers (do not automatically expire)
|
||||
let Expecting = "Expecting"
|
||||
/// Announcements (follow expiration rules)
|
||||
let Announcement = "Announcement"
|
||||
|
||||
(*-- SUPPORT TYPES --*)
|
||||
|
||||
/// How as-of dates should (or should not) be displayed with requests
|
||||
type AsOfDateDisplay =
|
||||
/// No as-of date should be displayed
|
||||
| NoDisplay
|
||||
/// The as-of date should be displayed in the culture's short date format
|
||||
| ShortDate
|
||||
/// The as-of date should be displayed in the culture's long date format
|
||||
| LongDate
|
||||
with
|
||||
/// Convert to a DU case from a single-character string
|
||||
static member fromCode code =
|
||||
match code with
|
||||
| "N" -> NoDisplay
|
||||
| "S" -> ShortDate
|
||||
| "L" -> LongDate
|
||||
| _ -> invalidArg "code" (sprintf "Unknown code %s" code)
|
||||
/// Convert this DU case to a single-character string
|
||||
member this.code =
|
||||
match this with
|
||||
| NoDisplay -> "N"
|
||||
| ShortDate -> "S"
|
||||
| LongDate -> "L"
|
||||
|
||||
|
||||
/// Acceptable e-mail formats
|
||||
type EmailFormat =
|
||||
/// HTML e-mail
|
||||
| HtmlFormat
|
||||
/// Plain-text e-mail
|
||||
| PlainTextFormat
|
||||
with
|
||||
/// Convert to a DU case from a single-character string
|
||||
static member fromCode code =
|
||||
match code with
|
||||
| "H" -> HtmlFormat
|
||||
| "P" -> PlainTextFormat
|
||||
| _ -> invalidArg "code" (sprintf "Unknown code %s" code)
|
||||
/// Convert this DU case to a single-character string
|
||||
member this.code =
|
||||
match this with
|
||||
| HtmlFormat -> "H"
|
||||
| PlainTextFormat -> "P"
|
||||
|
||||
|
||||
/// Expiration for requests
|
||||
type Expiration =
|
||||
/// Follow the rules for normal expiration
|
||||
| Automatic
|
||||
/// Do not expire via rules
|
||||
| Manual
|
||||
/// Force immediate expiration
|
||||
| Forced
|
||||
with
|
||||
/// Convert to a DU case from a single-character string
|
||||
static member fromCode code =
|
||||
match code with
|
||||
| "A" -> Automatic
|
||||
| "M" -> Manual
|
||||
| "F" -> Forced
|
||||
| _ -> invalidArg "code" (sprintf "Unknown code %s" code)
|
||||
/// Convert this DU case to a single-character string
|
||||
member this.code =
|
||||
match this with
|
||||
| Automatic -> "A"
|
||||
| Manual -> "M"
|
||||
| Forced -> "F"
|
||||
|
||||
|
||||
/// Types of prayer requests
|
||||
type PrayerRequestType =
|
||||
/// Current requests
|
||||
| CurrentRequest
|
||||
/// Long-term/ongoing request
|
||||
| LongTermRequest
|
||||
/// Expectant couples
|
||||
| Expecting
|
||||
/// Praise reports
|
||||
| PraiseReport
|
||||
/// Announcements
|
||||
| Announcement
|
||||
with
|
||||
/// Convert to a DU case from a single-character string
|
||||
static member fromCode code =
|
||||
match code with
|
||||
| "C" -> CurrentRequest
|
||||
| "L" -> LongTermRequest
|
||||
| "E" -> Expecting
|
||||
| "P" -> PraiseReport
|
||||
| "A" -> Announcement
|
||||
| _ -> invalidArg "code" (sprintf "Unknown code %s" code)
|
||||
/// Convert this DU case to a single-character string
|
||||
member this.code =
|
||||
match this with
|
||||
| CurrentRequest -> "C"
|
||||
| LongTermRequest -> "L"
|
||||
| Expecting -> "E"
|
||||
| PraiseReport -> "P"
|
||||
| Announcement -> "A"
|
||||
|
||||
|
||||
/// How requests should be sorted
|
||||
type RequestSort =
|
||||
/// Sort by date, then by requestor/subject
|
||||
| SortByDate
|
||||
/// Sort by requestor/subject, then by date
|
||||
| SortByRequestor
|
||||
with
|
||||
/// Convert to a DU case from a single-character string
|
||||
static member fromCode code =
|
||||
match code with
|
||||
| "D" -> SortByDate
|
||||
| "R" -> SortByRequestor
|
||||
| _ -> invalidArg "code" (sprintf "Unknown code %s" code)
|
||||
/// Convert this DU case to a single-character string
|
||||
member this.code =
|
||||
match this with
|
||||
| SortByDate -> "D"
|
||||
| SortByRequestor -> "R"
|
||||
|
||||
|
||||
module Converters =
|
||||
open Microsoft.EntityFrameworkCore.Storage.ValueConversion
|
||||
open Microsoft.FSharp.Linq.RuntimeHelpers
|
||||
open System.Linq.Expressions
|
||||
|
||||
let private asOfFromDU =
|
||||
<@ Func<AsOfDateDisplay, string>(fun (x : AsOfDateDisplay) -> x.code) @>
|
||||
|> LeafExpressionConverter.QuotationToExpression
|
||||
|> unbox<Expression<Func<AsOfDateDisplay, string>>>
|
||||
|
||||
let private asOfToDU =
|
||||
<@ Func<string, AsOfDateDisplay>(AsOfDateDisplay.fromCode) @>
|
||||
|> LeafExpressionConverter.QuotationToExpression
|
||||
|> unbox<Expression<Func<string, AsOfDateDisplay>>>
|
||||
|
||||
let private emailFromDU =
|
||||
<@ Func<EmailFormat, string>(fun (x : EmailFormat) -> x.code) @>
|
||||
|> LeafExpressionConverter.QuotationToExpression
|
||||
|> unbox<Expression<Func<EmailFormat, string>>>
|
||||
|
||||
let private emailToDU =
|
||||
<@ Func<string, EmailFormat>(EmailFormat.fromCode) @>
|
||||
|> LeafExpressionConverter.QuotationToExpression
|
||||
|> unbox<Expression<Func<string, EmailFormat>>>
|
||||
|
||||
let private expFromDU =
|
||||
<@ Func<Expiration, string>(fun (x : Expiration) -> x.code) @>
|
||||
|> LeafExpressionConverter.QuotationToExpression
|
||||
|> unbox<Expression<Func<Expiration, string>>>
|
||||
|
||||
let private expToDU =
|
||||
<@ Func<string, Expiration>(Expiration.fromCode) @>
|
||||
|> LeafExpressionConverter.QuotationToExpression
|
||||
|> unbox<Expression<Func<string, Expiration>>>
|
||||
|
||||
let private sortFromDU =
|
||||
<@ Func<RequestSort, string>(fun (x : RequestSort) -> x.code) @>
|
||||
|> LeafExpressionConverter.QuotationToExpression
|
||||
|> unbox<Expression<Func<RequestSort, string>>>
|
||||
|
||||
let private sortToDU =
|
||||
<@ Func<string, RequestSort>(RequestSort.fromCode) @>
|
||||
|> LeafExpressionConverter.QuotationToExpression
|
||||
|> unbox<Expression<Func<string, RequestSort>>>
|
||||
|
||||
let private typFromDU =
|
||||
<@ Func<PrayerRequestType, string>(fun (x : PrayerRequestType) -> x.code) @>
|
||||
|> LeafExpressionConverter.QuotationToExpression
|
||||
|> unbox<Expression<Func<PrayerRequestType, string>>>
|
||||
|
||||
let private typToDU =
|
||||
<@ Func<string, PrayerRequestType>(PrayerRequestType.fromCode) @>
|
||||
|> LeafExpressionConverter.QuotationToExpression
|
||||
|> unbox<Expression<Func<string, PrayerRequestType>>>
|
||||
|
||||
/// Conversion between a string and an AsOfDateDisplay DU value
|
||||
type AsOfDateDisplayConverter () =
|
||||
inherit ValueConverter<AsOfDateDisplay, string> (asOfFromDU, asOfToDU)
|
||||
|
||||
/// Conversion between a string and an EmailFormat DU value
|
||||
type EmailFormatConverter () =
|
||||
inherit ValueConverter<EmailFormat, string> (emailFromDU, emailToDU)
|
||||
|
||||
/// Conversion between a string and an Expiration DU value
|
||||
type ExpirationConverter () =
|
||||
inherit ValueConverter<Expiration, string> (expFromDU, expToDU)
|
||||
|
||||
/// Conversion between a string and an AsOfDateDisplay DU value
|
||||
type PrayerRequestTypeConverter () =
|
||||
inherit ValueConverter<PrayerRequestType, string> (typFromDU, typToDU)
|
||||
|
||||
/// Conversion between a string and a RequestSort DU value
|
||||
type RequestSortConverter () =
|
||||
inherit ValueConverter<RequestSort, string> (sortFromDU, sortToDU)
|
||||
|
||||
|
||||
/// Statistics for churches
|
||||
[<NoComparison; NoEquality>]
|
||||
type ChurchStats =
|
||||
@@ -145,17 +312,21 @@ and [<CLIMutable; NoComparison; NoEquality>] ListPreferences =
|
||||
/// The font size for the text on the prayer request list
|
||||
textFontSize : int
|
||||
/// The order in which the prayer requests are sorted
|
||||
requestSort : string
|
||||
requestSort : RequestSort
|
||||
/// The password used for "small group login" (view-only request list)
|
||||
groupPassword : string
|
||||
/// The default e-mail type for this class
|
||||
defaultEmailType : string
|
||||
defaultEmailType : EmailFormat
|
||||
/// Whether this class makes its request list public
|
||||
isPublic : bool
|
||||
/// The time zone which this class uses (use tzdata names)
|
||||
timeZoneId : TimeZoneId
|
||||
/// The time zone information
|
||||
timeZone : TimeZone
|
||||
/// The number of requests displayed per page
|
||||
pageSize : int
|
||||
/// How the as-of date should be automatically displayed
|
||||
asOfDateDisplay : AsOfDateDisplay
|
||||
}
|
||||
with
|
||||
/// A set of preferences with their default values
|
||||
@@ -171,12 +342,14 @@ and [<CLIMutable; NoComparison; NoEquality>] ListPreferences =
|
||||
lineColor = "navy"
|
||||
headingFontSize = 16
|
||||
textFontSize = 12
|
||||
requestSort = "D"
|
||||
requestSort = SortByDate
|
||||
groupPassword = ""
|
||||
defaultEmailType = EmailType.Html
|
||||
defaultEmailType = HtmlFormat
|
||||
isPublic = false
|
||||
timeZoneId = "America/Denver"
|
||||
timeZone = TimeZone.empty
|
||||
pageSize = 100
|
||||
asOfDateDisplay = NoDisplay
|
||||
}
|
||||
/// Configure EF for this entity
|
||||
static member internal configureEF (mb : ModelBuilder) =
|
||||
@@ -188,80 +361,97 @@ and [<CLIMutable; NoComparison; NoEquality>] ListPreferences =
|
||||
m.Property(fun e -> e.daysToKeepNew)
|
||||
.HasColumnName("DaysToKeepNew")
|
||||
.IsRequired()
|
||||
.HasDefaultValue(7)
|
||||
.HasDefaultValue 7
|
||||
|> ignore
|
||||
m.Property(fun e -> e.daysToExpire)
|
||||
.HasColumnName("DaysToExpire")
|
||||
.IsRequired()
|
||||
.HasDefaultValue(14)
|
||||
.HasDefaultValue 14
|
||||
|> ignore
|
||||
m.Property(fun e -> e.longTermUpdateWeeks)
|
||||
.HasColumnName("LongTermUpdateWeeks")
|
||||
.IsRequired()
|
||||
.HasDefaultValue(4)
|
||||
.HasDefaultValue 4
|
||||
|> ignore
|
||||
m.Property(fun e -> e.emailFromName)
|
||||
.HasColumnName("EmailFromName")
|
||||
.IsRequired()
|
||||
.HasDefaultValue("PrayerTracker")
|
||||
.HasDefaultValue "PrayerTracker"
|
||||
|> ignore
|
||||
m.Property(fun e -> e.emailFromAddress)
|
||||
.HasColumnName("EmailFromAddress")
|
||||
.IsRequired()
|
||||
.HasDefaultValue("prayer@djs-consulting.com")
|
||||
.HasDefaultValue "prayer@djs-consulting.com"
|
||||
|> ignore
|
||||
m.Property(fun e -> e.listFonts)
|
||||
.HasColumnName("ListFonts")
|
||||
.IsRequired()
|
||||
.HasDefaultValue("Century Gothic,Tahoma,Luxi Sans,sans-serif")
|
||||
.HasDefaultValue "Century Gothic,Tahoma,Luxi Sans,sans-serif"
|
||||
|> ignore
|
||||
m.Property(fun e -> e.headingColor)
|
||||
.HasColumnName("HeadingColor")
|
||||
.IsRequired()
|
||||
.HasDefaultValue("maroon")
|
||||
.HasDefaultValue "maroon"
|
||||
|> ignore
|
||||
m.Property(fun e -> e.lineColor)
|
||||
.HasColumnName("LineColor")
|
||||
.IsRequired()
|
||||
.HasDefaultValue("navy")
|
||||
.HasDefaultValue "navy"
|
||||
|> ignore
|
||||
m.Property(fun e -> e.headingFontSize)
|
||||
.HasColumnName("HeadingFontSize")
|
||||
.IsRequired()
|
||||
.HasDefaultValue(16)
|
||||
.HasDefaultValue 16
|
||||
|> ignore
|
||||
m.Property(fun e -> e.textFontSize)
|
||||
.HasColumnName("TextFontSize")
|
||||
.IsRequired()
|
||||
.HasDefaultValue(12)
|
||||
.HasDefaultValue 12
|
||||
|> ignore
|
||||
m.Property(fun e -> e.requestSort)
|
||||
.HasColumnName("RequestSort")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1)
|
||||
.HasDefaultValue("D")
|
||||
.HasDefaultValue SortByDate
|
||||
|> ignore
|
||||
m.Property(fun e -> e.groupPassword)
|
||||
.HasColumnName("GroupPassword")
|
||||
.IsRequired()
|
||||
.HasDefaultValue("")
|
||||
.HasDefaultValue ""
|
||||
|> ignore
|
||||
m.Property(fun e -> e.defaultEmailType)
|
||||
.HasColumnName("DefaultEmailType")
|
||||
.IsRequired()
|
||||
.HasDefaultValue(EmailType.Html)
|
||||
.HasDefaultValue HtmlFormat
|
||||
|> ignore
|
||||
m.Property(fun e -> e.isPublic)
|
||||
.HasColumnName("IsPublic")
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false)
|
||||
.HasDefaultValue false
|
||||
|> ignore
|
||||
m.Property(fun e -> e.timeZoneId)
|
||||
.HasColumnName("TimeZoneId")
|
||||
.IsRequired()
|
||||
.HasDefaultValue("America/Denver")
|
||||
.HasDefaultValue "America/Denver"
|
||||
|> ignore
|
||||
m.Property(fun e -> e.pageSize)
|
||||
.HasColumnName("PageSize")
|
||||
.IsRequired()
|
||||
.HasDefaultValue 100
|
||||
|> ignore
|
||||
m.Property(fun e -> e.asOfDateDisplay)
|
||||
.HasColumnName("AsOfDateDisplay")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1)
|
||||
.HasDefaultValue NoDisplay
|
||||
|> ignore)
|
||||
|> ignore
|
||||
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty("requestSort")
|
||||
.SetValueConverter(Converters.RequestSortConverter ())
|
||||
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty("defaultEmailType")
|
||||
.SetValueConverter(Converters.EmailFormatConverter ())
|
||||
mb.Model.FindEntityType(typeof<ListPreferences>).FindProperty("asOfDateDisplay")
|
||||
.SetValueConverter(Converters.AsOfDateDisplayConverter ())
|
||||
|
||||
|
||||
/// A member of a small group
|
||||
@@ -275,7 +465,7 @@ and [<CLIMutable; NoComparison; NoEquality>] Member =
|
||||
/// The e-mail address for the member
|
||||
email : string
|
||||
/// The type of e-mail preferred by this member (see <see cref="EmailTypes"/> constants)
|
||||
format : string option
|
||||
format : string option // TODO - do I need a custom formatter for this?
|
||||
/// The small group to which this member belongs
|
||||
smallGroup : SmallGroup
|
||||
}
|
||||
@@ -306,64 +496,62 @@ and [<CLIMutable; NoComparison; NoEquality>] Member =
|
||||
/// This represents a single prayer request
|
||||
and [<CLIMutable; NoComparison; NoEquality>] PrayerRequest =
|
||||
{ /// The Id of this request
|
||||
prayerRequestId : PrayerRequestId
|
||||
prayerRequestId : PrayerRequestId
|
||||
/// The type of the request
|
||||
requestType : string
|
||||
requestType : PrayerRequestType
|
||||
/// The user who entered the request
|
||||
userId : UserId
|
||||
userId : UserId
|
||||
/// The small group to which this request belongs
|
||||
smallGroupId : SmallGroupId
|
||||
smallGroupId : SmallGroupId
|
||||
/// The date/time on which this request was entered
|
||||
enteredDate : DateTime
|
||||
enteredDate : DateTime
|
||||
/// The date/time this request was last updated
|
||||
updatedDate : DateTime
|
||||
updatedDate : DateTime
|
||||
/// The name of the requestor or subject, or title of announcement
|
||||
requestor : string option
|
||||
requestor : string option
|
||||
/// The text of the request
|
||||
text : string
|
||||
/// Whether this request is exempt from standard expiration rules
|
||||
doNotExpire : bool
|
||||
text : string
|
||||
/// Whether the chaplain should be notified for this request
|
||||
notifyChaplain : bool
|
||||
/// Whether this request has been expired manually
|
||||
isManuallyExpired : bool
|
||||
notifyChaplain : bool
|
||||
/// The user who entered this request
|
||||
user : User
|
||||
user : User
|
||||
/// The small group to which this request belongs
|
||||
smallGroup : SmallGroup
|
||||
smallGroup : SmallGroup
|
||||
/// Is this request expired?
|
||||
expiration : Expiration
|
||||
}
|
||||
with
|
||||
/// An empty request
|
||||
static member empty =
|
||||
{ prayerRequestId = Guid.Empty
|
||||
requestType = RequestType.Current
|
||||
userId = Guid.Empty
|
||||
smallGroupId = Guid.Empty
|
||||
enteredDate = DateTime.MinValue
|
||||
updatedDate = DateTime.MinValue
|
||||
requestor = None
|
||||
text = ""
|
||||
doNotExpire = false
|
||||
notifyChaplain = false
|
||||
isManuallyExpired = false
|
||||
user = User.empty
|
||||
smallGroup = SmallGroup.empty
|
||||
{ prayerRequestId = Guid.Empty
|
||||
requestType = CurrentRequest
|
||||
userId = Guid.Empty
|
||||
smallGroupId = Guid.Empty
|
||||
enteredDate = DateTime.MinValue
|
||||
updatedDate = DateTime.MinValue
|
||||
requestor = None
|
||||
text = ""
|
||||
notifyChaplain = false
|
||||
user = User.empty
|
||||
smallGroup = SmallGroup.empty
|
||||
expiration = Automatic
|
||||
}
|
||||
/// Is this request expired?
|
||||
member this.isExpired (curr : DateTime) expDays =
|
||||
match this.isManuallyExpired with
|
||||
| true -> true // Manual expiration
|
||||
| false ->
|
||||
let nonExpiringTypes = [ RequestType.Recurring; RequestType.Expecting ]
|
||||
match this.doNotExpire || List.contains this.requestType nonExpiringTypes with
|
||||
| true -> false // No expiration
|
||||
| false -> curr.AddDays(-(float expDays)) > this.updatedDate // Automatic expiration
|
||||
match this.expiration with
|
||||
| Forced -> true
|
||||
| Manual -> false
|
||||
| Automatic ->
|
||||
match this.requestType with
|
||||
| LongTermRequest
|
||||
| Expecting -> false
|
||||
| _ -> curr.AddDays(-(float expDays)) > this.updatedDate // Automatic expiration
|
||||
|
||||
/// Is an update required for this long-term request?
|
||||
member this.updateRequired curr expDays updWeeks =
|
||||
match this.isExpired curr expDays with
|
||||
| true -> false
|
||||
| _ -> curr.AddDays(-(float (updWeeks * 7))) > this.updatedDate
|
||||
| false -> curr.AddDays(-(float (updWeeks * 7))) > this.updatedDate
|
||||
|
||||
/// Configure EF for this entity
|
||||
static member internal configureEF (mb : ModelBuilder) =
|
||||
@@ -378,12 +566,15 @@ and [<CLIMutable; NoComparison; NoEquality>] PrayerRequest =
|
||||
m.Property(fun e -> e.updatedDate).HasColumnName "UpdatedDate" |> ignore
|
||||
m.Property(fun e -> e.requestor).HasColumnName "Requestor" |> ignore
|
||||
m.Property(fun e -> e.text).HasColumnName("Text").IsRequired() |> ignore
|
||||
m.Property(fun e -> e.doNotExpire).HasColumnName "DoNotExpire" |> ignore
|
||||
m.Property(fun e -> e.notifyChaplain).HasColumnName "NotifyChaplain" |> ignore
|
||||
m.Property(fun e -> e.isManuallyExpired).HasColumnName "IsManuallyExpired" |> ignore)
|
||||
m.Property(fun e -> e.expiration).HasColumnName "Expiration" |> ignore)
|
||||
|> ignore
|
||||
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty("requestType")
|
||||
.SetValueConverter(Converters.PrayerRequestTypeConverter ())
|
||||
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty("requestor")
|
||||
.SetValueConverter(OptionConverter<string> ())
|
||||
mb.Model.FindEntityType(typeof<PrayerRequest>).FindProperty("expiration")
|
||||
.SetValueConverter(Converters.ExpirationConverter ())
|
||||
|
||||
|
||||
/// This represents a small group (Sunday School class, Bible study group, etc.)
|
||||
|
||||
@@ -37,6 +37,8 @@ type ListPreferencesTable =
|
||||
requestSort : OperationBuilder<AddColumnOperation>
|
||||
textFontSize : OperationBuilder<AddColumnOperation>
|
||||
timeZoneId : OperationBuilder<AddColumnOperation>
|
||||
pageSize : OperationBuilder<AddColumnOperation>
|
||||
asOfDateDisplay : OperationBuilder<AddColumnOperation>
|
||||
}
|
||||
|
||||
type MemberTable =
|
||||
@@ -48,17 +50,16 @@ type MemberTable =
|
||||
}
|
||||
|
||||
type PrayerRequestTable =
|
||||
{ prayerRequestId : OperationBuilder<AddColumnOperation>
|
||||
doNotExpire : OperationBuilder<AddColumnOperation>
|
||||
enteredDate : OperationBuilder<AddColumnOperation>
|
||||
isManuallyExpired : OperationBuilder<AddColumnOperation>
|
||||
notifyChaplain : OperationBuilder<AddColumnOperation>
|
||||
requestType : OperationBuilder<AddColumnOperation>
|
||||
requestor : OperationBuilder<AddColumnOperation>
|
||||
smallGroupId : OperationBuilder<AddColumnOperation>
|
||||
text : OperationBuilder<AddColumnOperation>
|
||||
updatedDate : OperationBuilder<AddColumnOperation>
|
||||
userId : OperationBuilder<AddColumnOperation>
|
||||
{ prayerRequestId : OperationBuilder<AddColumnOperation>
|
||||
enteredDate : OperationBuilder<AddColumnOperation>
|
||||
expiration : OperationBuilder<AddColumnOperation>
|
||||
notifyChaplain : OperationBuilder<AddColumnOperation>
|
||||
requestType : OperationBuilder<AddColumnOperation>
|
||||
requestor : OperationBuilder<AddColumnOperation>
|
||||
smallGroupId : OperationBuilder<AddColumnOperation>
|
||||
text : OperationBuilder<AddColumnOperation>
|
||||
updatedDate : OperationBuilder<AddColumnOperation>
|
||||
userId : OperationBuilder<AddColumnOperation>
|
||||
}
|
||||
|
||||
type SmallGroupTable =
|
||||
@@ -102,12 +103,12 @@ type InitialDatabase () =
|
||||
schema = "pt",
|
||||
columns =
|
||||
(fun table ->
|
||||
{ churchId = table.Column<Guid> (name = "ChurchId", nullable = false)
|
||||
city = table.Column<string> (name = "City", nullable = false)
|
||||
{ churchId = table.Column<Guid> (name = "ChurchId", nullable = false)
|
||||
city = table.Column<string> (name = "City", nullable = false)
|
||||
hasInterface = table.Column<bool> (name = "HasVirtualPrayerRoomInterface", nullable = false)
|
||||
interfaceAddress = table.Column<string> (name = "InterfaceAddress", nullable = true)
|
||||
name = table.Column<string> (name = "Name", nullable = false)
|
||||
st = table.Column<string> (name = "ST", maxLength = Nullable<int> 2, nullable = false)
|
||||
name = table.Column<string> (name = "Name", nullable = false)
|
||||
st = table.Column<string> (name = "ST", nullable = false, maxLength = Nullable<int> 2)
|
||||
}),
|
||||
constraints =
|
||||
fun table ->
|
||||
@@ -119,10 +120,10 @@ type InitialDatabase () =
|
||||
schema = "pt",
|
||||
columns =
|
||||
(fun table ->
|
||||
{ timeZoneId = table.Column<string> (name = "TimeZoneId", nullable = false)
|
||||
{ timeZoneId = table.Column<string> (name = "TimeZoneId", nullable = false)
|
||||
description = table.Column<string> (name = "Description", nullable = false)
|
||||
isActive = table.Column<bool> (name = "IsActive", nullable = false)
|
||||
sortOrder = table.Column<int> (name = "SortOrder", nullable = false)
|
||||
isActive = table.Column<bool> (name = "IsActive", nullable = false)
|
||||
sortOrder = table.Column<int> (name = "SortOrder", nullable = false)
|
||||
}),
|
||||
constraints =
|
||||
fun table ->
|
||||
@@ -134,13 +135,13 @@ type InitialDatabase () =
|
||||
schema = "pt",
|
||||
columns =
|
||||
(fun table ->
|
||||
{ userId = table.Column<Guid> (name = "UserId", nullable = false)
|
||||
emailAddress = table.Column<string> (name = "EmailAddress", nullable = false)
|
||||
firstName = table.Column<string> (name = "FirstName", nullable = false)
|
||||
{ userId = table.Column<Guid> (name = "UserId", nullable = false)
|
||||
emailAddress = table.Column<string> (name = "EmailAddress", nullable = false)
|
||||
firstName = table.Column<string> (name = "FirstName", nullable = false)
|
||||
isAdmin = table.Column<bool> (name = "IsSystemAdmin", nullable = false)
|
||||
lastName = table.Column<string> (name = "LastName", nullable = false)
|
||||
passwordHash = table.Column<string> (name = "PasswordHash", nullable = false)
|
||||
salt = table.Column<Guid> (name = "Salt", nullable = true)
|
||||
lastName = table.Column<string> (name = "LastName", nullable = false)
|
||||
passwordHash = table.Column<string> (name = "PasswordHash", nullable = false)
|
||||
salt = table.Column<Guid> (name = "Salt", nullable = true)
|
||||
}),
|
||||
constraints =
|
||||
fun table ->
|
||||
@@ -153,8 +154,8 @@ type InitialDatabase () =
|
||||
columns =
|
||||
(fun table ->
|
||||
{ smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false)
|
||||
churchId = table.Column<Guid> (name = "ChurchId", nullable = false)
|
||||
name = table.Column<string> (name = "Name", nullable = false)
|
||||
churchId = table.Column<Guid> (name = "ChurchId", nullable = false)
|
||||
name = table.Column<string> (name = "Name", nullable = false)
|
||||
}),
|
||||
constraints =
|
||||
fun table ->
|
||||
@@ -174,22 +175,24 @@ type InitialDatabase () =
|
||||
schema = "pt",
|
||||
columns =
|
||||
(fun table ->
|
||||
{ smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false)
|
||||
daysToExpire = table.Column<int> (name = "DaysToExpire", nullable = false, defaultValue = 14)
|
||||
daysToKeepNew = table.Column<int> (name = "DaysToKeepNew", nullable = false, defaultValue = 7)
|
||||
defaultEmailType = table.Column<string> (name = "DefaultEmailType", nullable = false, defaultValue = "Html")
|
||||
emailFromAddress = table.Column<string> (name = "EmailFromAddress", nullable = false, defaultValue = "prayer@djs-consulting.com")
|
||||
emailFromName = table.Column<string> (name = "EmailFromName", nullable = false, defaultValue = "PrayerTracker")
|
||||
groupPassword = table.Column<string> (name = "GroupPassword", nullable = false, defaultValue = "")
|
||||
headingColor = table.Column<string> (name = "HeadingColor", nullable = false, defaultValue = "maroon")
|
||||
headingFontSize = table.Column<int> (name = "HeadingFontSize", nullable = false, defaultValue = 16)
|
||||
isPublic = table.Column<bool> (name = "IsPublic", nullable = false, defaultValue = false)
|
||||
lineColor = table.Column<string> (name = "LineColor", nullable = false, defaultValue = "navy")
|
||||
listFonts = table.Column<string> (name = "ListFonts", nullable = false, defaultValue = "Century Gothic,Tahoma,Luxi Sans,sans-serif")
|
||||
{ smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false)
|
||||
daysToExpire = table.Column<int> (name = "DaysToExpire", nullable = false, defaultValue = 14)
|
||||
daysToKeepNew = table.Column<int> (name = "DaysToKeepNew", nullable = false, defaultValue = 7)
|
||||
defaultEmailType = table.Column<string> (name = "DefaultEmailType", nullable = false, defaultValue = "Html")
|
||||
emailFromAddress = table.Column<string> (name = "EmailFromAddress", nullable = false, defaultValue = "prayer@djs-consulting.com")
|
||||
emailFromName = table.Column<string> (name = "EmailFromName", nullable = false, defaultValue = "PrayerTracker")
|
||||
groupPassword = table.Column<string> (name = "GroupPassword", nullable = false, defaultValue = "")
|
||||
headingColor = table.Column<string> (name = "HeadingColor", nullable = false, defaultValue = "maroon")
|
||||
headingFontSize = table.Column<int> (name = "HeadingFontSize", nullable = false, defaultValue = 16)
|
||||
isPublic = table.Column<bool> (name = "IsPublic", nullable = false, defaultValue = false)
|
||||
lineColor = table.Column<string> (name = "LineColor", nullable = false, defaultValue = "navy")
|
||||
listFonts = table.Column<string> (name = "ListFonts", nullable = false, defaultValue = "Century Gothic,Tahoma,Luxi Sans,sans-serif")
|
||||
longTermUpdateWeeks = table.Column<int> (name = "LongTermUpdateWeeks", nullable = false, defaultValue = 4)
|
||||
requestSort = table.Column<string> (name = "RequestSort", maxLength = Nullable<int> 1, nullable = false, defaultValue = "D")
|
||||
textFontSize = table.Column<int> (name = "TextFontSize", nullable = false, defaultValue = 12)
|
||||
timeZoneId = table.Column<string> (name = "TimeZoneId", nullable = false, defaultValue = "America/Denver")
|
||||
requestSort = table.Column<string> (name = "RequestSort", nullable = false, defaultValue = "D", maxLength = Nullable<int> 1)
|
||||
textFontSize = table.Column<int> (name = "TextFontSize", nullable = false, defaultValue = 12)
|
||||
timeZoneId = table.Column<string> (name = "TimeZoneId", nullable = false, defaultValue = "America/Denver")
|
||||
pageSize = table.Column<int> (name = "PageSize", nullable = false, defaultValue = 100)
|
||||
asOfDateDisplay = table.Column<string> (name = "AsOfDateDisplay", nullable = false, defaultValue = "N", maxLength = Nullable<int> 1)
|
||||
}),
|
||||
constraints =
|
||||
fun table ->
|
||||
@@ -217,10 +220,10 @@ type InitialDatabase () =
|
||||
schema = "pt",
|
||||
columns =
|
||||
(fun table ->
|
||||
{ memberId = table.Column<Guid> (name = "MemberId", nullable = false)
|
||||
email = table.Column<string> (name = "Email", nullable = false)
|
||||
format = table.Column<string> (name = "Format", nullable = true)
|
||||
memberName = table.Column<string> (name = "MemberName", nullable = false)
|
||||
{ memberId = table.Column<Guid> (name = "MemberId", nullable = false)
|
||||
email = table.Column<string> (name = "Email", nullable = false)
|
||||
format = table.Column<string> (name = "Format", nullable = true)
|
||||
memberName = table.Column<string> (name = "MemberName", nullable = false)
|
||||
smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false)
|
||||
}),
|
||||
constraints =
|
||||
@@ -242,16 +245,15 @@ type InitialDatabase () =
|
||||
columns =
|
||||
(fun table ->
|
||||
{ prayerRequestId = table.Column<Guid> (name = "PrayerRequestId", nullable = false)
|
||||
doNotExpire = table.Column<bool> (name = "DoNotExpire", nullable = false)
|
||||
enteredDate = table.Column<DateTime> (name = "EnteredDate", nullable = false)
|
||||
isManuallyExpired = table.Column<bool> (name = "IsManuallyExpired", nullable = false)
|
||||
notifyChaplain = table.Column<bool> (name = "NotifyChaplain", nullable = false)
|
||||
requestType = table.Column<string> (name = "RequestType", nullable = false)
|
||||
requestor = table.Column<string> (name = "Requestor", nullable = true)
|
||||
smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false)
|
||||
text = table.Column<string> (name = "Text", nullable = false)
|
||||
updatedDate = table.Column<DateTime> (name = "UpdatedDate", nullable = false)
|
||||
userId = table.Column<Guid> (name = "UserId", nullable = false)
|
||||
expiration = table.Column<bool> (name = "Expiration", nullable = false)
|
||||
enteredDate = table.Column<DateTime> (name = "EnteredDate", nullable = false)
|
||||
notifyChaplain = table.Column<bool> (name = "NotifyChaplain", nullable = false)
|
||||
requestType = table.Column<string> (name = "RequestType", nullable = false)
|
||||
requestor = table.Column<string> (name = "Requestor", nullable = true)
|
||||
smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false)
|
||||
text = table.Column<string> (name = "Text", nullable = false)
|
||||
updatedDate = table.Column<DateTime> (name = "UpdatedDate", nullable = false)
|
||||
userId = table.Column<Guid> (name = "UserId", nullable = false)
|
||||
}),
|
||||
constraints =
|
||||
fun table ->
|
||||
@@ -279,7 +281,7 @@ type InitialDatabase () =
|
||||
schema = "pt",
|
||||
columns =
|
||||
(fun table ->
|
||||
{ userId = table.Column<Guid> (name = "UserId", nullable = false)
|
||||
{ userId = table.Column<Guid> (name = "UserId", nullable = false)
|
||||
smallGroupId = table.Column<Guid> (name = "SmallGroupId", nullable = false)
|
||||
}),
|
||||
constraints =
|
||||
@@ -347,7 +349,7 @@ type InitialDatabase () =
|
||||
b.Property<Guid>("smallGroupId") |> ignore
|
||||
b.Property<int>("daysToExpire").ValueGeneratedOnAdd().HasDefaultValue(14) |> ignore
|
||||
b.Property<int>("daysToKeepNew").ValueGeneratedOnAdd().HasDefaultValue(7) |> ignore
|
||||
b.Property<string>("defaultEmailType").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("Html") |> ignore
|
||||
b.Property<string>("defaultEmailType").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("H") |> ignore
|
||||
b.Property<string>("emailFromAddress").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("prayer@djs-consulting.com") |> ignore
|
||||
b.Property<string>("emailFromName").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("PrayerTracker") |> ignore
|
||||
b.Property<string>("groupPassword").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("") |> ignore
|
||||
@@ -357,9 +359,11 @@ type InitialDatabase () =
|
||||
b.Property<string>("lineColor").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("navy") |> ignore
|
||||
b.Property<string>("listFonts").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("Century Gothic,Tahoma,Luxi Sans,sans-serif") |> ignore
|
||||
b.Property<int>("longTermUpdateWeeks").ValueGeneratedOnAdd().HasDefaultValue(4) |> ignore
|
||||
b.Property<string>("requestSort").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("D").HasMaxLength(1) |> ignore
|
||||
b.Property<string>("requestSort").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("D").HasMaxLength(1) |> ignore
|
||||
b.Property<int>("textFontSize").ValueGeneratedOnAdd().HasDefaultValue(12) |> ignore
|
||||
b.Property<string>("timeZoneId").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("America/Denver") |> ignore
|
||||
b.Property<int>("pageSize").IsRequired().ValueGeneratedOnAdd().HasDefaultValue(100) |> ignore
|
||||
b.Property<string>("asOfDateDisplay").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("N").HasMaxLength(1) |> ignore
|
||||
b.HasKey("smallGroupId") |> ignore
|
||||
b.HasIndex("timeZoneId") |> ignore
|
||||
b.ToTable("ListPreference") |> ignore)
|
||||
@@ -382,11 +386,10 @@ type InitialDatabase () =
|
||||
typeof<PrayerRequest>,
|
||||
fun b ->
|
||||
b.Property<Guid>("prayerRequestId").ValueGeneratedOnAdd() |> ignore
|
||||
b.Property<bool>("doNotExpire") |> ignore
|
||||
b.Property<DateTime>("enteredDate") |> ignore
|
||||
b.Property<bool>("isManuallyExpired") |> ignore
|
||||
b.Property<DateTime>("enteredDate").IsRequired() |> ignore
|
||||
b.Property<string>("expiration").IsRequired().HasMaxLength 1 |> ignore
|
||||
b.Property<bool>("notifyChaplain") |> ignore
|
||||
b.Property<string>("requestType").IsRequired() |> ignore
|
||||
b.Property<string>("requestType").IsRequired().HasMaxLength 1 |> ignore
|
||||
b.Property<string>("requestor") |> ignore
|
||||
b.Property<Guid>("smallGroupId") |> ignore
|
||||
b.Property<string>("text").IsRequired() |> ignore
|
||||
|
||||
@@ -36,7 +36,7 @@ type AppDbContextModelSnapshot () =
|
||||
b.Property<Guid>("smallGroupId") |> ignore
|
||||
b.Property<int>("daysToExpire").ValueGeneratedOnAdd().HasDefaultValue(14) |> ignore
|
||||
b.Property<int>("daysToKeepNew").ValueGeneratedOnAdd().HasDefaultValue(7) |> ignore
|
||||
b.Property<string>("defaultEmailType").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("Html") |> ignore
|
||||
b.Property<string>("defaultEmailType").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("H").HasMaxLength(1) |> ignore
|
||||
b.Property<string>("emailFromAddress").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("prayer@djs-consulting.com") |> ignore
|
||||
b.Property<string>("emailFromName").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("PrayerTracker") |> ignore
|
||||
b.Property<string>("groupPassword").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("") |> ignore
|
||||
@@ -46,9 +46,11 @@ type AppDbContextModelSnapshot () =
|
||||
b.Property<string>("lineColor").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("navy") |> ignore
|
||||
b.Property<string>("listFonts").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("Century Gothic,Tahoma,Luxi Sans,sans-serif") |> ignore
|
||||
b.Property<int>("longTermUpdateWeeks").ValueGeneratedOnAdd().HasDefaultValue(4) |> ignore
|
||||
b.Property<string>("requestSort").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("D").HasMaxLength(1) |> ignore
|
||||
b.Property<string>("requestSort").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("D").HasMaxLength(1) |> ignore
|
||||
b.Property<int>("textFontSize").ValueGeneratedOnAdd().HasDefaultValue(12) |> ignore
|
||||
b.Property<string>("timeZoneId").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("America/Denver") |> ignore
|
||||
b.Property<int>("pageSize").IsRequired().ValueGeneratedOnAdd().HasDefaultValue(100) |> ignore
|
||||
b.Property<string>("asOfDateDisplay").IsRequired().ValueGeneratedOnAdd().HasDefaultValue("N").HasMaxLength(1) |> ignore
|
||||
b.HasKey("smallGroupId") |> ignore
|
||||
b.HasIndex("timeZoneId") |> ignore
|
||||
b.ToTable("ListPreference") |> ignore)
|
||||
@@ -71,11 +73,10 @@ type AppDbContextModelSnapshot () =
|
||||
typeof<PrayerRequest>,
|
||||
fun b ->
|
||||
b.Property<Guid>("prayerRequestId").ValueGeneratedOnAdd() |> ignore
|
||||
b.Property<bool>("doNotExpire") |> ignore
|
||||
b.Property<DateTime>("enteredDate") |> ignore
|
||||
b.Property<bool>("isManuallyExpired") |> ignore
|
||||
b.Property<string>("expiration").IsRequired().HasMaxLength(1) |> ignore
|
||||
b.Property<bool>("notifyChaplain") |> ignore
|
||||
b.Property<string>("requestType").IsRequired() |> ignore
|
||||
b.Property<string>("requestType").IsRequired().HasMaxLength(1) |> ignore
|
||||
b.Property<string>("requestor") |> ignore
|
||||
b.Property<Guid>("smallGroupId") |> ignore
|
||||
b.Property<string>("text").IsRequired() |> ignore
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AssemblyVersion>7.0.0.0</AssemblyVersion>
|
||||
<FileVersion>7.0.0.0</FileVersion>
|
||||
<AssemblyVersion>7.3.0.0</AssemblyVersion>
|
||||
<FileVersion>7.3.0.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,10 +1,36 @@
|
||||
module PrayerTracker.Entities.EntitiesTests
|
||||
|
||||
open Expecto
|
||||
open System
|
||||
open System.Linq
|
||||
open NodaTime.Testing
|
||||
open NodaTime
|
||||
open System
|
||||
|
||||
[<Tests>]
|
||||
let asOfDateDisplayTests =
|
||||
testList "AsOfDateDisplay" [
|
||||
test "NoDisplay code is correct" {
|
||||
Expect.equal NoDisplay.code "N" "The code for NoDisplay should have been \"N\""
|
||||
}
|
||||
test "ShortDate code is correct" {
|
||||
Expect.equal ShortDate.code "S" "The code for ShortDate should have been \"S\""
|
||||
}
|
||||
test "LongDate code is correct" {
|
||||
Expect.equal LongDate.code "L" "The code for LongDate should have been \"N\""
|
||||
}
|
||||
test "fromCode N should return NoDisplay" {
|
||||
Expect.equal (AsOfDateDisplay.fromCode "N") NoDisplay "\"N\" should have been converted to NoDisplay"
|
||||
}
|
||||
test "fromCode S should return ShortDate" {
|
||||
Expect.equal (AsOfDateDisplay.fromCode "S") ShortDate "\"S\" should have been converted to ShortDate"
|
||||
}
|
||||
test "fromCode L should return LongDate" {
|
||||
Expect.equal (AsOfDateDisplay.fromCode "L") LongDate "\"L\" should have been converted to LongDate"
|
||||
}
|
||||
test "fromCode X should raise" {
|
||||
Expect.throws (fun () -> AsOfDateDisplay.fromCode "X" |> ignore)
|
||||
"An unknown code should have raised an exception"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let churchTests =
|
||||
@@ -22,6 +48,52 @@ let churchTests =
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let emailFormatTests =
|
||||
testList "EmailFormat" [
|
||||
test "HtmlFormat code is correct" {
|
||||
Expect.equal HtmlFormat.code "H" "The code for HtmlFormat should have been \"H\""
|
||||
}
|
||||
test "PlainTextFormat code is correct" {
|
||||
Expect.equal PlainTextFormat.code "P" "The code for PlainTextFormat should have been \"P\""
|
||||
}
|
||||
test "fromCode H should return HtmlFormat" {
|
||||
Expect.equal (EmailFormat.fromCode "H") HtmlFormat "\"H\" should have been converted to HtmlFormat"
|
||||
}
|
||||
test "fromCode P should return ShortDate" {
|
||||
Expect.equal (EmailFormat.fromCode "P") PlainTextFormat "\"P\" should have been converted to PlainTextFormat"
|
||||
}
|
||||
test "fromCode Z should raise" {
|
||||
Expect.throws (fun () -> EmailFormat.fromCode "Z" |> ignore) "An unknown code should have raised an exception"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let expirationTests =
|
||||
testList "Expiration" [
|
||||
test "Automatic code is correct" {
|
||||
Expect.equal Automatic.code "A" "The code for Automatic should have been \"A\""
|
||||
}
|
||||
test "Manual code is correct" {
|
||||
Expect.equal Manual.code "M" "The code for Manual should have been \"M\""
|
||||
}
|
||||
test "Forced code is correct" {
|
||||
Expect.equal Forced.code "F" "The code for Forced should have been \"F\""
|
||||
}
|
||||
test "fromCode A should return Automatic" {
|
||||
Expect.equal (Expiration.fromCode "A") Automatic "\"A\" should have been converted to Automatic"
|
||||
}
|
||||
test "fromCode M should return Manual" {
|
||||
Expect.equal (Expiration.fromCode "M") Manual "\"M\" should have been converted to Manual"
|
||||
}
|
||||
test "fromCode F should return Forced" {
|
||||
Expect.equal (Expiration.fromCode "F") Forced "\"F\" should have been converted to Forced"
|
||||
}
|
||||
test "fromCode V should raise" {
|
||||
Expect.throws (fun () -> Expiration.fromCode "V" |> ignore) "An unknown code should have raised an exception"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let listPreferencesTests =
|
||||
testList "ListPreferences" [
|
||||
@@ -39,12 +111,14 @@ let listPreferencesTests =
|
||||
Expect.equal mt.lineColor "navy" "The default heding line color should have been navy"
|
||||
Expect.equal mt.headingFontSize 16 "The default heading font size should have been 16"
|
||||
Expect.equal mt.textFontSize 12 "The default text font size should have been 12"
|
||||
Expect.equal mt.requestSort "D" "The default request sort should have been D (date)"
|
||||
Expect.equal mt.requestSort SortByDate "The default request sort should have been by date"
|
||||
Expect.equal mt.groupPassword "" "The default group password should have been blank"
|
||||
Expect.equal mt.defaultEmailType EmailType.Html "The default e-mail type should have been HTML"
|
||||
Expect.equal mt.defaultEmailType HtmlFormat "The default e-mail type should have been HTML"
|
||||
Expect.isFalse mt.isPublic "The isPublic flag should not have been set"
|
||||
Expect.equal mt.timeZoneId "America/Denver" "The default time zone should have been America/Denver"
|
||||
Expect.equal mt.timeZone.timeZoneId "" "The default preferences should have included an empty time zone"
|
||||
Expect.equal mt.pageSize 100 "The default page size should have been 100"
|
||||
Expect.equal mt.asOfDateDisplay NoDisplay "The as-of date display should have been No Display"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -68,34 +142,33 @@ let prayerRequestTests =
|
||||
test "empty is as expected" {
|
||||
let mt = PrayerRequest.empty
|
||||
Expect.equal mt.prayerRequestId Guid.Empty "The request ID should have been an empty GUID"
|
||||
Expect.equal mt.requestType RequestType.Current "The request type should have been Current"
|
||||
Expect.equal mt.requestType CurrentRequest "The request type should have been Current"
|
||||
Expect.equal mt.userId Guid.Empty "The user ID should have been an empty GUID"
|
||||
Expect.equal mt.smallGroupId Guid.Empty "The small group ID should have been an empty GUID"
|
||||
Expect.equal mt.enteredDate DateTime.MinValue "The entered date should have been the minimum"
|
||||
Expect.equal mt.updatedDate DateTime.MinValue "The updated date should have been the minimum"
|
||||
Expect.isNone mt.requestor "The requestor should not exist"
|
||||
Expect.equal mt.text "" "The request text should have been blank"
|
||||
Expect.isFalse mt.doNotExpire "The do not expire flag should not have been set"
|
||||
Expect.isFalse mt.notifyChaplain "The notify chaplain flag should not have been set"
|
||||
Expect.isFalse mt.isManuallyExpired "The is manually expired flag should not have been set"
|
||||
Expect.equal mt.expiration Automatic "The expiration should have been Automatic"
|
||||
Expect.equal mt.user.userId Guid.Empty "The user should have been an empty one"
|
||||
Expect.equal mt.smallGroup.smallGroupId Guid.Empty "The small group should have been an empty one"
|
||||
}
|
||||
test "isExpired always returns false for expecting requests" {
|
||||
let req = { PrayerRequest.empty with requestType = RequestType.Expecting }
|
||||
let req = { PrayerRequest.empty with requestType = Expecting }
|
||||
Expect.isFalse (req.isExpired DateTime.Now 0) "An expecting request should never be considered expired"
|
||||
}
|
||||
test "isExpired always returns false for never-expired requests" {
|
||||
let req = { PrayerRequest.empty with updatedDate = DateTime.Now.AddMonths -1; doNotExpire = true }
|
||||
test "isExpired always returns false for manually-expired requests" {
|
||||
let req = { PrayerRequest.empty with updatedDate = DateTime.Now.AddMonths -1; expiration = Manual }
|
||||
Expect.isFalse (req.isExpired DateTime.Now 4) "A never-expired request should never be considered expired"
|
||||
}
|
||||
test "isExpired always returns false for recurring requests" {
|
||||
let req = { PrayerRequest.empty with requestType = RequestType.Recurring }
|
||||
test "isExpired always returns false for long term/recurring requests" {
|
||||
let req = { PrayerRequest.empty with requestType = LongTermRequest }
|
||||
Expect.isFalse (req.isExpired DateTime.Now 0) "A recurring/long-term request should never be considered expired"
|
||||
}
|
||||
test "isExpired always returns true for manually expired requests" {
|
||||
let req = { PrayerRequest.empty with updatedDate = DateTime.Now; isManuallyExpired = true }
|
||||
Expect.isTrue (req.isExpired DateTime.Now 5) "A manually expired request should always be considered expired"
|
||||
test "isExpired always returns true for force-expired requests" {
|
||||
let req = { PrayerRequest.empty with updatedDate = DateTime.Now; expiration = Forced }
|
||||
Expect.isTrue (req.isExpired DateTime.Now 5) "A force-expired request should always be considered expired"
|
||||
}
|
||||
test "isExpired returns false for non-expired requests" {
|
||||
let req = { PrayerRequest.empty with updatedDate = DateTime.Now.AddDays -5. }
|
||||
@@ -106,13 +179,13 @@ let prayerRequestTests =
|
||||
Expect.isTrue (req.isExpired DateTime.Now 7) "A request updated 8 days ago should be considered expired"
|
||||
}
|
||||
test "updateRequired returns false for expired requests" {
|
||||
let req = { PrayerRequest.empty with isManuallyExpired = true }
|
||||
let req = { PrayerRequest.empty with expiration = Forced }
|
||||
Expect.isFalse (req.updateRequired DateTime.Now 7 4) "An expired request should not require an update"
|
||||
}
|
||||
test "updateRequired returns false when an update is not required for an active request" {
|
||||
let req =
|
||||
{ PrayerRequest.empty with
|
||||
requestType = RequestType.Recurring
|
||||
requestType = LongTermRequest
|
||||
updatedDate = DateTime.Now.AddDays -14.
|
||||
}
|
||||
Expect.isFalse (req.updateRequired DateTime.Now 7 4)
|
||||
@@ -121,7 +194,7 @@ let prayerRequestTests =
|
||||
test "updateRequired returns true when an update is required for an active request" {
|
||||
let req =
|
||||
{ PrayerRequest.empty with
|
||||
requestType = RequestType.Recurring
|
||||
requestType = LongTermRequest
|
||||
updatedDate = DateTime.Now.AddDays -34.
|
||||
}
|
||||
Expect.isTrue (req.updateRequired DateTime.Now 7 4)
|
||||
@@ -129,6 +202,67 @@ let prayerRequestTests =
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let prayerRequestTypeTests =
|
||||
testList "PrayerRequestType" [
|
||||
test "CurrentRequest code is correct" {
|
||||
Expect.equal CurrentRequest.code "C" "The code for CurrentRequest should have been \"C\""
|
||||
}
|
||||
test "LongTermRequest code is correct" {
|
||||
Expect.equal LongTermRequest.code "L" "The code for LongTermRequest should have been \"L\""
|
||||
}
|
||||
test "PraiseReport code is correct" {
|
||||
Expect.equal PraiseReport.code "P" "The code for PraiseReport should have been \"P\""
|
||||
}
|
||||
test "Expecting code is correct" {
|
||||
Expect.equal Expecting.code "E" "The code for Expecting should have been \"E\""
|
||||
}
|
||||
test "Announcement code is correct" {
|
||||
Expect.equal Announcement.code "A" "The code for Announcement should have been \"A\""
|
||||
}
|
||||
test "fromCode C should return CurrentRequest" {
|
||||
Expect.equal (PrayerRequestType.fromCode "C") CurrentRequest
|
||||
"\"C\" should have been converted to CurrentRequest"
|
||||
}
|
||||
test "fromCode L should return LongTermRequest" {
|
||||
Expect.equal (PrayerRequestType.fromCode "L") LongTermRequest
|
||||
"\"L\" should have been converted to LongTermRequest"
|
||||
}
|
||||
test "fromCode P should return PraiseReport" {
|
||||
Expect.equal (PrayerRequestType.fromCode "P") PraiseReport "\"P\" should have been converted to PraiseReport"
|
||||
}
|
||||
test "fromCode E should return Expecting" {
|
||||
Expect.equal (PrayerRequestType.fromCode "E") Expecting "\"E\" should have been converted to Expecting"
|
||||
}
|
||||
test "fromCode A should return Announcement" {
|
||||
Expect.equal (PrayerRequestType.fromCode "A") Announcement "\"A\" should have been converted to Announcement"
|
||||
}
|
||||
test "fromCode R should raise" {
|
||||
Expect.throws (fun () -> PrayerRequestType.fromCode "R" |> ignore)
|
||||
"An unknown code should have raised an exception"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let requestSortTests =
|
||||
testList "RequestSort" [
|
||||
test "SortByDate code is correct" {
|
||||
Expect.equal SortByDate.code "D" "The code for SortByDate should have been \"D\""
|
||||
}
|
||||
test "SortByRequestor code is correct" {
|
||||
Expect.equal SortByRequestor.code "R" "The code for SortByRequestor should have been \"R\""
|
||||
}
|
||||
test "fromCode D should return SortByDate" {
|
||||
Expect.equal (RequestSort.fromCode "D") SortByDate "\"D\" should have been converted to SortByDate"
|
||||
}
|
||||
test "fromCode R should return SortByRequestor" {
|
||||
Expect.equal (RequestSort.fromCode "R") SortByRequestor "\"R\" should have been converted to SortByRequestor"
|
||||
}
|
||||
test "fromCode Q should raise" {
|
||||
Expect.throws (fun () -> RequestSort.fromCode "Q" |> ignore) "An unknown code should have raised an exception"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let smallGroupTests =
|
||||
testList "SmallGroup" [
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<AssemblyVersion>7.3.0.0</AssemblyVersion>
|
||||
<FileVersion>7.3.0.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -68,6 +68,21 @@ let htmlToPlainTextTests =
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let makeUrlTests =
|
||||
testList "makeUrl" [
|
||||
test "returns the URL when there are no parameters" {
|
||||
Expect.equal (makeUrl "/test" []) "/test" "The URL should not have had any query string parameters added"
|
||||
}
|
||||
test "returns the URL with one query string parameter" {
|
||||
Expect.equal (makeUrl "/test" [ "unit", "true" ]) "/test?unit=true" "The URL was not constructed properly"
|
||||
}
|
||||
test "returns the URL with multiple encoded query string parameters" {
|
||||
let url = makeUrl "/test" [ "space", "a space"; "turkey", "=" ]
|
||||
Expect.equal url "/test?space=a+space&turkey=%3D" "The URL was not constructed properly"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let sndAsStringTests =
|
||||
testList "sndAsString" [
|
||||
|
||||
@@ -15,19 +15,31 @@ let countAll _ = true
|
||||
|
||||
module ReferenceListTests =
|
||||
|
||||
[<Tests>]
|
||||
let asOfDateListTests =
|
||||
testList "ReferenceList.asOfDateList" [
|
||||
test "has all three options listed" {
|
||||
let asOf = ReferenceList.asOfDateList _s
|
||||
Expect.hasCountOf asOf 3u countAll "There should have been 3 as-of choices returned"
|
||||
Expect.exists asOf (fun (x, _) -> x = NoDisplay.code) "The option for no display was not found"
|
||||
Expect.exists asOf (fun (x, _) -> x = ShortDate.code) "The option for a short date was not found"
|
||||
Expect.exists asOf (fun (x, _) -> x = LongDate.code) "The option for a full date was not found"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let emailTypeListTests =
|
||||
testList "ReferenceList.emailTypeList" [
|
||||
test "includes default type" {
|
||||
let typs = ReferenceList.emailTypeList EmailType.Html _s
|
||||
let typs = ReferenceList.emailTypeList HtmlFormat _s
|
||||
Expect.hasCountOf typs 3u countAll "There should have been 3 e-mail type options returned"
|
||||
let top = Seq.head typs
|
||||
Expect.equal (fst top) "" "The default option should have been blank"
|
||||
Expect.equal (snd top).Value "Group Default (HTML Format)" "The default option label was incorrect"
|
||||
let nxt = typs |> Seq.skip 1 |> Seq.head
|
||||
Expect.equal (fst nxt) EmailType.Html "The 2nd option should have been HTML"
|
||||
Expect.equal (fst nxt) HtmlFormat.code "The 2nd option should have been HTML"
|
||||
let lst = typs |> Seq.last
|
||||
Expect.equal (fst lst) EmailType.PlainText "The 3rd option should have been plain text"
|
||||
Expect.equal (fst lst) PlainTextFormat.code "The 3rd option should have been plain text"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -37,15 +49,15 @@ module ReferenceListTests =
|
||||
test "excludes immediate expiration if not required" {
|
||||
let exps = ReferenceList.expirationList _s false
|
||||
Expect.hasCountOf exps 2u countAll "There should have been 2 expiration types returned"
|
||||
Expect.exists exps (fun exp -> fst exp = "N") "The option for normal expiration was not found"
|
||||
Expect.exists exps (fun exp -> fst exp = "Y") "The option for \"never expire\" was not found"
|
||||
Expect.exists exps (fun (exp, _) -> exp = Automatic.code) "The option for automatic expiration was not found"
|
||||
Expect.exists exps (fun (exp, _) -> exp = Manual.code) "The option for manual expiration was not found"
|
||||
}
|
||||
test "includes immediate expiration if required" {
|
||||
let exps = ReferenceList.expirationList _s true
|
||||
Expect.hasCountOf exps 3u countAll "There should have been 3 expiration types returned"
|
||||
Expect.exists exps (fun exp -> fst exp = "N") "The option for normal expiration was not found"
|
||||
Expect.exists exps (fun exp -> fst exp = "Y") "The option for \"never expire\" was not found"
|
||||
Expect.exists exps (fun exp -> fst exp = "X") "The option for \"expire immediately\" was not found"
|
||||
Expect.exists exps (fun (exp, _) -> exp = Automatic.code) "The option for automatic expiration was not found"
|
||||
Expect.exists exps (fun (exp, _) -> exp = Manual.code) "The option for manual expiration was not found"
|
||||
Expect.exists exps (fun (exp, _) -> exp = Forced.code) "The option for immediate expiration was not found"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -57,14 +69,12 @@ module ReferenceListTests =
|
||||
yield! testFixture withList [
|
||||
yield "returns 5 types",
|
||||
fun typs -> Expect.hasCountOf typs 5u countAll "There should have been 5 request types returned"
|
||||
yield! [ RequestType.Current; RequestType.Recurring; RequestType.Praise; RequestType.Expecting;
|
||||
RequestType.Announcement
|
||||
]
|
||||
|> List.map (fun typ ->
|
||||
sprintf "contains \"%s\"" typ,
|
||||
fun typs ->
|
||||
Expect.isSome (typs |> List.tryFind (fun x -> fst x = typ))
|
||||
(sprintf "The \"%s\" option was not found" typ))
|
||||
yield! [ CurrentRequest; LongTermRequest; PraiseReport; Expecting; Announcement ]
|
||||
|> List.map (fun typ ->
|
||||
sprintf "contains \"%O\"" typ,
|
||||
fun typs ->
|
||||
Expect.isSome (typs |> List.tryFind (fun x -> fst x = typ))
|
||||
(sprintf "The \"%O\" option was not found" typ))
|
||||
]
|
||||
]
|
||||
|
||||
@@ -220,8 +230,8 @@ let editMemberTests =
|
||||
Expect.equal edit.emailType "" "The e-mail type should have been blank for group default"
|
||||
}
|
||||
test "fromMember populates with specific format" {
|
||||
let edit = EditMember.fromMember { Member.empty with format = Some EmailType.Html }
|
||||
Expect.equal edit.emailType EmailType.Html "The e-mail type was not filled correctly"
|
||||
let edit = EditMember.fromMember { Member.empty with format = Some HtmlFormat.code }
|
||||
Expect.equal edit.emailType HtmlFormat.code "The e-mail type was not filled correctly"
|
||||
}
|
||||
test "empty is as expected" {
|
||||
let edit = EditMember.empty
|
||||
@@ -248,10 +258,10 @@ let editPreferencesTests =
|
||||
Expect.equal edit.expireDays prefs.daysToExpire "The expiration days were not filled correctly"
|
||||
Expect.equal edit.daysToKeepNew prefs.daysToKeepNew "The days to keep new were not filled correctly"
|
||||
Expect.equal edit.longTermUpdateWeeks prefs.longTermUpdateWeeks "The weeks for update were not filled correctly"
|
||||
Expect.equal edit.requestSort prefs.requestSort "The request sort was not filled correctly"
|
||||
Expect.equal edit.requestSort prefs.requestSort.code "The request sort was not filled correctly"
|
||||
Expect.equal edit.emailFromName prefs.emailFromName "The e-mail from name was not filled correctly"
|
||||
Expect.equal edit.emailFromAddress prefs.emailFromAddress "The e-mail from address was not filled correctly"
|
||||
Expect.equal edit.defaultEmailType prefs.defaultEmailType "The default e-mail type was not filled correctly"
|
||||
Expect.equal edit.defaultEmailType prefs.defaultEmailType.code "The default e-mail type was not filled correctly"
|
||||
Expect.equal edit.headingLineType "Name" "The heading line color type was not derived correctly"
|
||||
Expect.equal edit.headingLineColor prefs.lineColor "The heading line color was not filled correctly"
|
||||
Expect.equal edit.headingTextType "Name" "The heading text color type was not derived correctly"
|
||||
@@ -291,39 +301,29 @@ let editRequestTests =
|
||||
test "empty is as expected" {
|
||||
let mt = EditRequest.empty
|
||||
Expect.equal mt.requestId Guid.Empty "The request ID should be an empty GUID"
|
||||
Expect.equal mt.requestType "" "The request type should have been blank"
|
||||
Expect.equal mt.requestType CurrentRequest.code "The request type should have been \"Current\""
|
||||
Expect.isNone mt.enteredDate "The entered date should have been None"
|
||||
Expect.isNone mt.skipDateUpdate "The \"skip date update\" flag should have been None"
|
||||
Expect.isNone mt.requestor "The requestor should have been None"
|
||||
Expect.equal mt.expiration "N" "The expiration should have been \"N\""
|
||||
Expect.equal mt.expiration Automatic.code "The expiration should have been \"A\" (Automatic)"
|
||||
Expect.equal mt.text "" "The text should have been blank"
|
||||
}
|
||||
test "fromRequest succeeds when a request has the do-not-expire flag set" {
|
||||
test "fromRequest succeeds" {
|
||||
let req =
|
||||
{ PrayerRequest.empty with
|
||||
prayerRequestId = Guid.NewGuid ()
|
||||
requestType = RequestType.Current
|
||||
requestType = CurrentRequest
|
||||
requestor = Some "Me"
|
||||
doNotExpire = true
|
||||
expiration = Manual
|
||||
text = "the text"
|
||||
}
|
||||
let edit = EditRequest.fromRequest req
|
||||
Expect.equal edit.requestId req.prayerRequestId "The request ID was not filled correctly"
|
||||
Expect.equal edit.requestType req.requestType "The request type was not filled correctly"
|
||||
Expect.equal edit.requestType req.requestType.code "The request type was not filled correctly"
|
||||
Expect.equal edit.requestor req.requestor "The requestor was not filled correctly"
|
||||
Expect.equal edit.expiration "Y" "The expiration should have been \"Y\" since the do-not-expire flag was set"
|
||||
Expect.equal edit.expiration Manual.code "The expiration was not filled correctly"
|
||||
Expect.equal edit.text req.text "The text was not filled correctly"
|
||||
}
|
||||
test "fromRequest succeeds when a request has the do-not-expire flag unset" {
|
||||
let req =
|
||||
{ PrayerRequest.empty with
|
||||
requestor = None
|
||||
doNotExpire = false
|
||||
}
|
||||
let edit = EditRequest.fromRequest req
|
||||
Expect.equal edit.requestor req.requestor "The requestor was not filled correctly"
|
||||
Expect.equal edit.expiration "N" "The expiration should have been \"N\" since the do-not-expire flag was not set"
|
||||
}
|
||||
test "isNew works for a new request" {
|
||||
Expect.isTrue (EditRequest.empty.isNew ()) "An empty GUID should be flagged as a new request"
|
||||
}
|
||||
@@ -438,25 +438,38 @@ let groupLogOnTests =
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let maintainRequestsTests =
|
||||
testList "MaintainRequests" [
|
||||
test "empty is as expected" {
|
||||
let mt = MaintainRequests.empty
|
||||
Expect.isEmpty mt.requests "The requests for the model should have been empty"
|
||||
Expect.equal mt.smallGroup.smallGroupId Guid.Empty "The small group should have been an empty one"
|
||||
Expect.isNone mt.onlyActive "The only active flag should have been None"
|
||||
Expect.isNone mt.searchTerm "The search term should have been None"
|
||||
Expect.isNone mt.pageNbr "The page number should have been None"
|
||||
}
|
||||
]
|
||||
|
||||
[<Tests>]
|
||||
let requestListTests =
|
||||
testList "RequestList" [
|
||||
let withRequestList f () =
|
||||
{ requests = [
|
||||
{ PrayerRequest.empty with
|
||||
requestType = RequestType.Current
|
||||
requestType = CurrentRequest
|
||||
requestor = Some "Zeb"
|
||||
text = "zyx"
|
||||
updatedDate = DateTime.Today
|
||||
}
|
||||
{ PrayerRequest.empty with
|
||||
requestType = RequestType.Current
|
||||
requestType = CurrentRequest
|
||||
requestor = Some "Aaron"
|
||||
text = "abc"
|
||||
updatedDate = DateTime.Today - TimeSpan.FromDays 9.
|
||||
}
|
||||
{ PrayerRequest.empty with
|
||||
requestType = RequestType.Praise
|
||||
requestType = PraiseReport
|
||||
text = "nmo"
|
||||
updatedDate = DateTime.Today
|
||||
}
|
||||
@@ -469,7 +482,7 @@ let requestListTests =
|
||||
}
|
||||
|> f
|
||||
yield! testFixture withRequestList [
|
||||
"asHtml succeeds without header",
|
||||
"asHtml succeeds without header or as-of date",
|
||||
fun reqList ->
|
||||
let htmlList = { reqList with listGroup = { reqList.listGroup with name = "Test HTML Group" } }
|
||||
let html = htmlList.asHtml _s
|
||||
@@ -525,7 +538,37 @@ let requestListTests =
|
||||
Expect.stringContains html lstHeading "Expected HTML for the list heading not found"
|
||||
// spot check; without header test tests this exhaustively
|
||||
Expect.stringContains html "<strong>Zeb</strong> — zyx</li>" "Expected requests not found"
|
||||
"asText succeeds",
|
||||
"asHtml succeeds with short as-of date",
|
||||
fun reqList ->
|
||||
let htmlList =
|
||||
{ reqList with
|
||||
listGroup =
|
||||
{ reqList.listGroup with
|
||||
preferences = { reqList.listGroup.preferences with asOfDateDisplay = ShortDate }
|
||||
}
|
||||
}
|
||||
let html = htmlList.asHtml _s
|
||||
let expected =
|
||||
htmlList.requests.[0].updatedDate.ToShortDateString ()
|
||||
|> sprintf "<strong>Zeb</strong> — zyx<i style=\"font-size:9.60pt\"> (as of %s)</i>"
|
||||
// spot check; if one request has it, they all should
|
||||
Expect.stringContains html expected "Expected short as-of date not found"
|
||||
"asHtml succeeds with long as-of date",
|
||||
fun reqList ->
|
||||
let htmlList =
|
||||
{ reqList with
|
||||
listGroup =
|
||||
{ reqList.listGroup with
|
||||
preferences = { reqList.listGroup.preferences with asOfDateDisplay = LongDate }
|
||||
}
|
||||
}
|
||||
let html = htmlList.asHtml _s
|
||||
let expected =
|
||||
htmlList.requests.[0].updatedDate.ToLongDateString ()
|
||||
|> sprintf "<strong>Zeb</strong> — zyx<i style=\"font-size:9.60pt\"> (as of %s)</i>"
|
||||
// spot check; if one request has it, they all should
|
||||
Expect.stringContains html expected "Expected long as-of date not found"
|
||||
"asText succeeds with no as-of date",
|
||||
fun reqList ->
|
||||
let textList = { reqList with listGroup = { reqList.listGroup with name = "Test Group" } }
|
||||
let text = textList.asText _s
|
||||
@@ -539,29 +582,61 @@ let requestListTests =
|
||||
Expect.stringContains text "------------------\n PRAISE REPORTS\n------------------\n"
|
||||
"Heading for category \"Praise Reports\" not found"
|
||||
Expect.stringContains text " + nmo\n \n" "Last request not found"
|
||||
"asText succeeds with short as-of date",
|
||||
fun reqList ->
|
||||
let textList =
|
||||
{ reqList with
|
||||
listGroup =
|
||||
{ reqList.listGroup with
|
||||
preferences = { reqList.listGroup.preferences with asOfDateDisplay = ShortDate }
|
||||
}
|
||||
}
|
||||
let text = textList.asText _s
|
||||
let expected =
|
||||
textList.requests.[0].updatedDate.ToShortDateString ()
|
||||
|> sprintf " + Zeb - zyx (as of %s)"
|
||||
// spot check; if one request has it, they all should
|
||||
Expect.stringContains text expected "Expected short as-of date not found"
|
||||
"asText succeeds with long as-of date",
|
||||
fun reqList ->
|
||||
let textList =
|
||||
{ reqList with
|
||||
listGroup =
|
||||
{ reqList.listGroup with
|
||||
preferences = { reqList.listGroup.preferences with asOfDateDisplay = LongDate }
|
||||
}
|
||||
}
|
||||
let text = textList.asText _s
|
||||
let expected =
|
||||
textList.requests.[0].updatedDate.ToLongDateString ()
|
||||
|> sprintf " + Zeb - zyx (as of %s)"
|
||||
// spot check; if one request has it, they all should
|
||||
Expect.stringContains text expected "Expected long as-of date not found"
|
||||
"isNew succeeds for both old and new requests",
|
||||
fun reqList ->
|
||||
let reqs = reqList.requestsInCategory RequestType.Current
|
||||
let reqs = reqList.requestsInCategory CurrentRequest
|
||||
Expect.hasCountOf reqs 2u countAll "There should have been two requests"
|
||||
Expect.isTrue (reqList.isNew (List.head reqs)) "The first request should have been new"
|
||||
Expect.isFalse (reqList.isNew (List.last reqs)) "The second request should not have been new"
|
||||
"requestsInCategory succeeds when requests exist",
|
||||
fun reqList ->
|
||||
let reqs = reqList.requestsInCategory RequestType.Current
|
||||
let reqs = reqList.requestsInCategory CurrentRequest
|
||||
Expect.hasCountOf reqs 2u countAll "There should have been two requests"
|
||||
let first = List.head reqs
|
||||
Expect.equal first.text "zyx" "The requests should be sorted by updated date descending"
|
||||
"requestsInCategory succeeds when requests do not exist",
|
||||
fun reqList ->
|
||||
Expect.isEmpty (reqList.requestsInCategory "ABC") "There should have been no category \"ABC\" requests"
|
||||
Expect.isEmpty (reqList.requestsInCategory Announcement) "There should have been no \"Announcement\" requests"
|
||||
"requestsInCategory succeeds and sorts by requestor",
|
||||
fun reqList ->
|
||||
let newList =
|
||||
{ reqList with
|
||||
listGroup =
|
||||
{ reqList.listGroup with preferences = { reqList.listGroup.preferences with requestSort = "R" } }
|
||||
{ reqList.listGroup with
|
||||
preferences = { reqList.listGroup.preferences with requestSort = SortByRequestor }
|
||||
}
|
||||
}
|
||||
let reqs = newList.requestsInCategory RequestType.Current
|
||||
let reqs = newList.requestsInCategory CurrentRequest
|
||||
Expect.hasCountOf reqs 2u countAll "There should have been two requests"
|
||||
let first = List.head reqs
|
||||
Expect.equal first.text "abc" "The requests should be sorted by requestor"
|
||||
|
||||
@@ -75,7 +75,7 @@ let maintain (churches : Church list) (stats : Map<string, ChurchStats>) ctx vi
|
||||
|> List.map (fun ch ->
|
||||
let chId = flatGuid ch.churchId
|
||||
let delAction = sprintf "/church/%s/delete" chId
|
||||
let delPrompt = s.["Are you want to delete this {0}? This action cannot be undone.",
|
||||
let delPrompt = s.["Are you sure you want to delete this {0}? This action cannot be undone.",
|
||||
sprintf "%s (%s)" (s.["Church"].Value.ToLower ()) ch.name]
|
||||
tr [] [
|
||||
td [] [
|
||||
|
||||
@@ -23,7 +23,7 @@ let edit (m : EditRequest) today ctx vi =
|
||||
label [ _for "requestType" ] [ locStr s.["Request Type"] ]
|
||||
ReferenceList.requestTypeList s
|
||||
|> Seq.ofList
|
||||
|> Seq.map (fun item -> fst item, (snd item).Value)
|
||||
|> Seq.map (fun (typ, desc) -> typ.code, desc.Value)
|
||||
|> selectList "requestType" m.requestType [ _required; _autofocus ]
|
||||
]
|
||||
yield div [ _class "pt-field" ] [
|
||||
@@ -160,39 +160,50 @@ let lists (grps : SmallGroup list) vi =
|
||||
|
||||
|
||||
/// View for the prayer request maintenance page
|
||||
let maintain (reqs : PrayerRequest seq) (grp : SmallGroup) onlyActive (ctx : HttpContext) vi =
|
||||
let maintain m (ctx : HttpContext) vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let now = grp.localDateNow (ctx.GetService<IClock> ())
|
||||
let l = I18N.forView "Requests/Maintain"
|
||||
use sw = new StringWriter ()
|
||||
let raw = rawLocText sw
|
||||
let now = m.smallGroup.localDateNow (ctx.GetService<IClock> ())
|
||||
let typs = ReferenceList.requestTypeList s |> Map.ofList
|
||||
let updReq (req : PrayerRequest) =
|
||||
match req.updateRequired now grp.preferences.daysToExpire grp.preferences.longTermUpdateWeeks with
|
||||
match req.updateRequired now m.smallGroup.preferences.daysToExpire m.smallGroup.preferences.longTermUpdateWeeks with
|
||||
| true -> "pt-request-update"
|
||||
| false -> ""
|
||||
|> _class
|
||||
let reqExp (req : PrayerRequest) =
|
||||
_class (match req.isExpired now grp.preferences.daysToExpire with true -> "pt-request-expired" | false -> "")
|
||||
_class (match req.isExpired now m.smallGroup.preferences.daysToExpire with true -> "pt-request-expired" | false -> "")
|
||||
/// Iterate the sequence once, before we render, so we can get the count of it at the top of the table
|
||||
let requests =
|
||||
reqs
|
||||
m.requests
|
||||
|> Seq.map (fun req ->
|
||||
let reqId = flatGuid req.prayerRequestId
|
||||
let reqText = Utils.htmlToPlainText req.text
|
||||
let delAction = sprintf "/prayer-request/%s/delete" reqId
|
||||
let delPrompt = s.["Are you want to delete this prayer request? This action cannot be undone.\\n(If the prayer request has been answered, or an event has passed, consider inactivating it instead.)"].Value
|
||||
let delPrompt =
|
||||
[ s.["Are you sure you want to delete this {0}? This action cannot be undone.",
|
||||
s.["Prayer Request"].Value.ToLower() ]
|
||||
.Value
|
||||
"\\n"
|
||||
l.["(If the prayer request has been answered, or an event has passed, consider inactivating it instead.)"]
|
||||
.Value
|
||||
]
|
||||
|> String.concat ""
|
||||
tr [] [
|
||||
td [] [
|
||||
yield a [ _href (sprintf "/prayer-request/%s/edit" reqId); _title s.["Edit This Prayer Request"].Value ]
|
||||
yield a [ _href (sprintf "/prayer-request/%s/edit" reqId); _title l.["Edit This Prayer Request"].Value ]
|
||||
[ icon "edit" ]
|
||||
match req.isExpired now grp.preferences.daysToExpire with
|
||||
match req.isExpired now m.smallGroup.preferences.daysToExpire with
|
||||
| true ->
|
||||
yield a [ _href (sprintf "/prayer-request/%s/restore" reqId)
|
||||
_title s.["Restore This Inactive Request"].Value ]
|
||||
_title l.["Restore This Inactive Request"].Value ]
|
||||
[ icon "visibility" ]
|
||||
| false ->
|
||||
yield a [ _href (sprintf "/prayer-request/%s/expire" reqId)
|
||||
_title s.["Expire This Request Immediately"].Value ]
|
||||
_title l.["Expire This Request Immediately"].Value ]
|
||||
[ icon "visibility_off" ]
|
||||
yield a [ _href delAction; _title s.["Delete This Request"].Value;
|
||||
yield a [ _href delAction; _title l.["Delete This Request"].Value;
|
||||
_onclick (sprintf "return PT.confirmDelete('%s','%s')" delAction delPrompt) ]
|
||||
[ icon "delete_forever" ]
|
||||
]
|
||||
@@ -205,20 +216,34 @@ let maintain (reqs : PrayerRequest seq) (grp : SmallGroup) onlyActive (ctx : Htt
|
||||
yield
|
||||
match 60 > reqText.Length with
|
||||
| true -> rawText reqText
|
||||
| false -> rawText (sprintf "%s…" (reqText.Substring (0, 60)))
|
||||
| false -> rawText (sprintf "%s…" reqText.[0..59])
|
||||
]
|
||||
])
|
||||
|> List.ofSeq
|
||||
[ yield div [ _class "pt-center-text" ] [
|
||||
br []
|
||||
a [ _href (sprintf "/prayer-request/%s/edit" emptyGuid); _title s.["Add a New Request"].Value ]
|
||||
yield br []
|
||||
yield a [ _href (sprintf "/prayer-request/%s/edit" emptyGuid); _title s.["Add a New Request"].Value ]
|
||||
[ icon "add_circle"; rawText " "; locStr s.["Add a New Request"] ]
|
||||
rawText " "
|
||||
a [ _href "/prayer-requests/view"; _title s.["View Prayer Request List"].Value ]
|
||||
yield rawText " "
|
||||
yield a [ _href "/prayer-requests/view"; _title s.["View Prayer Request List"].Value ]
|
||||
[ icon "list"; rawText " "; locStr s.["View Prayer Request List"] ]
|
||||
br []
|
||||
br []
|
||||
match m.searchTerm with
|
||||
| Some _ ->
|
||||
yield rawText " "
|
||||
yield a [ _href "/prayer-requests"; _title l.["Clear Search Criteria"].Value ]
|
||||
[ icon "highlight_off"; rawText " "; raw l.["Clear Search Criteria"] ]
|
||||
| None -> ()
|
||||
]
|
||||
yield form [ _action "/prayer-requests"; _method "get"; _class "pt-center-text pt-search-form" ] [
|
||||
input [ _type "text"
|
||||
_name "search"
|
||||
_placeholder l.["Search requests..."].Value
|
||||
_value (defaultArg m.searchTerm "")
|
||||
]
|
||||
space
|
||||
submit [] "search" s.["Search"]
|
||||
]
|
||||
yield br []
|
||||
yield tableSummary requests.Length s
|
||||
match requests.Length with
|
||||
| 0 -> ()
|
||||
@@ -237,20 +262,41 @@ let maintain (reqs : PrayerRequest seq) (grp : SmallGroup) onlyActive (ctx : Htt
|
||||
]
|
||||
yield div [ _class "pt-center-text" ] [
|
||||
yield br []
|
||||
match onlyActive with
|
||||
| true ->
|
||||
yield locStr s.["Inactive requests are currently not shown"]
|
||||
match m.onlyActive with
|
||||
| Some true ->
|
||||
yield raw l.["Inactive requests are currently not shown"]
|
||||
yield br []
|
||||
yield a [ _href "/prayer-requests/inactive" ] [ locStr s.["Show Inactive Requests"] ]
|
||||
| false ->
|
||||
yield locStr s.["Inactive requests are currently shown"]
|
||||
yield br []
|
||||
yield a [ _href "/prayer-requests" ] [ locStr s.["Do Not Show Inactive Requests"] ]
|
||||
yield a [ _href "/prayer-requests/inactive" ] [ raw l.["Show Inactive Requests"] ]
|
||||
| _ ->
|
||||
match Option.isSome m.onlyActive with
|
||||
| true ->
|
||||
yield raw l.["Inactive requests are currently shown"]
|
||||
yield br []
|
||||
yield a [ _href "/prayer-requests" ] [ raw l.["Do Not Show Inactive Requests"] ]
|
||||
yield br []
|
||||
yield br []
|
||||
| false -> ()
|
||||
let srch = [ match m.searchTerm with Some s -> yield "search", s | None -> () ]
|
||||
let url = match m.onlyActive with Some true | None -> "" | _ -> "/inactive" |> sprintf "/prayer-requests%s"
|
||||
let pg = defaultArg m.pageNbr 1
|
||||
match pg with
|
||||
| 1 -> ()
|
||||
| _ ->
|
||||
// button (_type "submit" :: attrs) [ icon ico; rawText " "; locStr text ]
|
||||
let withPage = match pg with 2 -> srch | _ -> ("page", string (pg - 1)) :: srch
|
||||
yield a [ _href (makeUrl url withPage) ]
|
||||
[ icon "keyboard_arrow_left"; space; raw l.["Previous Page"] ]
|
||||
yield rawText " "
|
||||
match requests.Length = m.smallGroup.preferences.pageSize with
|
||||
| true ->
|
||||
yield a [ _href (makeUrl url (("page", string (pg + 1)) :: srch)) ]
|
||||
[ raw l.["Next Page"]; space; icon "keyboard_arrow_right" ]
|
||||
| false -> ()
|
||||
]
|
||||
yield form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ]
|
||||
]
|
||||
|> Layout.Content.wide
|
||||
|> Layout.standard vi "Maintain Requests"
|
||||
|> Layout.standard vi (match m.searchTerm with Some _ -> "Search Results" | None -> "Maintain Requests")
|
||||
|
||||
|
||||
/// View for the printable prayer request list
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AssemblyVersion>7.0.0.0</AssemblyVersion>
|
||||
<AssemblyVersion>7.3.0.0</AssemblyVersion>
|
||||
<FileVersion>7.0.0.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -55,6 +55,9 @@
|
||||
<EmbeddedResource Update="Resources\Views\Requests\Lists.es.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\Views\Requests\Maintain.es.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Update="Resources\Views\SmallGroup\Preferences.es.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
|
||||
@@ -144,8 +144,8 @@
|
||||
<data name="Aqua" xml:space="preserve">
|
||||
<value>Verde Azulado Brillante</value>
|
||||
</data>
|
||||
<data name="Are you want to delete this {0}? This action cannot be undone." xml:space="preserve">
|
||||
<value>¿Está desea eliminar este {0}? Esta acción no se puede deshacer.</value>
|
||||
<data name="Are you sure you want to delete this {0}? This action cannot be undone." xml:space="preserve">
|
||||
<value>¿Seguro que desea eliminar este {0}? Esta acción no se puede deshacer.</value>
|
||||
</data>
|
||||
<data name="Attached PDF" xml:space="preserve">
|
||||
<value>PDF Adjunto</value>
|
||||
@@ -612,9 +612,6 @@
|
||||
<data name="Delete This Group" xml:space="preserve">
|
||||
<value>Eliminar Este Grupo</value>
|
||||
</data>
|
||||
<data name="Do Not Show Inactive Requests" xml:space="preserve">
|
||||
<value>No Muestran las Peticiones Inactivos</value>
|
||||
</data>
|
||||
<data name="E-mail" xml:space="preserve">
|
||||
<value>Correo Electrónico</value>
|
||||
</data>
|
||||
@@ -633,12 +630,6 @@
|
||||
<data name="Group Preferences" xml:space="preserve">
|
||||
<value>Las Preferencias del Grupo</value>
|
||||
</data>
|
||||
<data name="Inactive requests are currently not shown" xml:space="preserve">
|
||||
<value>Peticiones inactivas no se muestra actualmente</value>
|
||||
</data>
|
||||
<data name="Inactive requests are currently shown" xml:space="preserve">
|
||||
<value>Peticiones inactivas se muestra actualmente</value>
|
||||
</data>
|
||||
<data name="Maintain Groups" xml:space="preserve">
|
||||
<value>Mantener los Grupos</value>
|
||||
</data>
|
||||
@@ -696,9 +687,6 @@
|
||||
<data name="Send Announcement to" xml:space="preserve">
|
||||
<value>Enviar anuncio a</value>
|
||||
</data>
|
||||
<data name="Show Inactive Requests" xml:space="preserve">
|
||||
<value>Muestran las Peticiones Inactivos</value>
|
||||
</data>
|
||||
<data name="Sort by Last Updated Date" xml:space="preserve">
|
||||
<value>Ordenar por Fecha de Última Actualización</value>
|
||||
</data>
|
||||
@@ -729,15 +717,6 @@
|
||||
<data name="Active Requests" xml:space="preserve">
|
||||
<value>Peticiones Activas</value>
|
||||
</data>
|
||||
<data name="Delete This Request" xml:space="preserve">
|
||||
<value>Eliminar esta petición</value>
|
||||
</data>
|
||||
<data name="Edit This Prayer Request" xml:space="preserve">
|
||||
<value>Editar esta petición de oración</value>
|
||||
</data>
|
||||
<data name="Expire This Request Immediately" xml:space="preserve">
|
||||
<value>Expirar esta petición de oración de inmediato</value>
|
||||
</data>
|
||||
<data name="Maintain Prayer Requests" xml:space="preserve">
|
||||
<value>Mantener las Peticiones de Oración</value>
|
||||
</data>
|
||||
@@ -747,9 +726,6 @@
|
||||
<data name="Quick Actions" xml:space="preserve">
|
||||
<value>Acciones Rápidas</value>
|
||||
</data>
|
||||
<data name="Restore This Inactive Request" xml:space="preserve">
|
||||
<value>Restaurar esta petición inactiva</value>
|
||||
</data>
|
||||
<data name="Save" xml:space="preserve">
|
||||
<value>Guardar</value>
|
||||
</data>
|
||||
@@ -819,4 +795,31 @@
|
||||
<data name="Click for Help on This Page" xml:space="preserve">
|
||||
<value>Haga Clic para Obtener Ayuda en Esta Página</value>
|
||||
</data>
|
||||
<data name="Search" xml:space="preserve">
|
||||
<value>Buscar</value>
|
||||
</data>
|
||||
<data name="Display a full “as of” date" xml:space="preserve">
|
||||
<value>Mostrar una fecha “como de” completa</value>
|
||||
</data>
|
||||
<data name="Display a short “as of” date" xml:space="preserve">
|
||||
<value>Mostrar una fecha “como de” corta</value>
|
||||
</data>
|
||||
<data name="Do not display the “as of” date" xml:space="preserve">
|
||||
<value>No se muestran la fecha “como de”</value>
|
||||
</data>
|
||||
<data name="Page Size" xml:space="preserve">
|
||||
<value>Tamaño de Página</value>
|
||||
</data>
|
||||
<data name="Prayer Request" xml:space="preserve">
|
||||
<value>Petición de Oración</value>
|
||||
</data>
|
||||
<data name="Search Results" xml:space="preserve">
|
||||
<value>Resultados de la Búsqueda</value>
|
||||
</data>
|
||||
<data name="“As of” Date Display" xml:space="preserve">
|
||||
<value>Visualización de la Fecha “Como de”</value>
|
||||
</data>
|
||||
<data name="as of" xml:space="preserve">
|
||||
<value>como de</value>
|
||||
</data>
|
||||
</root>
|
||||
159
src/PrayerTracker.UI/Resources/Views/Requests/Maintain.es.resx
Normal file
159
src/PrayerTracker.UI/Resources/Views/Requests/Maintain.es.resx
Normal file
@@ -0,0 +1,159 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="(If the prayer request has been answered, or an event has passed, consider inactivating it instead.)" xml:space="preserve">
|
||||
<value>(Si la solicitud de oración ha sido respondida o si un evento ha pasado, considere desactivarla.)</value>
|
||||
</data>
|
||||
<data name="Clear Search Criteria" xml:space="preserve">
|
||||
<value>Borrar los Criterios de Búsqueda</value>
|
||||
</data>
|
||||
<data name="Delete This Request" xml:space="preserve">
|
||||
<value>Eliminar esta petición</value>
|
||||
</data>
|
||||
<data name="Do Not Show Inactive Requests" xml:space="preserve">
|
||||
<value>No Muestran las Peticiones Inactivos</value>
|
||||
</data>
|
||||
<data name="Edit This Prayer Request" xml:space="preserve">
|
||||
<value>Editar esta petición de oración</value>
|
||||
</data>
|
||||
<data name="Expire This Request Immediately" xml:space="preserve">
|
||||
<value>Expirar esta petición de oración de inmediato</value>
|
||||
</data>
|
||||
<data name="Inactive requests are currently not shown" xml:space="preserve">
|
||||
<value>Peticiones inactivas no se muestra actualmente</value>
|
||||
</data>
|
||||
<data name="Inactive requests are currently shown" xml:space="preserve">
|
||||
<value>Peticiones inactivas se muestra actualmente</value>
|
||||
</data>
|
||||
<data name="Next Page" xml:space="preserve">
|
||||
<value>Siguiente Página</value>
|
||||
</data>
|
||||
<data name="Previous Page" xml:space="preserve">
|
||||
<value>Página Anterior</value>
|
||||
</data>
|
||||
<data name="Restore This Inactive Request" xml:space="preserve">
|
||||
<value>Restaurar esta petición inactiva</value>
|
||||
</data>
|
||||
<data name="Search requests..." xml:space="preserve">
|
||||
<value>Busca las peticiones...</value>
|
||||
</data>
|
||||
<data name="Show Inactive Requests" xml:space="preserve">
|
||||
<value>Muestran las Peticiones Inactivos</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -45,7 +45,7 @@ let announcement isAdmin ctx vi =
|
||||
label [ _for "requestType" ] [ locStr s.["Request Type"] ]
|
||||
reqTypes
|
||||
|> Seq.ofList
|
||||
|> Seq.map (fun item -> fst item, (snd item).Value)
|
||||
|> Seq.map (fun (typ, desc) -> typ.code, desc.Value)
|
||||
|> selectList "requestType" "Announcement" []
|
||||
]
|
||||
]
|
||||
@@ -192,7 +192,7 @@ let maintain (grps : SmallGroup list) ctx vi =
|
||||
|> List.map (fun g ->
|
||||
let grpId = flatGuid g.smallGroupId
|
||||
let delAction = sprintf "/small-group/%s/delete" grpId
|
||||
let delPrompt = s.["Are you want to delete this {0}? This action cannot be undone.",
|
||||
let delPrompt = s.["Are you sure you want to delete this {0}? This action cannot be undone.",
|
||||
sprintf "%s (%s)" (s.["Small Group"].Value.ToLower ()) g.name].Value
|
||||
tr [] [
|
||||
td [] [
|
||||
@@ -246,8 +246,10 @@ let members (mbrs : Member list) (emailTyps : Map<string, LocalizedString>) ctx
|
||||
|> List.map (fun mbr ->
|
||||
let mbrId = flatGuid mbr.memberId
|
||||
let delAction = sprintf "/small-group/member/%s/delete" mbrId
|
||||
let delPrompt = s.["Are you want to delete this {0} ({1})? This action cannot be undone.",
|
||||
s.["group member"], mbr.memberName].Value
|
||||
let delPrompt =
|
||||
s.["Are you sure you want to delete this {0}? This action cannot be undone.", s.["group member"]]
|
||||
.Value
|
||||
.Replace("?", sprintf " (%s)?" mbr.memberName)
|
||||
tr [] [
|
||||
td [] [
|
||||
a [ _href (sprintf "/small-group/member/%s/edit" mbrId); _title s.["Edit This Group Member"].Value ]
|
||||
@@ -282,7 +284,7 @@ let members (mbrs : Member list) (emailTyps : Map<string, LocalizedString>) ctx
|
||||
let overview m vi =
|
||||
let s = I18N.localizer.Force ()
|
||||
let linkSpacer = rawText " "
|
||||
let typs = ReferenceList.requestTypeList s |> Map.ofList
|
||||
let typs = ReferenceList.requestTypeList s |> dict
|
||||
article [ _class "pt-overview" ] [
|
||||
section [] [
|
||||
header [ _role "heading" ] [
|
||||
@@ -348,7 +350,7 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
|
||||
use sw = new StringWriter ()
|
||||
let raw = rawLocText sw
|
||||
[ form [ _action "/small-group/preferences/save"; _method "post"; _class "pt-center-columns" ] [
|
||||
style [ _scoped ] [ rawText "#expireDays, #daysToKeepNew, #longTermUpdateWeeks, #headingFontSize, #listFontSize { width: 3rem; } #emailFromAddress { width: 20rem; } #listFonts { width: 40rem; } @media screen and (max-width: 40rem) { #listFonts { width: 100%; } }" ]
|
||||
style [ _scoped ] [ rawText "#expireDays, #daysToKeepNew, #longTermUpdateWeeks, #headingFontSize, #listFontSize, #pageSize { width: 3rem; } #emailFromAddress { width: 20rem; } #listFonts { width: 40rem; } @media screen and (max-width: 40rem) { #listFonts { width: 100%; } }" ]
|
||||
csrfToken ctx
|
||||
fieldset [] [
|
||||
legend [] [ strong [] [ icon "date_range"; rawText " "; locStr s.["Dates"] ] ]
|
||||
@@ -405,7 +407,9 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
|
||||
label [ _for "defaultEmailType" ] [ locStr s.["E-mail Format"] ]
|
||||
seq {
|
||||
yield "", selectDefault s.["Select"].Value
|
||||
yield! ReferenceList.emailTypeList "" s |> Seq.skip 1 |> Seq.map (fun typ -> fst typ, (snd typ).Value)
|
||||
yield! ReferenceList.emailTypeList HtmlFormat s
|
||||
|> Seq.skip 1
|
||||
|> Seq.map (fun typ -> fst typ, (snd typ).Value)
|
||||
}
|
||||
|> selectList "defaultEmailType" m.defaultEmailType [ _required ]
|
||||
]
|
||||
@@ -477,7 +481,7 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
|
||||
legend [] [ strong [] [ icon "settings"; rawText " "; locStr s.["Other Settings"] ] ]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "TimeZone" ] [ locStr s.["Time Zone"] ]
|
||||
label [ _for "timeZone" ] [ locStr s.["Time Zone"] ]
|
||||
seq {
|
||||
yield "", selectDefault s.["Select"].Value
|
||||
yield! tzs |> List.map (fun tz -> tz.timeZoneId, (TimeZones.name tz.timeZoneId s).Value)
|
||||
@@ -509,6 +513,19 @@ let preferences (m : EditPreferences) (tzs : TimeZone list) ctx vi =
|
||||
_value (match m.groupPassword with Some x -> x | None -> "") ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "pageSize" ] [ locStr s.["Page Size"] ]
|
||||
input [ _type "number"; _name "pageSize"; _id "pageSize"; _min "10"; _max "255"; _required
|
||||
_value (string m.pageSize) ]
|
||||
]
|
||||
div [ _class "pt-field" ] [
|
||||
label [ _for "asOfDate" ] [ locStr s.["“As of” Date Display"] ]
|
||||
ReferenceList.asOfDateList s
|
||||
|> List.map (fun (code, desc) -> code, desc.Value)
|
||||
|> selectList "asOfDate" m.asOfDate [ _required ]
|
||||
]
|
||||
]
|
||||
div [ _class "pt-field-row" ] [ submit [] "save" s.["Save Preferences"] ]
|
||||
]
|
||||
]
|
||||
|
||||
@@ -190,7 +190,7 @@ let maintain (users : User list) ctx vi =
|
||||
|> List.map (fun user ->
|
||||
let userId = flatGuid user.userId
|
||||
let delAction = sprintf "/user/%s/delete" userId
|
||||
let delPrompt = s.["Are you want to delete this {0}? This action cannot be undone.",
|
||||
let delPrompt = s.["Are you sure you want to delete this {0}? This action cannot be undone.",
|
||||
(sprintf "%s (%s)" (s.["User"].Value.ToLower()) user.fullName)].Value
|
||||
tr [] [
|
||||
td [] [
|
||||
|
||||
@@ -127,6 +127,20 @@ let htmlToPlainText html =
|
||||
/// Get the second portion of a tuple as a string
|
||||
let sndAsString x = (snd >> string) x
|
||||
|
||||
|
||||
/// Make a URL with query string parameters
|
||||
let makeUrl (url : string) (qs : (string * string) list) =
|
||||
let queryString =
|
||||
qs
|
||||
|> List.fold
|
||||
(fun (acc : StringBuilder) (key, value) ->
|
||||
acc.Append(key).Append("=").Append(WebUtility.UrlEncode value).Append "&")
|
||||
(StringBuilder ())
|
||||
match queryString.Length with
|
||||
| 0 -> url
|
||||
| _ -> queryString.Insert(0, "?").Insert(0, url).Remove(queryString.Length - 1, 1).ToString ()
|
||||
|
||||
|
||||
/// "Magic string" repository
|
||||
[<RequireQualifiedAccess>]
|
||||
module Key =
|
||||
|
||||
@@ -10,37 +10,40 @@ open System
|
||||
/// Helper module to return localized reference lists
|
||||
module ReferenceList =
|
||||
|
||||
/// A localized list of the AsOfDateDisplay DU cases
|
||||
let asOfDateList (s : IStringLocalizer) =
|
||||
[ NoDisplay.code, s.["Do not display the “as of” date"]
|
||||
ShortDate.code, s.["Display a short “as of” date"]
|
||||
LongDate.code, s.["Display a full “as of” date"]
|
||||
]
|
||||
|
||||
/// A list of e-mail type options
|
||||
let emailTypeList def (s : IStringLocalizer) =
|
||||
// Localize the default type
|
||||
let defaultType =
|
||||
match def with
|
||||
| EmailType.Html -> s.["HTML Format"].Value
|
||||
| EmailType.PlainText -> s.["Plain-Text Format"].Value
|
||||
| EmailType.AttachedPdf -> s.["Attached PDF"].Value
|
||||
| _ -> ""
|
||||
| HtmlFormat -> s.["HTML Format"].Value
|
||||
| PlainTextFormat -> s.["Plain-Text Format"].Value
|
||||
seq {
|
||||
yield "", LocalizedString ("", sprintf "%s (%s)" s.["Group Default"].Value defaultType)
|
||||
yield EmailType.Html, s.["HTML Format"]
|
||||
yield EmailType.PlainText, s.["Plain-Text Format"]
|
||||
yield HtmlFormat.code, s.["HTML Format"]
|
||||
yield PlainTextFormat.code, s.["Plain-Text Format"]
|
||||
}
|
||||
|
||||
/// A list of expiration options
|
||||
let expirationList (s : IStringLocalizer) includeExpireNow =
|
||||
seq {
|
||||
yield "N", s.["Expire Normally"]
|
||||
yield "Y", s.["Request Never Expires"]
|
||||
match includeExpireNow with true -> yield "X", s.["Expire Immediately"] | false -> ()
|
||||
}
|
||||
|> List.ofSeq
|
||||
[ yield Automatic.code, s.["Expire Normally"]
|
||||
yield Manual.code, s.["Request Never Expires"]
|
||||
match includeExpireNow with true -> yield Forced.code, s.["Expire Immediately"] | false -> ()
|
||||
]
|
||||
|
||||
/// A list of request types
|
||||
let requestTypeList (s : IStringLocalizer) =
|
||||
[ RequestType.Current, s.["Current Requests"]
|
||||
RequestType.Recurring, s.["Long-Term Requests"]
|
||||
RequestType.Praise, s.["Praise Reports"]
|
||||
RequestType.Expecting, s.["Expecting"]
|
||||
RequestType.Announcement, s.["Announcements"]
|
||||
[ CurrentRequest, s.["Current Requests"]
|
||||
LongTermRequest, s.["Long-Term Requests"]
|
||||
PraiseReport, s.["Praise Reports"]
|
||||
Expecting, s.["Expecting"]
|
||||
Announcement, s.["Announcements"]
|
||||
]
|
||||
|
||||
|
||||
@@ -273,6 +276,10 @@ type EditPreferences =
|
||||
listVisibility : int
|
||||
/// The small group password
|
||||
groupPassword : string option
|
||||
/// The page size for search / inactive requests
|
||||
pageSize : int
|
||||
/// How the as-of date should be displayed
|
||||
asOfDate : string
|
||||
}
|
||||
with
|
||||
static member fromPreferences (prefs : ListPreferences) =
|
||||
@@ -280,10 +287,10 @@ with
|
||||
{ expireDays = prefs.daysToExpire
|
||||
daysToKeepNew = prefs.daysToKeepNew
|
||||
longTermUpdateWeeks = prefs.longTermUpdateWeeks
|
||||
requestSort = prefs.requestSort
|
||||
requestSort = prefs.requestSort.code
|
||||
emailFromName = prefs.emailFromName
|
||||
emailFromAddress = prefs.emailFromAddress
|
||||
defaultEmailType = prefs.defaultEmailType
|
||||
defaultEmailType = prefs.defaultEmailType.code
|
||||
headingLineType = setType prefs.lineColor
|
||||
headingLineColor = prefs.lineColor
|
||||
headingTextType = setType prefs.headingColor
|
||||
@@ -293,6 +300,8 @@ with
|
||||
listFontSize = prefs.textFontSize
|
||||
timeZone = prefs.timeZoneId
|
||||
groupPassword = Some prefs.groupPassword
|
||||
pageSize = prefs.pageSize
|
||||
asOfDate = prefs.asOfDateDisplay.code
|
||||
listVisibility =
|
||||
match true with
|
||||
| _ when prefs.isPublic -> RequestVisibility.``public``
|
||||
@@ -311,10 +320,10 @@ with
|
||||
daysToExpire = this.expireDays
|
||||
daysToKeepNew = this.daysToKeepNew
|
||||
longTermUpdateWeeks = this.longTermUpdateWeeks
|
||||
requestSort = this.requestSort
|
||||
requestSort = RequestSort.fromCode this.requestSort
|
||||
emailFromName = this.emailFromName
|
||||
emailFromAddress = this.emailFromAddress
|
||||
defaultEmailType = this.defaultEmailType
|
||||
defaultEmailType = EmailFormat.fromCode this.defaultEmailType
|
||||
lineColor = this.headingLineColor
|
||||
headingColor = this.headingTextColor
|
||||
listFonts = this.listFonts
|
||||
@@ -323,6 +332,8 @@ with
|
||||
timeZoneId = this.timeZone
|
||||
isPublic = isPublic
|
||||
groupPassword = grpPw
|
||||
pageSize = this.pageSize
|
||||
asOfDateDisplay = AsOfDateDisplay.fromCode this.asOfDate
|
||||
}
|
||||
|
||||
|
||||
@@ -349,20 +360,20 @@ with
|
||||
/// An empty instance to use for new requests
|
||||
static member empty =
|
||||
{ requestId = Guid.Empty
|
||||
requestType = ""
|
||||
requestType = CurrentRequest.code
|
||||
enteredDate = None
|
||||
skipDateUpdate = None
|
||||
requestor = None
|
||||
expiration = "N"
|
||||
expiration = Automatic.code
|
||||
text = ""
|
||||
}
|
||||
/// Create an instance from an existing request
|
||||
static member fromRequest req =
|
||||
{ EditRequest.empty with
|
||||
requestId = req.prayerRequestId
|
||||
requestType = req.requestType
|
||||
requestType = req.requestType.code
|
||||
requestor = req.requestor
|
||||
expiration = match req.doNotExpire with true -> "Y" | false -> "N"
|
||||
expiration = req.expiration.code
|
||||
text = req.text
|
||||
}
|
||||
/// Is this a new request?
|
||||
@@ -473,12 +484,37 @@ with
|
||||
}
|
||||
|
||||
|
||||
/// Items needed to display the request maintenance page
|
||||
[<NoComparison; NoEquality>]
|
||||
type MaintainRequests =
|
||||
{ /// The requests to be displayed
|
||||
requests : PrayerRequest seq
|
||||
/// The small group to which the requests belong
|
||||
smallGroup : SmallGroup
|
||||
/// Whether only active requests are included
|
||||
onlyActive : bool option
|
||||
/// The search term for the requests
|
||||
searchTerm : string option
|
||||
/// The page number of the results
|
||||
pageNbr : int option
|
||||
}
|
||||
with
|
||||
static member empty =
|
||||
{ requests = Seq.empty
|
||||
smallGroup = SmallGroup.empty
|
||||
onlyActive = None
|
||||
searchTerm = None
|
||||
pageNbr = None
|
||||
}
|
||||
|
||||
|
||||
/// Items needed to display the small group overview page
|
||||
[<NoComparison; NoEquality>]
|
||||
type Overview =
|
||||
{ /// The total number of active requests
|
||||
totalActiveReqs : int
|
||||
/// The numbers of active requests by category
|
||||
activeReqsByCat : Map<string, int>
|
||||
activeReqsByCat : Map<PrayerRequestType, int>
|
||||
/// A count of all requests
|
||||
allReqs : int
|
||||
/// A count of all members
|
||||
@@ -535,15 +571,16 @@ with
|
||||
|> Seq.ofList
|
||||
|> Seq.filter (fun req -> req.requestType = cat)
|
||||
match this.listGroup.preferences.requestSort with
|
||||
| "D" -> reqs |> Seq.sortByDescending (fun req -> req.updatedDate)
|
||||
| _ -> reqs |> Seq.sortBy (fun req -> req.requestor)
|
||||
| SortByDate -> reqs |> Seq.sortByDescending (fun req -> req.updatedDate)
|
||||
| SortByRequestor -> reqs |> Seq.sortBy (fun req -> req.requestor)
|
||||
|> List.ofSeq
|
||||
/// Is this request new?
|
||||
member this.isNew (req : PrayerRequest) =
|
||||
(this.date - req.updatedDate).Days <= this.listGroup.preferences.daysToKeepNew
|
||||
/// Generate this list as HTML
|
||||
member this.asHtml (s : IStringLocalizer) =
|
||||
let prefs = this.listGroup.preferences
|
||||
let prefs = this.listGroup.preferences
|
||||
let asOfSize = Math.Round (float prefs.textFontSize * 0.8, 2)
|
||||
[ match this.showHeader with
|
||||
| true ->
|
||||
yield div [ _style (sprintf "text-align:center;font-family:%s" prefs.listFonts) ] [
|
||||
@@ -590,6 +627,18 @@ with
|
||||
| Some _ -> ()
|
||||
| None -> ()
|
||||
yield rawText req.text
|
||||
match prefs.asOfDateDisplay with
|
||||
| NoDisplay -> ()
|
||||
| ShortDate
|
||||
| LongDate ->
|
||||
let dt =
|
||||
match prefs.asOfDateDisplay with
|
||||
| ShortDate -> req.updatedDate.ToShortDateString ()
|
||||
| LongDate -> req.updatedDate.ToLongDateString ()
|
||||
| _ -> ""
|
||||
yield i [ _style (sprintf "font-size:%.2fpt" asOfSize) ] [
|
||||
rawText " ("; str s.["as of"].Value; str " "; str dt; rawText ")"
|
||||
]
|
||||
])
|
||||
|> ul []
|
||||
yield br []
|
||||
@@ -618,7 +667,17 @@ with
|
||||
for req in reqs do
|
||||
let bullet = match this.isNew req with true -> "+" | false -> "-"
|
||||
let requestor = match req.requestor with Some r -> sprintf "%s - " r | None -> ""
|
||||
yield sprintf " %s %s%s" bullet requestor (htmlToPlainText req.text)
|
||||
yield
|
||||
match this.listGroup.preferences.asOfDateDisplay with
|
||||
| NoDisplay -> ""
|
||||
| _ ->
|
||||
let dt =
|
||||
match this.listGroup.preferences.asOfDateDisplay with
|
||||
| ShortDate -> req.updatedDate.ToShortDateString ()
|
||||
| LongDate -> req.updatedDate.ToLongDateString ()
|
||||
| _ -> ""
|
||||
sprintf " (%s %s)" s.["as of"].Value dt
|
||||
|> sprintf " %s %s%s%s" bullet requestor (htmlToPlainText req.text)
|
||||
yield " "
|
||||
}
|
||||
|> String.concat "\n"
|
||||
|
||||
@@ -67,18 +67,15 @@ let sendEmails (client : SmtpClient) (recipients : Member list) grp subj html te
|
||||
let plainTextMsg = createTextMessage grp subj text s
|
||||
|
||||
for mbr in recipients do
|
||||
let emailType = match mbr.format with Some f -> f | None -> grp.preferences.defaultEmailType
|
||||
let emailTo = MailboxAddress (mbr.memberName, mbr.email)
|
||||
let emailType = match mbr.format with Some f -> EmailFormat.fromCode f | None -> grp.preferences.defaultEmailType
|
||||
let emailTo = MailboxAddress (mbr.memberName, mbr.email)
|
||||
match emailType with
|
||||
| EmailType.Html ->
|
||||
| HtmlFormat ->
|
||||
htmlMsg.To.Add emailTo
|
||||
do! client.SendAsync htmlMsg
|
||||
htmlMsg.To.Clear ()
|
||||
| EmailType.PlainText ->
|
||||
| PlainTextFormat ->
|
||||
plainTextMsg.To.Add emailTo
|
||||
do! client.SendAsync plainTextMsg
|
||||
plainTextMsg.To.Clear ()
|
||||
| EmailType.AttachedPdf ->
|
||||
raise <| NotImplementedException "Attached PDF format has not been implemented"
|
||||
| _ -> invalidOp <| sprintf "Unknown e-mail type %s passed" emailType
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ let private generateRequestList ctx date =
|
||||
match date with
|
||||
| Some d -> d
|
||||
| None -> grp.localDateNow clock
|
||||
let reqs = ctx.dbContext().AllRequestsForSmallGroup grp clock (Some listDate) true
|
||||
let reqs = ctx.dbContext().AllRequestsForSmallGroup grp clock (Some listDate) true 0
|
||||
{ requests = reqs |> List.ofSeq
|
||||
date = listDate
|
||||
listGroup = grp
|
||||
@@ -136,7 +136,7 @@ let expire reqId : HttpHandler =
|
||||
| Ok r ->
|
||||
let db = ctx.dbContext ()
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
db.UpdateEntry { r with isManuallyExpired = true }
|
||||
db.UpdateEntry { r with expiration = Forced }
|
||||
let! _ = db.SaveChangesAsync ()
|
||||
addInfo ctx s.["Successfully {0} prayer request", s.["Expired"].Value.ToLower ()]
|
||||
return! redirectTo false "/prayer-requests" next ctx
|
||||
@@ -155,7 +155,7 @@ let list groupId : HttpHandler =
|
||||
match grp with
|
||||
| Some g when g.preferences.isPublic ->
|
||||
let clock = ctx.GetService<IClock> ()
|
||||
let reqs = db.AllRequestsForSmallGroup g clock None true
|
||||
let reqs = db.AllRequestsForSmallGroup g clock None true 0
|
||||
return!
|
||||
viewInfo ctx startTicks
|
||||
|> Views.PrayerRequest.list
|
||||
@@ -190,6 +190,8 @@ let lists : HttpHandler =
|
||||
|
||||
|
||||
/// GET /prayer-requests[/inactive?]
|
||||
/// - OR -
|
||||
/// GET /prayer-requests?search=[search-query]
|
||||
let maintain onlyActive : HttpHandler =
|
||||
requireAccess [ User ]
|
||||
>=> fun next ctx ->
|
||||
@@ -197,10 +199,27 @@ let maintain onlyActive : HttpHandler =
|
||||
let db = ctx.dbContext ()
|
||||
let grp = currentGroup ctx
|
||||
task {
|
||||
let reqs = db.AllRequestsForSmallGroup grp (ctx.GetService<IClock> ()) None onlyActive
|
||||
let pageNbr =
|
||||
match ctx.GetQueryStringValue "page" with
|
||||
| Ok pg -> match Int32.TryParse pg with true, p -> p | false, _ -> 1
|
||||
| Error _ -> 1
|
||||
let m =
|
||||
match ctx.GetQueryStringValue "search" with
|
||||
| Ok srch ->
|
||||
{ MaintainRequests.empty with
|
||||
requests = db.SearchRequestsForSmallGroup grp srch pageNbr
|
||||
searchTerm = Some srch
|
||||
pageNbr = Some pageNbr
|
||||
}
|
||||
| Error _ ->
|
||||
{ MaintainRequests.empty with
|
||||
requests = db.AllRequestsForSmallGroup grp (ctx.GetService<IClock> ()) None onlyActive pageNbr
|
||||
onlyActive = Some onlyActive
|
||||
pageNbr = match onlyActive with true -> None | false -> Some pageNbr
|
||||
}
|
||||
return!
|
||||
{ viewInfo ctx startTicks with helpLink = Some Help.maintainRequests }
|
||||
|> Views.PrayerRequest.maintain reqs grp onlyActive ctx
|
||||
|> Views.PrayerRequest.maintain { m with smallGroup = grp } ctx
|
||||
|> renderHtml next ctx
|
||||
}
|
||||
|
||||
@@ -228,7 +247,7 @@ let restore reqId : HttpHandler =
|
||||
| Ok r ->
|
||||
let db = ctx.dbContext ()
|
||||
let s = Views.I18N.localizer.Force ()
|
||||
db.UpdateEntry { r with isManuallyExpired = false; updatedDate = DateTime.Now }
|
||||
db.UpdateEntry { r with expiration = Automatic; updatedDate = DateTime.Now }
|
||||
let! _ = db.SaveChangesAsync ()
|
||||
addInfo ctx s.["Successfully {0} prayer request", s.["Restored"].Value.ToLower ()]
|
||||
return! redirectTo false "/prayer-requests" next ctx
|
||||
@@ -254,11 +273,10 @@ let save : HttpHandler =
|
||||
| Some pr ->
|
||||
let upd8 =
|
||||
{ pr with
|
||||
requestType = m.requestType
|
||||
requestor = m.requestor
|
||||
text = ckEditorToText m.text
|
||||
doNotExpire = m.expiration = "Y"
|
||||
isManuallyExpired = m.expiration = "X"
|
||||
requestType = PrayerRequestType.fromCode m.requestType
|
||||
requestor = m.requestor
|
||||
text = ckEditorToText m.text
|
||||
expiration = Expiration.fromCode m.expiration
|
||||
}
|
||||
let grp = currentGroup ctx
|
||||
let now = grp.localDateNow (ctx.GetService<IClock> ())
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<AssemblyVersion>7.2.0.0</AssemblyVersion>
|
||||
<FileVersion>7.0.0.0</FileVersion>
|
||||
<AssemblyVersion>7.3.0.0</AssemblyVersion>
|
||||
<FileVersion>7.3.0.0</FileVersion>
|
||||
<Authors></Authors>
|
||||
<Company>Bit Badger Solutions</Company>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -208,7 +208,7 @@ let overview : HttpHandler =
|
||||
let db = ctx.dbContext ()
|
||||
let clock = ctx.GetService<IClock> ()
|
||||
task {
|
||||
let reqs = db.AllRequestsForSmallGroup (currentGroup ctx) clock None true |> List.ofSeq
|
||||
let reqs = db.AllRequestsForSmallGroup (currentGroup ctx) clock None true 0 |> List.ofSeq
|
||||
let! reqCount = db.CountRequestsBySmallGroup (currentGroup ctx).smallGroupId
|
||||
let! mbrCount = db.CountMembersForSmallGroup (currentGroup ctx).smallGroupId
|
||||
let m =
|
||||
@@ -387,7 +387,7 @@ let sendAnnouncement : HttpHandler =
|
||||
prayerRequestId = Guid.NewGuid ()
|
||||
smallGroupId = grp.smallGroupId
|
||||
userId = usr.userId
|
||||
requestType = Option.get m.requestType
|
||||
requestType = (Option.get >> PrayerRequestType.fromCode) m.requestType
|
||||
text = requestText
|
||||
enteredDate = now
|
||||
updatedDate = now
|
||||
|
||||
@@ -369,6 +369,12 @@ article.pt-overview section div p {
|
||||
border-radius: 5px;
|
||||
margin: auto;
|
||||
}
|
||||
.pt-search-form input,
|
||||
.pt-search-form button,
|
||||
.pt-search-form i.material-icons {
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.pt-request-update {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
27
src/search-sql.txt
Normal file
27
src/search-sql.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
create extension pg_trgm;
|
||||
set search_path=pt,public;
|
||||
create index "IX_PrayerRequest_Requestor_TRGM" on "PrayerRequest" using GIN (COALESCE("Requestor", '') gin_trgm_ops);
|
||||
create index "IX_PrayerRequest_Text_TRGM" on "PrayerRequest" using GIN ("Text" gin_trgm_ops);
|
||||
alter table "ListPreference" add column "PageSize" int not null default 100;
|
||||
alter table "ListPreference" add column "AsOfDateDisplay" varchar(1) not null default 'N';
|
||||
/* RequestType to 1 character code */
|
||||
update "PrayerRequest" set "RequestType" = 'C' where "RequestType" = 'Current';
|
||||
update "PrayerRequest" set "RequestType" = 'L' where "RequestType" = 'Recurring';
|
||||
update "PrayerRequest" set "RequestType" = 'P' where "RequestType" = 'Praise';
|
||||
update "PrayerRequest" set "RequestType" = 'E' where "RequestType" = 'Expecting';
|
||||
update "PrayerRequest" set "RequestType" = 'A' where "RequestType" = 'Announcement';
|
||||
alter table "PrayerRequest" alter column "RequestType" set data type varchar(1);
|
||||
/* Change expiration to a 1-character code field */
|
||||
alter table "PrayerRequest" add column "Expiration" varchar(1);
|
||||
update "PrayerRequest" set "Expiration" = case when "IsManuallyExpired" then 'F' when "DoNotExpire" then 'M' else 'A' end;
|
||||
alter table "PrayerRequest" alter column "Expiration" set not null;
|
||||
alter table "PrayerRequest" drop column "DoNotExpire";
|
||||
alter table "PrayerRequest" drop column "IsManuallyExpired";
|
||||
/* Change e-mail type to 1-character code field in list preferences and members */
|
||||
update "ListPreference" set "DefaultEmailType" = 'H' where "DefaultEmailType" = 'Html';
|
||||
update "ListPreference" set "DefaultEmailType" = 'P' where "DefaultEmailType" = 'Text';
|
||||
alter table "ListPreference" alter column "DefaultEmailType" set default 'H';
|
||||
alter table "ListPreference" alter column "DefaultEmailType" set data type varchar(1);
|
||||
update "Member" set "Format" = 'H' where "Format" = 'Html';
|
||||
update "Member" set "Format" = 'P' where "Format" = 'Text';
|
||||
alter table "Member" alter column "Format" set data type varchar(1);
|
||||
Reference in New Issue
Block a user