v1.0.5.3
Activity index, Db schema validation.
This commit is contained in:
@@ -159,6 +159,9 @@
|
||||
<data name="dialog.title.signout" xml:space="preserve">
|
||||
<value>Sign-out</value>
|
||||
</data>
|
||||
<data name="home.caption.activities" xml:space="preserve">
|
||||
<value>Activities</value>
|
||||
</data>
|
||||
<data name="home.caption.application" xml:space="preserve">
|
||||
<value>Online</value>
|
||||
</data>
|
||||
@@ -216,6 +219,9 @@
|
||||
<data name="label.delete" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="label.effective" xml:space="preserve">
|
||||
<value>Effective</value>
|
||||
</data>
|
||||
<data name="label.email.address" xml:space="preserve">
|
||||
<value>Email address</value>
|
||||
</data>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Bus;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Saxonite.Net.Core.Web.Base.App;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Saxonite.Net.Core.Web.Online.App
|
||||
@@ -32,6 +36,54 @@ namespace Saxonite.Net.Core.Web.Online.App
|
||||
/// <summary>User - JSON deserialisation route.</summary>
|
||||
public new UserData User { get { return base.User; } set { SetUser(value); } }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// User Profile.
|
||||
// Protected constructors & setters for JSON deserialisation.
|
||||
public class UserProfileData
|
||||
{
|
||||
public UserProfileData() { }
|
||||
// Sports.
|
||||
public class SportData
|
||||
{
|
||||
public SportData(SysId sportId, SaStatus status)
|
||||
{
|
||||
SportId = sportId;
|
||||
Status = status;
|
||||
}
|
||||
public SysId SportId { get; protected set; }
|
||||
public SaStatus Status { get; protected set; }
|
||||
}
|
||||
private class SportData_Online : SportData
|
||||
{
|
||||
// Internal class to allow values to be updated.
|
||||
public SportData_Online(SysId sportId, SaStatus status) : base(sportId, status) { }
|
||||
public SportData_Online(SportData sportData) : base(sportData.SportId, sportData.Status) { }
|
||||
public void SetSportId(SysId sportId) { SportId = sportId; }
|
||||
public void SetStatus(SaStatus status) { Status = status; }
|
||||
}
|
||||
[JsonIgnore]
|
||||
private readonly List<SportData_Online> sports = new List<SportData_Online>();
|
||||
[JsonIgnore]
|
||||
public int SportCount { get { return sports.Count; } }
|
||||
public List<SportData> Sports { get { return new List<SportData>(sports); } protected set { sports.Clear(); sports.AddRange(value.Select(item => new SportData_Online(item))); } }
|
||||
public void AddSport(SysId sportId, SaStatus status)
|
||||
{
|
||||
if (!SysId.IsValid(sportId)) throw new NotSupportedException("Session Profile Sport must have a valid Id");
|
||||
SportData_Online sport = this.sports.Find(item => item.SportId == sportId);
|
||||
if (sport != null)
|
||||
{
|
||||
sport.SetStatus(status);
|
||||
}
|
||||
else
|
||||
this.sports.Add(new SportData_Online(sportId, status));
|
||||
}
|
||||
public void ClearSports()
|
||||
{
|
||||
this.sports.Clear();
|
||||
}
|
||||
}
|
||||
public UserProfileData Profile { get; protected set; } = new UserProfileData();
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Filter properties.
|
||||
public class FilterData
|
||||
@@ -69,6 +121,25 @@ namespace Saxonite.Net.Core.Web.Online.App
|
||||
}
|
||||
public FilterData Filter { get; private set; } = new FilterData();
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// JSON Deserialisation.
|
||||
[JsonExtensionData]
|
||||
#pragma warning disable IDE0044 // Add readonly modifier
|
||||
private IDictionary<string, JToken> _additionalData;
|
||||
#pragma warning restore IDE0044 // Add readonly modifier
|
||||
[OnDeserialized]
|
||||
private void OnDeserialized(StreamingContext context)
|
||||
{
|
||||
if (_additionalData != null)
|
||||
{
|
||||
foreach (String name in _additionalData.Keys)
|
||||
{
|
||||
JToken value = _additionalData[name];
|
||||
SaLogger.Info("OnDeserialized Name: " + name + ". Value: " + value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
}
|
||||
//=======================================================================
|
||||
|
||||
@@ -5,6 +5,7 @@ using Sarand.Net.Standard.Base.Bus;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Dbas;
|
||||
using Saxonite.Net.Core.Web.Base.App;
|
||||
using Saxonite.Net.Core.Web.Online.App;
|
||||
using Saxonite.Net.Core.Web.Online.Models;
|
||||
using Saxonite.Net.Standard.Base.App;
|
||||
using Saxonite.Net.Standard.Base.Bus;
|
||||
@@ -25,26 +26,19 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
|
||||
// Construction.
|
||||
public ActivityController(SaxLocaliser localiser, ILogger<HomeController> logger) :
|
||||
base(localiser, logger)
|
||||
{}
|
||||
{ }
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Data definitions.
|
||||
public class ActivityResponseData
|
||||
{
|
||||
public short ItemType { get; set; }
|
||||
// NB Need to mask the Item Id by storing with XRef to alternative generated value in User's session.
|
||||
public int ItemIdx { get; set; }
|
||||
public String Caption{ get; set; }
|
||||
}
|
||||
public class ArticleActivityResponseData : ActivityResponseData
|
||||
{
|
||||
public String Headline { get; set; }
|
||||
public String Summary { get; set; }
|
||||
}
|
||||
public class OpportunityActivityResponseData : ActivityResponseData
|
||||
{
|
||||
public String Position { get; set; }
|
||||
public String Outline { get; set; }
|
||||
public long ItemIdx { get; set; }
|
||||
public String Caption { get; set; }
|
||||
public String Blurb { get; set; }
|
||||
public String Sport { get; set; }
|
||||
public String Region { get; set; }
|
||||
public String PublishedOnFmt{ get; set; }
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
@@ -52,7 +46,7 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
|
||||
//-------------------------------------------------------------------
|
||||
public class AjaxResponse_ActivityList : AjaxResponse_Base
|
||||
{
|
||||
public List<SaxModel_Online.RegionData> Regions { get; } = new List<SaxModel_Online.RegionData>();
|
||||
public List<ActivityResponseData> Activities { get; } = new List<ActivityResponseData>();
|
||||
}
|
||||
//-------------------------------------------------------------------
|
||||
[HttpPost]
|
||||
@@ -70,13 +64,34 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
|
||||
return Forbid();
|
||||
if (!ValidUserSession)
|
||||
return GetAjaxResult_InvalidSession(responseData);
|
||||
SaxSessionData_Online sessionData = SessionData_Online;
|
||||
|
||||
// User context.
|
||||
SaxActivityConsole.IndexContext indexContext = new SaxActivityConsole.IndexContext();
|
||||
|
||||
// Regions.
|
||||
if (SysId.IsValid(sessionData.User.RegionIdProvince))
|
||||
indexContext.RegionIds.Add(sessionData.User.RegionIdProvince);
|
||||
else if (SysId.IsValid(sessionData.User.RegionIdCountry))
|
||||
indexContext.RegionIds.Add(sessionData.User.RegionIdCountry);
|
||||
|
||||
// Sports.
|
||||
indexContext.SportIds.AddRange(sessionData.Profile.Sports.Select(item => item.SportId));
|
||||
|
||||
// Populate activities.
|
||||
SaxActivityConsole.IndexContext indexContext = new SaxActivityConsole.IndexContext();
|
||||
SaxActivityConsole.IndexResults indexResults = ActivityConsole.QueryIndex(indexContext);
|
||||
foreach (SaxActivityConsole.IndexResults.ItemData itemData in indexResults.Items)
|
||||
foreach (SaxActivityConsole.IndexResults.ActivityData itemData in indexResults.Items)
|
||||
{
|
||||
|
||||
ActivityResponseData activityData = new ActivityResponseData();
|
||||
activityData.ItemType = itemData.ItemId.Type;
|
||||
// ToDo Mask activity index Id with cross-reference stored in session data.
|
||||
activityData.ItemIdx = SysId.ToLong(itemData.ItemId.Id);
|
||||
activityData.Caption = itemData.Caption;
|
||||
activityData.Blurb = itemData.Blurb;
|
||||
activityData.Sport = Localiser.GetSportName(itemData.SportId, itemData.SportName);
|
||||
activityData.Region = Localiser.GetRegionName(itemData.RegionId, itemData.RegionName);
|
||||
activityData.PublishedOnFmt = Localiser.FormatDate(itemData.EffectiveOn);
|
||||
responseData.Activities.Add(activityData);
|
||||
}
|
||||
|
||||
// Complete response.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Sarand.Net.Standard.Base.App;
|
||||
using Sarand.Net.Standard.Base.Bus;
|
||||
using Sarand.Net.Standard.Base.Core;
|
||||
using Sarand.Net.Standard.Base.Dbas;
|
||||
using Sarand.Net.Standard.Base.Helpers;
|
||||
using Sarand.Net.Standard.Base.Utils;
|
||||
using Saxonite.Net.Core.Web.Base.App;
|
||||
@@ -86,6 +87,7 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
|
||||
|
||||
// Create session.
|
||||
OpenUserSession(SessionContext, userAccount, userRoles);
|
||||
RefreshSessionProfileSports();
|
||||
|
||||
// Send the 'Keep Logged In' cookie if requested.
|
||||
if (userAccount.KeepLoggedIn_Online)
|
||||
@@ -94,6 +96,39 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
|
||||
return true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Populate the user's profile sports to the session.</summary>
|
||||
protected bool RefreshSessionProfileSports()
|
||||
{
|
||||
String fn = GetType().Name + ".RefreshSessionProfileSports";
|
||||
SaxSessionData_Online sessionData = SessionData_Online;
|
||||
SysId userAccountId = sessionData?.User.UserAccountId ?? null;
|
||||
if (!SysId.IsValid(userAccountId)) return false;
|
||||
sessionData.Profile.ClearSports();
|
||||
|
||||
// Read discrete values to avoid loading the entire UserSport records which can be substantial.
|
||||
ColumnDfn colSportId = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.SportId);
|
||||
ColumnDfn colSportName = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.Sport_Name);
|
||||
ColumnDfn colStatus = AccountMgr.GetUserSportColumnDfn(SaxUserSport.Column.Status);
|
||||
|
||||
SaxUserSport.Params prmUserSport = new SaxUserSport.Params();
|
||||
prmUserSport.UserAccountIdsIncluded.Add(userAccountId);
|
||||
prmUserSport.StatusCat = SaxUserSport.StatusCat.ActiveOnly;
|
||||
using (DbResultSet resultSet = AccountMgr.ExecuteUserSportQuery(new List<ColumnDfn>() { colSportId, colSportName, colStatus }, prmUserSport))
|
||||
{
|
||||
while (resultSet != null && resultSet.NextRecord())
|
||||
{
|
||||
NamedValue.Map values = resultSet.GetRecordValues();
|
||||
SysId sportId = new SysId(values.GetLong(colSportId.DataName));
|
||||
String sportName = values.GetString(colSportName.DataName);
|
||||
SaStatus status = SacsSystem.GetStatusForCode(values.GetString(colStatus.DataName));
|
||||
sessionData.Profile.AddSport(sportId, status);
|
||||
}
|
||||
}
|
||||
SaLogger.Debug(fn+" Profile Sports: " + sessionData.Profile.SportCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
/// <summary>Start a new user session on sign-in.</summary>
|
||||
protected bool OpenUserSession(SaxSessionContext sessionContext, SaxUserAccount userAccount, List<SaxUserRole> userRoles)
|
||||
|
||||
@@ -56,9 +56,7 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
|
||||
|
||||
String errorMessage = "";
|
||||
if (!SignUserIn(userAccount, AccountMgr.GetRolesForUser(userAccount.Id), model.KeepMeSignedIn, ref errorMessage))
|
||||
{
|
||||
return Json(responseData.SetControlError(nameof(model.EmailAddress), errorMessage));
|
||||
}
|
||||
|
||||
// Construct response.
|
||||
responseData.Success = SessionData_Online?.ValidUserSession ?? false;
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace Saxonite.Net.Core.Web.Online.Models
|
||||
public Model_Home(SaxLocaliser localiser) :
|
||||
base(localiser)
|
||||
{
|
||||
AdditionalFiles_CSS.Add("css/sax-home.css");
|
||||
AdditionalFiles_JS.Add("js/sax-home.js");
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
@@ -4,6 +4,55 @@
|
||||
ViewData["Title"] = "Home";
|
||||
}
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-4">@Localiser["home.caption.welcome"]</h1>
|
||||
</div>
|
||||
@if (Model.AwaitingLogin)
|
||||
{
|
||||
<div class="text-center">
|
||||
<h1 class="display-4">@Localiser["home.caption.welcome"]</h1>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="sax-panel-filter">
|
||||
<div class="sax-header row">
|
||||
<div class="col"><div class="sax-caption">@Localiser["home.caption.activities"]</div></div>
|
||||
<div class="fixed row">
|
||||
</div>
|
||||
</div>
|
||||
<div class="sax-content">
|
||||
@using (@Html.BeginForm(FormMethod.Post, new { @id = "frmActivityFilter", @class = "sax-form", @action = "#", @method = "POST", onsubmit = "return false;" }))
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-sm-4"><div class="sax-label text-left text-sm-right">@Localiser["label.search"]</div></div>
|
||||
<div class="col-sm-8"><input type="search" name="Filter.Activity" id="ifActivityFilter" class="ActivityFilter sax-input" maxlength="64" /></div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sax-panel-results">
|
||||
<div class="sax-panel-messages"></div>
|
||||
<div class="sax-panel-item-list"></div>
|
||||
<div class="sax-panel-item-template">
|
||||
<div class="sax-panel-item Activity">
|
||||
<div class="sax-header row">
|
||||
<div class="col">
|
||||
<div class="sax-caption"><div class="sax-action ActivityCaption"></div></div>
|
||||
</div>
|
||||
<div class="fixed sax-actions">
|
||||
</div>
|
||||
</div>
|
||||
<div class="sax-content">
|
||||
<div class="row">
|
||||
<div class="col-sm-12"><div class="ActivityBlurb"></div></div>
|
||||
<div class="col-sm-6">
|
||||
<div class="sax-note">
|
||||
<span class="ActivitySport"></span> - <span class="ActivityRegion"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 right"><div class="sax-note ActivityPublished">@Localiser["label.published"]: <span class="ActivityPublishedOn"></span></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -122,29 +122,29 @@
|
||||
border-bottom: solid 1px #979797;
|
||||
}
|
||||
|
||||
.sax-panel-item .sax-caption
|
||||
.sax-panel-item > .sax-header .sax-caption
|
||||
{
|
||||
margin-top: 4px;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.sax-panel-item .sax-header .sax-action
|
||||
.sax-panel-item > .sax-header .sax-action
|
||||
{
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.sax-panel-item .sax-header .sax-caption .sax-action:hover
|
||||
.sax-panel-item > .sax-header .sax-caption .sax-action:hover
|
||||
{
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.sax-panel-item .sax-header .sax-actions .sax-action
|
||||
.sax-panel-item > .sax-header .sax-actions .sax-action
|
||||
{
|
||||
margin: 2px 0 0 8px;
|
||||
}
|
||||
|
||||
.sax-panel-item .sax-header .sax-actions .sax-action:hover
|
||||
.sax-panel-item > .sax-header .sax-actions .sax-action:hover
|
||||
{
|
||||
background-color: #c0c0c0;
|
||||
box-shadow: 0 0 4px 2px #c0c0c080;
|
||||
@@ -156,6 +156,11 @@
|
||||
padding: 4px 4px;
|
||||
}
|
||||
|
||||
.sax-panel-item .sax-note
|
||||
{
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Profile Sports */
|
||||
|
||||
.sax-panel-item.ProfileSport,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
/*
|
||||
Home stylesheet.
|
||||
*/
|
||||
|
||||
|
||||
/* The End */
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Home scripts.
|
||||
*/
|
||||
|
||||
(function (win, doc, undef)
|
||||
{
|
||||
"use strict";
|
||||
|
||||
//--------------------------------------------------------------
|
||||
sax.refreshResults = function (delay)
|
||||
{
|
||||
var $results = $('.sax-panel-results');
|
||||
if (!sax.awaitingRefreshResponse)
|
||||
{
|
||||
var $items = $('.sax-panel-item-list', $results).fadeOut(200).empty();
|
||||
var $msgs = $('.sax-panel-messages', $results).hide().empty().append($('<div class="sax-message">' + sax.msg.dataQuerying + '</div>')).fadeIn(200);
|
||||
}
|
||||
sax.ajaxRefreshResults = sax.ajaxRefreshResults || { url: sax.url.base + 'Activity/Ajax/GetList', formId: 'frmActivityFilter', callbackFn: sax.doRefreshResults };
|
||||
sax.doAjaxRequest(sax.ajaxRefreshResults, delay || 50);
|
||||
sax.awaitingRefreshResponse = true;
|
||||
}
|
||||
sax.doRefreshResults = function (response)
|
||||
{
|
||||
sax.debug('doRefreshResults', response);
|
||||
delete sax.awaitingRefreshResponse;
|
||||
var $results = $('.sax-panel-results');
|
||||
var $msgs = $('.sax-panel-messages', $results).hide().empty();
|
||||
var $items = $('.sax-panel-item-list', $results).hide().empty();
|
||||
var $tmpl = $('.sax-panel-item-template .sax-panel-item', $results);
|
||||
if (!response.success) return;
|
||||
var items = response.activities;
|
||||
if (!items.length)
|
||||
return $msgs.append($('<div class="sax-message">' + sax.msg.dataNoneFound + '</div>')).fadeIn(500);
|
||||
for (let itemIdx = 0; itemIdx < items.length; itemIdx++)
|
||||
{
|
||||
let item = items[itemIdx];
|
||||
let $item = $tmpl.clone();
|
||||
sax.populateItem($item, item);
|
||||
$items.append($item);
|
||||
}
|
||||
$items.fadeIn(50);
|
||||
}
|
||||
sax.populateItem = function ($item, item)
|
||||
{
|
||||
if (!item) return;
|
||||
$item.prop('id', sax.itemIdPrefix + item.id);
|
||||
$('.ActivityCaption', $item).text(item.caption);
|
||||
$('.ActivityBlurb', $item).text(item.blurb);
|
||||
$('.ActivitySport', $item).text(item.sport);
|
||||
$('.ActivityRegion', $item).text(item.region);
|
||||
$('.ActivityPublishedOn', $item).text(item.publishedOnFmt);
|
||||
$item.off('click').on('click', function () { sax.expandItem(item.id) })
|
||||
$('.sax-panel-results .sax-panel-messages').hide().empty();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
sax.expandItem = function (id)
|
||||
{
|
||||
sax.debug('expandItem(' + id + ')');
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------
|
||||
$(function ()
|
||||
{
|
||||
setTimeout(sax.refreshResults, 250);
|
||||
});
|
||||
|
||||
}(window, window.document));
|
||||
|
||||
/* The End */
|
||||
@@ -49,7 +49,6 @@
|
||||
sax.populateItem($item, item);
|
||||
$items.append($item);
|
||||
}
|
||||
$('.sax-action.sax-item-update', $items).toggle(sax.data.userCanManageOpportunities);
|
||||
$items.fadeIn(50);
|
||||
}
|
||||
sax.populateItem = function ($item, item)
|
||||
@@ -67,7 +66,8 @@
|
||||
$('.OpportunityExpiresOn', $item).text(item.expiresOnFmt);
|
||||
$('.OpportunityExpires', $item).toggle(!!item.expiresOnFmt);
|
||||
$('.OpportunityStatusDesc', $item).text(item.statusDesc);
|
||||
$('.sax-caption .sax-action, .sax-actions .sax-action.sax-item-update', $item).off('click').on('click', function () { sax.updateItem(item.id) });
|
||||
if (sax.data.userCanManageOpportunities)
|
||||
$('.sax-caption .sax-action, .sax-actions .sax-action.sax-item-update', $item).off('click').on('click', function () { sax.updateItem(item.id) }).removeClass('hidden');
|
||||
$item.off('dblclick').on('dblclick', function () { sax.updateItem(item.id) });
|
||||
$('.sax-panel-results .sax-panel-messages').hide().empty();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user