Use grid layout for tables (#38)

- Add icon for current language
This commit is contained in:
Daniel J. Summers 2022-08-06 19:36:02 -04:00
parent 57814b5bf1
commit 980346b98d
6 changed files with 206 additions and 196 deletions

View File

@ -2,6 +2,7 @@
open Giraffe.ViewEngine open Giraffe.ViewEngine
open Giraffe.ViewEngine.Accessibility open Giraffe.ViewEngine.Accessibility
open Giraffe.ViewEngine.Htmx
open PrayerTracker open PrayerTracker
open PrayerTracker.Entities open PrayerTracker.Entities
open PrayerTracker.ViewModels open PrayerTracker.ViewModels
@ -58,71 +59,49 @@ let edit (model : EditChurch) ctx viewInfo =
/// View for church maintenance page /// View for church maintenance page
let maintain (churches : Church list) (stats : Map<string, ChurchStats>) ctx viewInfo = let maintain (churches : Church list) (stats : Map<string, ChurchStats>) ctx viewInfo =
let s = I18N.localizer.Force () let s = I18N.localizer.Force ()
let vi = let vi = AppViewInfo.withScopedStyles [ "#churchList { grid-template-columns: repeat(7, auto); }" ] viewInfo
AppViewInfo.withScopedStyles [ let churchTable =
"#churchList { grid-template-columns: repeat(7, auto); }"
] viewInfo
let chTbl =
match churches with match churches with
| [] -> space | [] -> space
| _ -> | _ ->
section [ _id "churchList"; _class "pt-data-list"; _ariaLabel "Church list" ] [ section [ _id "churchList"; _class "pt-table"; _ariaLabel "Church list" ] [
header [] [ locStr s["Actions"] ] div [ _class "row head" ] [
header [] [ locStr s["Name"] ] header [ _class "cell" ] [ locStr s["Actions"] ]
header [] [ locStr s["Location"] ] header [ _class "cell" ] [ locStr s["Name"] ]
header [] [ locStr s["Groups"] ] header [ _class "cell" ] [ locStr s["Location"] ]
header [] [ locStr s["Requests"] ] header [ _class "cell" ] [ locStr s["Groups"] ]
header [] [ locStr s["Users"] ] header [ _class "cell" ] [ locStr s["Requests"] ]
header [] [ locStr s["Interface?"] ] header [ _class "cell" ] [ locStr s["Users"] ]
header [ _class "cell" ] [ locStr s["Interface?"] ]
]
for church in churches do for church in churches do
let churchId = shortGuid church.Id.Value let churchId = shortGuid church.Id.Value
let delAction = $"/church/{churchId}/delete" let delAction = $"/church/{churchId}/delete"
let delPrompt = s["Are you sure 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.",
$"""{s["Church"].Value.ToLower ()} ({church.Name})"""] $"""{s["Church"].Value.ToLower ()} ({church.Name})"""]
div [ _class "row" ] [ div [ _class "row" ] [
div [] [ div [ _class "cell actions" ] [
a [ _href $"/church/{churchId}/edit"; _title s["Edit This Church"].Value ] [ icon "edit" ] a [ _href $"/church/{churchId}/edit"; _title s["Edit This Church"].Value ] [
a [ _href delAction iconSized 18 "edit"
_title s["Delete This Church"].Value ]
_onclick $"return PT.confirmDelete('{delAction}','{delPrompt}')" ] [ a [ _href delAction
icon "delete_forever" _title s["Delete This Church"].Value
_hxPost delAction
_hxConfirm delPrompt.Value ] [
iconSized 18 "delete_forever"
] ]
] ]
div [] [ str church.Name ] div [ _class "cell" ] [ str church.Name ]
div [] [ str church.City; rawText ", "; str church.State ] div [ _class "cell" ] [ str church.City; rawText ", "; str church.State ]
div [ _class "pt-right-text" ] [ rawText (stats[churchId].SmallGroups.ToString "N0") ] div [ _class "cell pt-right-text" ] [ rawText (stats[churchId].SmallGroups.ToString "N0") ]
div [ _class "pt-right-text" ] [ rawText (stats[churchId].PrayerRequests.ToString "N0") ] div [ _class "cell pt-right-text" ] [ rawText (stats[churchId].PrayerRequests.ToString "N0") ]
div [ _class "pt-right-text" ] [ rawText (stats[churchId].Users.ToString "N0") ] div [ _class "cell pt-right-text" ] [ rawText (stats[churchId].Users.ToString "N0") ]
div [ _class "pt-center-text" ] [ locStr s[if church.HasVpsInterface then "Yes" else "No"] ] div [ _class "cell pt-center-text" ] [
locStr s[if church.HasVpsInterface then "Yes" else "No"]
]
] ]
] ]
// table [ _class "pt-table pt-action-table" ] [
// tableHeadings s [ "Actions"; "Name"; "Location"; "Groups"; "Requests"; "Users"; "Interface?" ]
// churches
// |> List.map (fun ch ->
// let chId = shortGuid ch.Id.Value
// let delAction = $"/church/{chId}/delete"
// let delPrompt = s["Are you sure you want to delete this {0}? This action cannot be undone.",
// $"""{s["Church"].Value.ToLower ()} ({ch.Name})"""]
// tr [] [
// td [] [
// a [ _href $"/church/{chId}/edit"; _title s["Edit This Church"].Value ] [ icon "edit" ]
// a [ _href delAction
// _title s["Delete This Church"].Value
// _onclick $"return PT.confirmDelete('{delAction}','{delPrompt}')" ] [
// icon "delete_forever"
// ]
// ]
// td [] [ str ch.Name ]
// td [] [ str ch.City; rawText ", "; str ch.State ]
// td [ _class "pt-right-text" ] [ rawText (stats[chId].SmallGroups.ToString "N0") ]
// td [ _class "pt-right-text" ] [ rawText (stats[chId].PrayerRequests.ToString "N0") ]
// td [ _class "pt-right-text" ] [ rawText (stats[chId].Users.ToString "N0") ]
// td [ _class "pt-center-text" ] [ locStr s[if ch.HasVpsInterface then "Yes" else "No"] ]
// ])
// |> tbody []
// ]
[ div [ _class "pt-center-text" ] [ [ div [ _class "pt-center-text" ] [
br [] br []
a [ _href $"/church/{emptyGuid}/edit"; _title s["Add a New Church"].Value ] [ a [ _href $"/church/{emptyGuid}/edit"; _title s["Add a New Church"].Value ] [
@ -132,8 +111,10 @@ let maintain (churches : Church list) (stats : Map<string, ChurchStats>) ctx vie
br [] br []
] ]
tableSummary churches.Length s tableSummary churches.Length s
chTbl form [ _method "post" ] [
form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ] csrfToken ctx
churchTable
]
] ]
|> Layout.Content.wide |> Layout.Content.wide
|> Layout.standard vi "Maintain Churches" |> Layout.standard vi "Maintain Churches"

View File

@ -141,15 +141,15 @@ module Navigation =
let s = I18N.localizer.Force () let s = I18N.localizer.Force ()
header [ _id "pt-language"; Target.body ] [ header [ _id "pt-language"; Target.body ] [
div [] [ div [] [
span [ _class "u" ] [ locStr s["Language"]; rawText ": " ] span [ _title s["Language"].Value ] [ icon "record_voice_over"; space ]
match langCode () with match langCode () with
| "es" -> | "es" ->
locStr s["Spanish"] strong [] [ locStr s["Spanish"] ]
rawText " &nbsp; &bull; &nbsp; " rawText " &nbsp; &nbsp; "
a [ _href "/language/en" ] [ locStr s["Change to English"] ] a [ _href "/language/en" ] [ locStr s["Change to English"] ]
| _ -> | _ ->
locStr s["English"] strong [] [ locStr s["English"] ]
rawText " &nbsp; &bull; &nbsp; " rawText " &nbsp; &nbsp; "
a [ _href "/language/es" ] [ locStr s["Cambie a Español"] ] a [ _href "/language/es" ] [ locStr s["Cambie a Español"] ]
] ]
match m.Group with match m.Group with

View File

@ -4,6 +4,8 @@ open System
open System.IO open System.IO
open Giraffe open Giraffe
open Giraffe.ViewEngine open Giraffe.ViewEngine
open Giraffe.ViewEngine.Accessibility
open Giraffe.ViewEngine.Htmx
open Microsoft.AspNetCore.Http open Microsoft.AspNetCore.Http
open NodaTime open NodaTime
open PrayerTracker open PrayerTracker
@ -156,10 +158,11 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
let prefs = model.SmallGroup.Preferences let prefs = model.SmallGroup.Preferences
let types = ReferenceList.requestTypeList s |> Map.ofList let types = ReferenceList.requestTypeList s |> Map.ofList
let updReq (req : PrayerRequest) = let updReq (req : PrayerRequest) =
if req.UpdateRequired now prefs.DaysToExpire prefs.LongTermUpdateWeeks then "pt-request-update" else "" if req.UpdateRequired now prefs.DaysToExpire prefs.LongTermUpdateWeeks then "cell pt-request-update" else "cell"
|> _class |> _class
let reqExp (req : PrayerRequest) = let reqExp (req : PrayerRequest) =
_class (if req.IsExpired now prefs.DaysToExpire then "pt-request-expired" else "") _class (if req.IsExpired now prefs.DaysToExpire then "cell pt-request-expired" else "cell")
let vi = AppViewInfo.withScopedStyles [ "#requestList { grid-template-columns: repeat(5, auto); }" ] viewInfo
/// Iterate the sequence once, before we render, so we can get the count of it at the top of the table /// Iterate the sequence once, before we render, so we can get the count of it at the top of the table
let requests = let requests =
model.Requests model.Requests
@ -175,33 +178,34 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
.Value .Value
] ]
|> String.concat "" |> String.concat ""
tr [] [ div [ _class "row" ] [
td [] [ div [ _class "cell actions" ] [
a [ _href $"/prayer-request/{reqId}/edit"; _title l["Edit This Prayer Request"].Value ] [ a [ _href $"/prayer-request/{reqId}/edit"; _title l["Edit This Prayer Request"].Value ] [
icon "edit" iconSized 18 "edit"
] ]
if req.IsExpired now prefs.DaysToExpire then if req.IsExpired now prefs.DaysToExpire then
a [ _href $"/prayer-request/{reqId}/restore" a [ _href $"/prayer-request/{reqId}/restore"
_title l["Restore This Inactive Request"].Value ] [ _title l["Restore This Inactive Request"].Value ] [
icon "visibility" iconSized 18 "visibility"
] ]
else else
a [ _href $"/prayer-request/{reqId}/expire" a [ _href $"/prayer-request/{reqId}/expire"
_title l["Expire This Request Immediately"].Value ] [ _title l["Expire This Request Immediately"].Value ] [
icon "visibility_off" iconSized 18 "visibility_off"
] ]
a [ _href delAction a [ _href delAction
_title l["Delete This Request"].Value _title l["Delete This Request"].Value
_onclick $"return PT.confirmDelete('{delAction}','{delPrompt}')" ] [ _hxPost delAction
icon "delete_forever" _hxConfirm delPrompt ] [
iconSized 18 "delete_forever"
] ]
] ]
td [ updReq req ] [ div [ updReq req ] [
str (req.UpdatedDate.ToString(s["MMMM d, yyyy"].Value, Globalization.CultureInfo.CurrentUICulture)) str (req.UpdatedDate.ToString(s["MMMM d, yyyy"].Value, Globalization.CultureInfo.CurrentUICulture))
] ]
td [] [ locStr types[req.RequestType] ] div [ _class "cell" ] [ locStr types[req.RequestType] ]
td [ reqExp req ] [ str (match req.Requestor with Some r -> r | None -> " ") ] div [ reqExp req ] [ str (match req.Requestor with Some r -> r | None -> " ") ]
td [] [ div [ _class "cell" ] [
match reqText.Length with match reqText.Length with
| len when len < 60 -> rawText reqText | len when len < 60 -> rawText reqText
| _ -> rawText $"{reqText[0..59]}&hellip;" | _ -> rawText $"{reqText[0..59]}&hellip;"
@ -235,9 +239,18 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
match requests.Length with match requests.Length with
| 0 -> () | 0 -> ()
| _ -> | _ ->
table [ _class "pt-table pt-action-table" ] [ form [ _method "post" ] [
tableHeadings s [ "Actions"; "Updated Date"; "Type"; "Requestor"; "Request"] csrfToken ctx
tbody [] requests section [ _id "requestList"; _class "pt-table"; _ariaLabel "Prayer request list" ] [
div [ _class "row head" ] [
header [ _class "cell" ] [ locStr s["Actions"] ]
header [ _class "cell" ] [ locStr s["Updated Date"] ]
header [ _class "cell" ] [ locStr s["Type"] ]
header [ _class "cell" ] [ locStr s["Requestor"] ]
header [ _class "cell" ] [ locStr s["Request"] ]
]
yield! requests
]
] ]
div [ _class "pt-center-text" ] [ div [ _class "pt-center-text" ] [
br [] br []
@ -272,10 +285,9 @@ let maintain (model : MaintainRequests) (ctx : HttpContext) viewInfo =
] ]
| false -> () | false -> ()
] ]
form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ]
] ]
|> Layout.Content.wide |> Layout.Content.wide
|> Layout.standard viewInfo (match model.SearchTerm with Some _ -> "Search Results" | None -> "Maintain Requests") |> Layout.standard vi (match model.SearchTerm with Some _ -> "Search Results" | None -> "Maintain Requests")
/// View for the printable prayer request list /// View for the printable prayer request list

View File

@ -1,6 +1,8 @@
module PrayerTracker.Views.SmallGroup module PrayerTracker.Views.SmallGroup
open Giraffe.ViewEngine open Giraffe.ViewEngine
open Giraffe.ViewEngine.Accessibility
open Giraffe.ViewEngine.Htmx
open Microsoft.Extensions.Localization open Microsoft.Extensions.Localization
open PrayerTracker open PrayerTracker
open PrayerTracker.Entities open PrayerTracker.Entities
@ -179,33 +181,40 @@ let logOn (groups : SmallGroup list) grpId ctx viewInfo =
/// View for the small group maintenance page /// View for the small group maintenance page
let maintain (groups : SmallGroup list) ctx viewInfo = let maintain (groups : SmallGroup list) ctx viewInfo =
let s = I18N.localizer.Force () let s = I18N.localizer.Force ()
let grpTbl = let vi = AppViewInfo.withScopedStyles [ "#groupList { grid-template-columns: repeat(4, auto); }" ] viewInfo
let groupTable =
match groups with match groups with
| [] -> space | [] -> space
| _ -> | _ ->
table [ _class "pt-table pt-action-table" ] [ section [ _id "groupList"; _class "pt-table"; _ariaLabel "Small group list" ] [
tableHeadings s [ "Actions"; "Name"; "Church"; "Time Zone"] div [ _class "row head" ] [
groups header [ _class "cell" ] [ locStr s["Actions"] ]
|> List.map (fun g -> header [ _class "cell" ] [ locStr s["Name"] ]
let grpId = shortGuid g.Id.Value header [ _class "cell" ] [ locStr s["Church"] ]
header [ _class "cell" ] [ locStr s["Time Zone"] ]
]
for group in groups do
let grpId = shortGuid group.Id.Value
let delAction = $"/small-group/{grpId}/delete" let delAction = $"/small-group/{grpId}/delete"
let delPrompt = s["Are you sure 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.",
$"""{s["Small Group"].Value.ToLower ()} ({g.Name})""" ].Value $"""{s["Small Group"].Value.ToLower ()} ({group.Name})""" ].Value
tr [] [ div [ _class "row" ] [
td [] [ div [ _class "cell actions" ] [
a [ _href $"/small-group/{grpId}/edit"; _title s["Edit This Group"].Value ] [ icon "edit" ] a [ _href $"/small-group/{grpId}/edit"; _title s["Edit This Group"].Value ] [
a [ _href delAction iconSized 18 "edit"
_title s["Delete This Group"].Value ]
_onclick $"return PT.confirmDelete('{delAction}','{delPrompt}')" ] [ a [ _href delAction
icon "delete_forever" _title s["Delete This Group"].Value
_hxDelete delAction
_hxConfirm delPrompt ] [
iconSized 18 "delete_forever"
] ]
] ]
td [] [ str g.Name ] div [ _class "cell" ] [ str group.Name ]
td [] [ str g.Church.Name ] div [ _class "cell" ] [ str group.Church.Name ]
td [] [ locStr (TimeZones.name g.Preferences.TimeZoneId s) ] div [ _class "cell" ] [ locStr (TimeZones.name group.Preferences.TimeZoneId s) ]
]) ]
|> tbody []
] ]
[ div [ _class "pt-center-text" ] [ [ div [ _class "pt-center-text" ] [
br [] br []
@ -216,45 +225,54 @@ let maintain (groups : SmallGroup list) ctx viewInfo =
br [] br []
] ]
tableSummary groups.Length s tableSummary groups.Length s
grpTbl form [ _method "post" ] [
form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ] csrfToken ctx
groupTable
]
] ]
|> Layout.Content.standard |> Layout.Content.standard
|> Layout.standard viewInfo "Maintain Groups" |> Layout.standard vi "Maintain Groups"
/// View for the member maintenance page /// View for the member maintenance page
let members (members : Member list) (emailTypes : Map<string, LocalizedString>) ctx viewInfo = let members (members : Member list) (emailTypes : Map<string, LocalizedString>) ctx viewInfo =
let s = I18N.localizer.Force () let s = I18N.localizer.Force ()
let mbrTbl = let vi = AppViewInfo.withScopedStyles [ "#memberList { grid-template-columns: repeat(4, auto); }" ] viewInfo
let memberTable =
match members with match members with
| [] -> space | [] -> space
| _ -> | _ ->
table [ _class "pt-table pt-action-table" ] [ section [ _id "memberList"; _class "pt-table"; _ariaLabel "Small group member list" ] [
tableHeadings s [ "Actions"; "Name"; "E-mail Address"; "Format"] div [ _class "row head" ] [
members header [ _class "cell"] [ locStr s["Actions"] ]
|> List.map (fun mbr -> header [ _class "cell"] [ locStr s["Name"] ]
header [ _class "cell"] [ locStr s["E-mail Address"] ]
header [ _class "cell"] [ locStr s["Format"] ]
]
for mbr in members do
let mbrId = shortGuid mbr.Id.Value let mbrId = shortGuid mbr.Id.Value
let delAction = $"/small-group/member/{mbrId}/delete" let delAction = $"/small-group/member/{mbrId}/delete"
let delPrompt = let delPrompt =
s["Are you sure you want to delete this {0}? This action cannot be undone.", s["group member"]] s["Are you sure you want to delete this {0}? This action cannot be undone.", s["group member"]]
.Value.Replace("?", $" ({mbr.Name})?") .Value.Replace("?", $" ({mbr.Name})?")
tr [] [ div [ _class "row" ] [
td [] [ div [ _class "cell actions" ] [
a [ _href $"/small-group/member/{mbrId}/edit"; _title s["Edit This Group Member"].Value ] [ a [ _href $"/small-group/member/{mbrId}/edit"; _title s["Edit This Group Member"].Value ] [
icon "edit" iconSized 18 "edit"
] ]
a [ _href delAction a [ _href delAction
_title s["Delete This Group Member"].Value _title s["Delete This Group Member"].Value
_onclick $"return PT.confirmDelete('{delAction}','{delPrompt}')" ] [ _hxPost delAction
icon "delete_forever" _hxConfirm delPrompt ] [
iconSized 18 "delete_forever"
] ]
] ]
td [] [ str mbr.Name ] div [ _class "cell" ] [ str mbr.Name ]
td [] [ str mbr.Email ] div [ _class "cell" ] [ str mbr.Email ]
td [] [ locStr emailTypes[defaultArg (mbr.Format |> Option.map EmailFormat.toCode) ""] ] div [ _class "cell" ] [
]) locStr emailTypes[defaultArg (mbr.Format |> Option.map EmailFormat.toCode) ""]
|> tbody [] ]
]
] ]
[ div [ _class"pt-center-text" ] [ [ div [ _class"pt-center-text" ] [
br [] br []
@ -265,15 +283,15 @@ let members (members : Member list) (emailTypes : Map<string, LocalizedString>)
br [] br []
] ]
tableSummary members.Length s tableSummary members.Length s
mbrTbl form [ _method "post" ] [
form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ] csrfToken ctx
memberTable
]
] ]
|> Layout.Content.standard |> Layout.Content.standard
|> Layout.standard viewInfo "Maintain Group Members" |> Layout.standard vi "Maintain Group Members"
open Giraffe.ViewEngine.Accessibility
/// View for the small group overview page /// View for the small group overview page
let overview model viewInfo = let overview model viewInfo =
let s = I18N.localizer.Force () let s = I18N.localizer.Force ()

View File

@ -1,6 +1,8 @@
module PrayerTracker.Views.User module PrayerTracker.Views.User
open Giraffe.ViewEngine open Giraffe.ViewEngine
open Giraffe.ViewEngine.Accessibility
open Giraffe.ViewEngine.Htmx
open PrayerTracker open PrayerTracker
open PrayerTracker.ViewModels open PrayerTracker.ViewModels
@ -8,37 +10,33 @@ open PrayerTracker.ViewModels
let assignGroups model groups curGroups ctx viewInfo = let assignGroups model groups curGroups ctx viewInfo =
let s = I18N.localizer.Force () let s = I18N.localizer.Force ()
let pageTitle = sprintf "%s %A" model.UserName s["Assign Groups"] let pageTitle = sprintf "%s %A" model.UserName s["Assign Groups"]
let vi = AppViewInfo.withScopedStyles [ "#groupList { grid-template-columns: auto; }" ] viewInfo
form [ _action "/user/small-groups/save"; _method "post"; _class "pt-center-columns"; Target.content ] [ form [ _action "/user/small-groups/save"; _method "post"; _class "pt-center-columns"; Target.content ] [
csrfToken ctx csrfToken ctx
inputField "hidden" (nameof model.UserId) model.UserId [] inputField "hidden" (nameof model.UserId) model.UserId []
inputField "hidden" (nameof model.UserName) model.UserName [] inputField "hidden" (nameof model.UserName) model.UserName []
table [ _class "pt-table" ] [ section [ _id "groupList"; _class "pt-table"; _ariaLabel "Assigned small groups" ] [
thead [] [ div [ _class "row head" ] [
tr [] [ header [ _class "cell" ] [ locStr s["Group"] ]
th [] [ rawText "&nbsp;" ]
th [] [ locStr s["Group"] ]
]
] ]
groups for groupId, name in groups do
|> List.map (fun (grpId, grpName) -> div [ _class "row" ] [
let inputId = $"id-{grpId}" div [ _class "cell" ] [
tr [] [
td [] [
input [ _type "checkbox" input [ _type "checkbox"
_name (nameof model.SmallGroups) _name (nameof model.SmallGroups)
_id inputId _id groupId
_value grpId _value groupId
if List.contains grpId curGroups then _checked ] if List.contains groupId curGroups then _checked ]
space
label [ _for groupId ] [ str name ]
] ]
td [] [ label [ _for inputId ] [ str grpName ] ] ]
])
|> tbody []
] ]
div [ _fieldRow ] [ submit [] "save" s["Save Group Assignments"] ] div [ _fieldRow ] [ submit [] "save" s["Save Group Assignments"] ]
] ]
|> List.singleton |> List.singleton
|> Layout.Content.standard |> Layout.Content.standard
|> Layout.standard viewInfo pageTitle |> Layout.standard vi pageTitle
/// View for the password change page /// View for the password change page
@ -186,40 +184,45 @@ open PrayerTracker.Entities
/// View for the user maintenance page /// View for the user maintenance page
let maintain (users : User list) ctx viewInfo = let maintain (users : User list) ctx viewInfo =
let s = I18N.localizer.Force () let s = I18N.localizer.Force ()
let usrTbl = let vi = AppViewInfo.withScopedStyles [ "#userList { grid-template-columns: repeat(4, auto); }" ] viewInfo
let userTable =
match users with match users with
| [] -> space | [] -> space
| _ -> | _ ->
table [ _class "pt-table pt-action-table" ] [ section [ _id "userList"; _class "pt-table"; _ariaLabel "User list" ] [
tableHeadings s [ "Actions"; "Name"; "Last Seen"; "Admin?" ] div [ _class "row head" ] [
users header [ _class "cell" ] [ locStr s["Actions"] ]
|> List.map (fun user -> header [ _class "cell" ] [ locStr s["Name"] ]
header [ _class "cell" ] [ locStr s["Last Seen"] ]
header [ _class "cell" ] [ locStr s["Admin?"] ]
]
for user in users do
let userId = shortGuid user.Id.Value let userId = shortGuid user.Id.Value
let delAction = $"/user/{userId}/delete" let delAction = $"/user/{userId}/delete"
let delPrompt = s["Are you sure 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.",
$"""{s["User"].Value.ToLower ()} ({user.Name})"""].Value $"""{s["User"].Value.ToLower ()} ({user.Name})"""].Value
tr [] [ div [ _class "row" ] [
td [] [ div [ _class "cell actions" ] [
a [ _href $"/user/{userId}/edit"; _title s["Edit This User"].Value ] [ icon "edit" ] a [ _href $"/user/{userId}/edit"; _title s["Edit This User"].Value ] [ iconSized 18 "edit" ]
a [ _href $"/user/{userId}/small-groups"; _title s["Assign Groups to This User"].Value ] [ a [ _href $"/user/{userId}/small-groups"; _title s["Assign Groups to This User"].Value ] [
icon "group" iconSized 18 "group"
] ]
a [ _href delAction a [ _href delAction
_title s["Delete This User"].Value _title s["Delete This User"].Value
_onclick $"return PT.confirmDelete('{delAction}','{delPrompt}')" ] [ _hxPost delAction
icon "delete_forever" _hxConfirm delPrompt ] [
iconSized 18 "delete_forever"
] ]
] ]
td [] [ str user.Name ] div [ _class "cell" ] [ str user.Name ]
td [] [ div [ _class "cell" ] [
str (match user.LastSeen with Some dt -> dt.ToString s["MMMM d, yyyy"] | None -> "--") str (match user.LastSeen with Some dt -> dt.ToString s["MMMM d, yyyy"] | None -> "--")
] ]
td [ _class "pt-center-text" ] [ div [ _class "cell pt-center-text" ] [
if user.IsAdmin then strong [] [ locStr s["Yes"] ] else locStr s["No"] if user.IsAdmin then strong [] [ locStr s["Yes"] ] else locStr s["No"]
] ]
]) ]
|> tbody []
] ]
[ div [ _class "pt-center-text" ] [ [ div [ _class "pt-center-text" ] [
br [] br []
@ -230,8 +233,10 @@ let maintain (users : User list) ctx viewInfo =
br [] br []
] ]
tableSummary users.Length s tableSummary users.Length s
usrTbl form [ _method "post" ] [
form [ _id "DeleteForm"; _action ""; _method "post" ] [ csrfToken ctx ] csrfToken ctx
userTable
]
] ]
|> Layout.Content.standard |> Layout.Content.standard
|> Layout.standard viewInfo "Maintain Users" |> Layout.standard vi "Maintain Users"

View File

@ -263,54 +263,48 @@ footer a:hover {
text-align: right; text-align: right;
} }
.pt-data-list { .pt-table {
display: grid; display: grid;
justify-content: center; justify-content: center;
grid-row-gap: .5rem;
grid-column-gap: 1rem;
} }
.pt-data-list .row { .pt-table .row.head,
.pt-table .row {
display: contents; display: contents;
} }
.pt-data-list .row:hover > * { .pt-table .row:hover > * {
background-color: purple;
}
.pt-table {
margin: auto;
border-collapse: collapse;
}
.pt-table thead {
background-image: linear-gradient(to bottom, var(--dark), var(--lighter-dark));
color: white;
}
.pt-table tbody tr:hover {
background-color: #eee; background-color: #eee;
} }
.pt-table tr th, .pt-table .row.head .cell {
.pt-table tr td { background-image: linear-gradient(to bottom, var(--dark), var(--lighter-dark));
font-weight: bold;
color: white;
text-align: center;
font-size: .85rem;
}
.pt-table .row.head .cell:first-of-type {
border-top-left-radius: .5rem;
}
.pt-table .row.head .cell:last-of-type {
border-top-right-radius: .5rem;
}
.pt-table .cell {
padding: .25rem .5rem; padding: .25rem .5rem;
border-bottom: dotted 1px var(--lighter-dark);
} }
.pt-table tbody tr td { .pt-table .cell.actions {
border-bottom: solid 1px var(--lighter-dark); border-bottom-color: var(--background);
} }
.pt-action-table tbody tr td:first-child { .pt-table .cell.actions a {
white-space: nowrap;
border-bottom: 0;
}
.pt-action-table tbody tr td:first-child a {
background-color: gray; background-color: gray;
color: white; color: white;
border-radius: .5rem; border-radius: .5rem;
padding: .125rem .5rem .5rem .5rem; padding: 0 .5rem .25rem;
margin: 0 .125rem; margin: 0 .125rem;
} }
.pt-action-table tbody tr:hover td:first-child a { .pt-table .row:hover .cell.actions a {
background-color: var(--dark); background-color: var(--dark);
} }
.pt-action-table tbody tr td:first-child a:hover {
border-bottom: 0;
}
/* TODO: Figure out nice CSS transitions for these; these don't work */ /* TODO: Figure out nice CSS transitions for these; these don't work */
.pt-fadeable { .pt-fadeable {
height: 0; height: 0;