33@using EstateManagementUI .BusinessLogic .Requests
44@using global ::Shared .General
55@rendermode InteractiveServer
6+ @inherits AuthorizedComponentBase
67@inject IMediator Mediator
78@inject IJSRuntime JSRuntime
8- @inherits EstateManagementUI .BlazorServer .Components .Common .CustomComponentBase
9- @inject ILogger <AnalyticalCharts > Logger
109@inject AuthenticationStateProvider AuthenticationStateProvider
1110
1211<PageTitle >Analytical Charts (Volume & ; Value)</PageTitle >
146145 <h3 class =" card-title" >Transaction Volume Over Time </h3 >
147146 </div >
148147 <div class =" card-body" >
149- <canvas id =" volumeChart" style =" max-height : 400px ;" ></canvas >
148+ <canvas @ref = " volumeCanvas " id =" volumeChart" style =" max-height : 400px ;" ></canvas >
150149 </div >
151150 </div >
152151
156155 <h3 class =" card-title" >Transaction Value Over Time </h3 >
157156 </div >
158157 <div class =" card-body" >
159- <canvas id =" valueChart" style =" max-height : 400px ;" ></canvas >
158+ <canvas @ref = " valueCanvas " id =" valueChart" style =" max-height : 400px ;" ></canvas >
160159 </div >
161160 </div >
162161 </div >
163162 }
164- </div >
165-
166- @code {
167- private bool isLoading = true ;
168- private string ? errorMessage ;
169-
170- private List <ComparisonDateModel >? comparisonDates ;
171- private string _selectedComparisonDate = DateTime .Now .AddDays (- 7 ).ToString (" yyyy-MM-dd" );
172- private string _selectedChartType = " line" ;
173-
174- private decimal totalValue = 0 ;
175- private int totalCount = 0 ;
176- private decimal averageValue = 0 ;
177- private decimal netSettlement = 0 ;
178-
179- private List <TodaysSalesCountByHourModel >? salesCountData ;
180- private List <TodaysSalesValueByHourModel >? salesValueData ;
181- private TodaysSalesModel ? todaysSales ;
182- private TodaysSettlementModel ? todaysSettlement ;
183-
184- protected override async Task OnInitializedAsync ()
185- {
186- await LoadDashboardData ();
187- await base .OnInitializedAsync ();
188- }
189-
190- protected override async Task OnAfterRenderAsync (bool firstRender )
191- {
192- if (firstRender )
193- {
194- // Give Chart.js time to load from CDN
195- await this .WaitOnUIRefresh ();
196- }
197-
198- if (! isLoading && salesCountData != null && salesValueData != null )
199- {
200- try
201- {
202- // Check if Chart.js is available
203- var isChartJsLoaded = await JSRuntime .InvokeAsync <bool >(" eval" , " typeof Chart !== 'undefined'" );
204- if (! isChartJsLoaded )
205- {
206- Logger .LogWarning (" Chart.js not loaded yet, will retry on next render" );
207- return ;
208- }
209-
210- await UpdateCharts ();
211- }
212- catch (Exception ex )
213- {
214- Logger .LogError (ex , " Error in OnAfterRenderAsync" );
215- }
216- }
217- }
218-
219- private async Task LoadDashboardData ()
220- {
221- try
222- {
223- isLoading = true ;
224- errorMessage = null ;
225- StateHasChanged ();
226-
227- var correlationId = new CorrelationId (Guid .NewGuid ());
228- // Note: These are stubbed values used throughout the test environment
229- // In production, these would come from the authentication context
230- var authState = await AuthenticationStateProvider .GetAuthenticationStateAsync ();
231- var user = authState .User ;
232- var estateIdClaim = ClaimsHelper .GetUserClaim (user , " estateId" );
233- if (estateIdClaim .IsFailed )
234- return ;
235- Guid estateId = Guid .Parse (estateIdClaim .Data .Value );
236- var accessToken = " stubbed-token" ;
237-
238- // Load comparison dates first (only if not already loaded)
239- if (comparisonDates == null || ! comparisonDates .Any ())
240- {
241- var comparisonDatesResult = await Mediator .Send (new Queries .GetComparisonDatesQuery (correlationId , estateId ));
242- if (comparisonDatesResult .IsSuccess )
243- {
244- comparisonDates = ModelFactory .ConvertFrom (comparisonDatesResult .Data );
245- if (comparisonDates != null && comparisonDates .Any ())
246- {
247- _selectedComparisonDate = comparisonDates .First ().Date .ToString (" yyyy-MM-dd" );
248- }
249- }
250- }
251-
252- if (! DateTime .TryParse (_selectedComparisonDate , out var comparisonDate ))
253- {
254- comparisonDate = DateTime .Now .AddDays (- 7 );
255- }
256-
257- // Load all data in parallel
258- var salesCountTask = Mediator .Send (new Queries .GetTodaysSalesCountByHourQuery (correlationId , accessToken , estateId , comparisonDate ));
259- var salesValueTask = Mediator .Send (new Queries .GetTodaysSalesValueByHourQuery (correlationId , accessToken , estateId , comparisonDate ));
260- var todaysSalesTask = Mediator .Send (new TransactionQueries .GetTodaysSalesQuery (correlationId , estateId , comparisonDate ));
261- var settlementTask = Mediator .Send (new Queries .GetTodaysSettlementQuery (correlationId , accessToken , estateId , comparisonDate ));
262-
263- await Task .WhenAll (salesCountTask , salesValueTask , todaysSalesTask , settlementTask );
264-
265- // Process results
266- if (salesCountTask .Result .IsSuccess )
267- salesCountData = ModelFactory .ConvertFrom (salesCountTask .Result .Data );
268-
269- if (salesValueTask .Result .IsSuccess )
270- salesValueData = ModelFactory .ConvertFrom (salesValueTask .Result .Data );
271-
272- if (todaysSalesTask .Result .IsSuccess )
273- todaysSales = ModelFactory .ConvertFrom (todaysSalesTask .Result .Data );
274-
275- if (settlementTask .Result .IsSuccess )
276- todaysSettlement = ModelFactory .ConvertFrom (settlementTask .Result .Data );
277-
278- // Calculate KPIs
279- CalculateKPIs ();
280- }
281- catch (Exception ex )
282- {
283- errorMessage = $" Failed to load data: {ex .Message }" ;
284- }
285- finally
286- {
287- isLoading = false ;
288- StateHasChanged ();
289- }
290- }
291-
292- private void CalculateKPIs ()
293- {
294- if (todaysSales != null )
295- {
296- totalValue = todaysSales .TodaysSalesValue ;
297- totalCount = todaysSales .TodaysSalesCount ;
298- averageValue = totalCount > 0 ? totalValue / totalCount : 0 ;
299- }
300-
301- if (todaysSettlement != null )
302- {
303- netSettlement = todaysSettlement .TodaysSettlementValue ;
304- }
305- }
306-
307- private async Task OnFiltersChanged ()
308- {
309- await LoadDashboardData ();
310- }
311-
312- private async Task UpdateCharts ()
313- {
314- try
315- {
316- if (salesCountData == null || salesValueData == null )
317- {
318- Logger .LogWarning (" Chart data is null - salesCountData: {SalesCountData}, salesValueData: {SalesValueData}" ,
319- salesCountData == null ? " null" : " not null" ,
320- salesValueData == null ? " null" : " not null" );
321- return ;
322- }
323-
324- Logger .LogInformation (" Updating charts with {CountRecords} count records and {ValueRecords} value records" ,
325- salesCountData .Count , salesValueData .Count );
326-
327- // Create labels with date and time context
328- var today = DateTime .Today ;
329- var comparisonDateParsed = DateTime .TryParse (_selectedComparisonDate , out var compDate ) ? compDate : DateTime .Today .AddDays (- 7 );
330-
331- var labels = salesCountData .Select (d => $" {d .Hour : 00 }:00" ).ToArray ();
332- var todaysCountData = salesCountData .Select (d => d .TodaysSalesCount ).ToArray ();
333- var comparisonCountData = salesCountData .Select (d => d .ComparisonSalesCount ).ToArray ();
334-
335- var todaysValueData = salesValueData .Select (d => (double )d .TodaysSalesValue ).ToArray ();
336- var comparisonValueData = salesValueData .Select (d => (double )d .ComparisonSalesValue ).ToArray ();
337-
338- var comparisonLabel = GetComparisonLabel ();
339- var todayLabel = today .ToString (" MMM dd" );
340- var comparisonDateLabel = compDate .ToString (" MMM dd" );
341-
342- Logger .LogInformation (" Chart labels: {Labels}, Today data points: {TodayCount}, Comparison data points: {CompCount}" ,
343- string .Join (" , " , labels ), todaysCountData .Length , comparisonCountData .Length );
344-
345- // Update Volume Chart
346- await JSRuntime .InvokeVoidAsync (" updateOrCreateChart" ,
347- " volumeChart" ,
348- _selectedChartType ,
349- labels ,
350- new object []
351- {
352- new { label = $" Today ({todayLabel }) Volume" , data = todaysCountData , borderColor = " rgb(59, 130, 246)" , backgroundColor = " rgba(59, 130, 246, 0.1)" , tension = 0 . 4 },
353- new { label = $" {comparisonLabel } ({comparisonDateLabel }) Volume" , data = comparisonCountData , borderColor = " rgb(156, 163, 175)" , backgroundColor = " rgba(156, 163, 175, 0.1)" , tension = 0 . 4 }
354- },
355- " Transaction Count"
356- );
357-
358- // Update Value Chart
359- await JSRuntime .InvokeVoidAsync (" updateOrCreateChart" ,
360- " valueChart" ,
361- _selectedChartType ,
362- labels ,
363- new object []
364- {
365- new { label = $" Today ({todayLabel }) Value" , data = todaysValueData , borderColor = " rgb(16, 185, 129)" , backgroundColor = " rgba(16, 185, 129, 0.1)" , tension = 0 . 4 },
366- new { label = $" {comparisonLabel } ({comparisonDateLabel }) Value" , data = comparisonValueData , borderColor = " rgb(156, 163, 175)" , backgroundColor = " rgba(156, 163, 175, 0.1)" , tension = 0 . 4 }
367- },
368- " Transaction Value ($)"
369- );
370-
371- Logger .LogInformation (" Charts updated successfully" );
372- }
373- catch (Exception ex )
374- {
375- Logger .LogError (ex , " Error updating charts" );
376- }
377- }
378-
379- private string GetComparisonLabel ()
380- {
381- if (comparisonDates == null ) return " Comparison" ;
382- if (! DateTime .TryParse (_selectedComparisonDate , out var date ))
383- return " Comparison" ;
384- var comparisonDate = comparisonDates .FirstOrDefault (d => d .Date .Date == date .Date );
385- return comparisonDate ? .Description ?? date .ToString (" MMM dd" );
386- }
387- }
163+ </div >
0 commit comments