1+ using MediatR ;
2+ using Microsoft . AspNetCore . Components ;
3+ using Microsoft . AspNetCore . Components . Authorization ;
4+ using ShopInventory . Web . Data ;
5+ using ShopInventory . Web . Features . Reports . Queries . GetFiscalTransactionLog ;
6+ using ShopInventory . Web . Services ;
7+
8+ namespace ShopInventory . Web . Components . Pages ;
9+
10+ public partial class FiscalTransactionLog : IDisposable
11+ {
12+ private const int TransactionsPerPage = 25 ;
13+
14+ [ Inject ] private IMediator Mediator { get ; set ; } = default ! ;
15+ [ Inject ] private IAuditService AuditService { get ; set ; } = default ! ;
16+ [ Inject ] private AuthenticationStateProvider AuthStateProvider { get ; set ; } = default ! ;
17+ [ Inject ] private ILogger < FiscalTransactionLog > Logger { get ; set ; } = default ! ;
18+
19+ private GetFiscalTransactionLogResult reportResult = new ( ) ;
20+ private FiscalTransactionLogItemModel ? selectedTransaction ;
21+ private bool isDetailsModalOpen ;
22+ private bool isLoading = true ;
23+ private bool hasInitialized ;
24+ private bool hasLoggedView ;
25+ private bool awaitingAuthentication ;
26+ private string ? errorMessage ;
27+ private string searchTerm = string . Empty ;
28+ private string selectedStatus = "All" ;
29+ private string selectedDocumentType = "All" ;
30+ private DateTime ? fromDate = DateTime . Today . AddDays ( - 30 ) ;
31+ private DateTime ? toDate = DateTime . Today ;
32+ private int currentPage = 1 ;
33+
34+ private string PageSummary => reportResult . TotalCount == 0
35+ ? "No records returned"
36+ : $ "{ reportResult . TotalCount } total records across { Math . Max ( 1 , currentPage ) } page(s)";
37+
38+ private string LatestActivityText => reportResult . Summary . LatestTransactionAtUtc . HasValue
39+ ? $ "Latest activity { FormatTimestamp ( reportResult . Summary . LatestTransactionAtUtc . Value ) } "
40+ : "No activity yet" ;
41+
42+ protected override void OnInitialized ( )
43+ {
44+ AuthStateProvider . AuthenticationStateChanged += HandleAuthenticationStateChanged ;
45+ }
46+
47+ protected override async Task OnAfterRenderAsync ( bool firstRender )
48+ {
49+ if ( ! firstRender || hasInitialized )
50+ {
51+ return ;
52+ }
53+
54+ var authState = await AuthStateProvider . GetAuthenticationStateAsync ( ) ;
55+ if ( ! ( authState . User . Identity ? . IsAuthenticated ?? false ) )
56+ {
57+ awaitingAuthentication = true ;
58+ isLoading = false ;
59+ return ;
60+ }
61+
62+ hasInitialized = true ;
63+ await LoadReportAsync ( ) ;
64+ StateHasChanged ( ) ;
65+ }
66+
67+ private async Task LoadReportAsync ( )
68+ {
69+ isLoading = true ;
70+ errorMessage = null ;
71+
72+ try
73+ {
74+ var authState = await AuthStateProvider . GetAuthenticationStateAsync ( ) ;
75+ if ( ! ( authState . User . Identity ? . IsAuthenticated ?? false ) )
76+ {
77+ awaitingAuthentication = true ;
78+ reportResult = new GetFiscalTransactionLogResult ( ) ;
79+ selectedTransaction = null ;
80+ isDetailsModalOpen = false ;
81+ return ;
82+ }
83+
84+ awaitingAuthentication = false ;
85+ var selectedTransactionId = selectedTransaction ? . Id ;
86+ var result = await Mediator . Send ( new GetFiscalTransactionLogQuery (
87+ fromDate ,
88+ toDate ,
89+ string . IsNullOrWhiteSpace ( searchTerm ) ? null : searchTerm . Trim ( ) ,
90+ NormalizeFilter ( selectedStatus ) ,
91+ NormalizeFilter ( selectedDocumentType ) ,
92+ currentPage ,
93+ TransactionsPerPage ) ) ;
94+
95+ result . SwitchFirst (
96+ value =>
97+ {
98+ reportResult = value ;
99+ selectedTransaction = value . Transactions . FirstOrDefault ( transaction => transaction . Id == selectedTransactionId ) ;
100+ isDetailsModalOpen = isDetailsModalOpen && selectedTransaction is not null ;
101+ } ,
102+ error =>
103+ {
104+ reportResult = new GetFiscalTransactionLogResult ( ) ;
105+ selectedTransaction = null ;
106+ isDetailsModalOpen = false ;
107+ errorMessage = error . Description ;
108+ } ) ;
109+
110+ if ( ! result . IsError && ! hasLoggedView )
111+ {
112+ hasLoggedView = true ;
113+ await AuditService . LogAsync ( AuditActions . ViewReports , "Report" , "FiscalTransactionLog" ) ;
114+ }
115+ }
116+ catch ( Exception ex )
117+ {
118+ Logger . LogError ( ex , "Failed to load fiscal transaction log page" ) ;
119+ reportResult = new GetFiscalTransactionLogResult ( ) ;
120+ selectedTransaction = null ;
121+ isDetailsModalOpen = false ;
122+ errorMessage = "Failed to load fiscal transaction log." ;
123+ }
124+ finally
125+ {
126+ isLoading = false ;
127+ }
128+ }
129+
130+ private async void HandleAuthenticationStateChanged ( Task < AuthenticationState > authenticationStateTask )
131+ {
132+ try
133+ {
134+ var authState = await authenticationStateTask ;
135+ if ( ! ( authState . User . Identity ? . IsAuthenticated ?? false ) )
136+ {
137+ return ;
138+ }
139+
140+ await InvokeAsync ( async ( ) =>
141+ {
142+ if ( ! hasInitialized )
143+ {
144+ hasInitialized = true ;
145+ }
146+
147+ if ( awaitingAuthentication || ( ! hasLoggedView && string . IsNullOrWhiteSpace ( errorMessage ) ) )
148+ {
149+ await LoadReportAsync ( ) ;
150+ StateHasChanged ( ) ;
151+ }
152+ } ) ;
153+ }
154+ catch ( Exception ex )
155+ {
156+ Logger . LogDebug ( ex , "Authentication state change handler failed for fiscal transaction log page" ) ;
157+ }
158+ }
159+
160+ private async Task ApplyFiltersAsync ( )
161+ {
162+ currentPage = 1 ;
163+ await LoadReportAsync ( ) ;
164+ }
165+
166+ private async Task ClearFiltersAsync ( )
167+ {
168+ searchTerm = string . Empty ;
169+ selectedStatus = "All" ;
170+ selectedDocumentType = "All" ;
171+ fromDate = DateTime . Today . AddDays ( - 30 ) ;
172+ toDate = DateTime . Today ;
173+ currentPage = 1 ;
174+ await LoadReportAsync ( ) ;
175+ }
176+
177+ private async Task ReloadAsync ( ) => await LoadReportAsync ( ) ;
178+
179+ private async Task PreviousPageAsync ( )
180+ {
181+ if ( currentPage <= 1 )
182+ {
183+ return ;
184+ }
185+
186+ currentPage -- ;
187+ await LoadReportAsync ( ) ;
188+ }
189+
190+ private async Task NextPageAsync ( )
191+ {
192+ if ( ! reportResult . HasMore )
193+ {
194+ return ;
195+ }
196+
197+ currentPage ++ ;
198+ await LoadReportAsync ( ) ;
199+ }
200+
201+ private void OpenTransactionDetails ( FiscalTransactionLogItemModel transaction )
202+ {
203+ selectedTransaction = transaction ;
204+ isDetailsModalOpen = true ;
205+ }
206+
207+ private void CloseTransactionDetails ( )
208+ => isDetailsModalOpen = false ;
209+
210+ private string GetRowClass ( FiscalTransactionLogItemModel transaction )
211+ => selectedTransaction ? . Id == transaction . Id ? "ftr-row--selected" : string . Empty ;
212+
213+ private string GetStatusBadgeClass ( string ? status )
214+ => ( status ?? string . Empty ) . Trim ( ) . ToLowerInvariant ( ) switch
215+ {
216+ "success" => "ftr-status-badge ftr-status-badge--success" ,
217+ "fiscalised" => "ftr-status-badge ftr-status-badge--info" ,
218+ "not fiscalised" => "ftr-status-badge ftr-status-badge--warning" ,
219+ "failed" => "ftr-status-badge ftr-status-badge--danger" ,
220+ _ => "ftr-status-badge ftr-status-badge--muted"
221+ } ;
222+
223+ private static string ? NormalizeFilter ( string ? value )
224+ => string . IsNullOrWhiteSpace ( value ) || string . Equals ( value , "All" , StringComparison . OrdinalIgnoreCase )
225+ ? null
226+ : value . Trim ( ) ;
227+
228+ private static string DisplayCustomer ( FiscalTransactionLogItemModel transaction )
229+ => string . IsNullOrWhiteSpace ( transaction . CardName ) ? "Walk-in / not captured" : transaction . CardName ;
230+
231+ private static string DisplayCardCode ( FiscalTransactionLogItemModel transaction )
232+ => string . IsNullOrWhiteSpace ( transaction . CardCode ) ? "No card code" : transaction . CardCode ;
233+
234+ private static string DisplayVerification ( FiscalTransactionLogItemModel transaction )
235+ => string . IsNullOrWhiteSpace ( transaction . VerificationCode ) ? "Not captured" : transaction . VerificationCode ;
236+
237+ private static string DisplayReceiptGlobalNo ( FiscalTransactionLogItemModel transaction )
238+ => transaction . ReceiptGlobalNo . HasValue && transaction . ReceiptGlobalNo . Value > 0
239+ ? transaction . ReceiptGlobalNo . Value . ToString ( )
240+ : "Not captured" ;
241+
242+ private static string DisplayOperator ( FiscalTransactionLogItemModel transaction )
243+ => string . IsNullOrWhiteSpace ( transaction . CreatedByUsername )
244+ ? ( string . IsNullOrWhiteSpace ( transaction . CreatedByUserId ) ? "Unknown operator" : transaction . CreatedByUserId )
245+ : transaction . CreatedByUsername ;
246+
247+ private static string DisplayOriginalInvoice ( FiscalTransactionLogItemModel transaction )
248+ => string . IsNullOrWhiteSpace ( transaction . OriginalInvoiceNumber ) ? "Not applicable" : transaction . OriginalInvoiceNumber ;
249+
250+ private static string DisplayDeviceSerial ( FiscalTransactionLogItemModel transaction )
251+ => string . IsNullOrWhiteSpace ( transaction . DeviceSerialNumber ) ? "Not captured" : transaction . DeviceSerialNumber ;
252+
253+ private static string DisplayDeviceId ( FiscalTransactionLogItemModel transaction )
254+ => string . IsNullOrWhiteSpace ( transaction . DeviceId ) ? "Not captured" : transaction . DeviceId ;
255+
256+ private static string DisplayFiscalDay ( FiscalTransactionLogItemModel transaction )
257+ => string . IsNullOrWhiteSpace ( transaction . FiscalDay ) ? "Not captured" : transaction . FiscalDay ;
258+
259+ private static string DisplayMessage ( FiscalTransactionLogItemModel transaction )
260+ => string . IsNullOrWhiteSpace ( transaction . Message ) ? "No operator message captured." : transaction . Message ;
261+
262+ private static string FormatAmount ( decimal amount , string ? currency )
263+ => string . IsNullOrWhiteSpace ( currency ) ? amount . ToString ( "N2" ) : $ "{ currency } { amount : N2} ";
264+
265+ private static string FormatTimestamp ( DateTime timestampUtc )
266+ => IAuditService . ToCAT ( timestampUtc ) . ToString ( "yyyy-MM-dd HH:mm" ) ;
267+
268+ public void Dispose ( )
269+ {
270+ AuthStateProvider . AuthenticationStateChanged -= HandleAuthenticationStateChanged ;
271+ }
272+ }
0 commit comments