Compare commits

...

12 Commits

Author SHA1 Message Date
Loretta d27f53453f Refactor fullscreen grid UI; add serializer diagnostics/tests
- Replaced DxWindow with custom Bootstrap 5 fullscreen overlay for grid components, improving fullscreen UX and styling.
- Added new CSS for fullscreen overlay, header, and body; retained legacy DxWindow styles for compatibility.
- Introduced SignalRSerializerDiagnosticLog flag to control binary serializer diagnostics at runtime.
- Enabled diagnostics in DevAdminSignalRHub, FruitBankSignalRClient, and Program.cs based on the new flag.
- Updated OrderClientTests to use GetStockTakings(false).
- Added StockTakingSerializerTests for binary serialization/deserialization validation and debugging.
2025-12-20 08:40:03 +01:00
Loretta 3c5b737207 Refactor MgGridInfoPanel for theme, UX, and PDF perf
- Refactored MgGridInfoPanel for DevExpress theme compatibility and improved usability; streamlined HTML/CSS, added OnDataItemChanged event, and enhanced empty state handling.
- Updated CSS to use theme variables, improved responsive grid and table styling, and enhanced integration with DevExpress components.
- GridShippingDocumentInfoPanel now uses OnDataItemChanged to load a random PDF per row selection; table layout and totals improved.
- Optimized pdfViewer.js to cache rendered PDFs, skip redundant renders, and improve logging and error handling.
- Added empty helper classes for future extensibility.
- Minor: MainLayout now uses RefreshMainLayout for UI refresh after auto-login.
2025-12-19 13:59:00 +01:00
Loretta ed99db3238 Merge branch 'main' of https://git.aycode.com/Adam/FruitBankHybridApp 2025-12-19 07:16:15 +01:00
Loretta 7923c42a1e Add fullscreen grid support and PDF preview in info panel
- Added fullscreen mode to grid and info panel components, including toolbar toggle and fullscreen styling.
- Introduced embedded PDF viewing in the info panel using PDF.js and a custom JavaScript viewer.
- Updated interfaces, CSS, and toolbar templates to support new features.
- Added new PDF asset (2_BANK  FRA.pdf) for document preview.
- Minor: Added local settings for Bash permission, fixed text encoding, and improved info panel table layout.
- No code changes in other referenced PDF files; added for informational or asset purposes only.
2025-12-19 07:15:54 +01:00
Loretta 2bcf802da7 Enhance MgGridInfoPanel with responsive/fixed columns
Add support for both responsive and fixed column layouts in MgGridInfoPanel via new parameters (TwoColumnBreakpoint, ThreeColumnBreakpoint, FourColumnBreakpoint, FixedColumnCount). Refactor CSS to use variables for breakpoints, add fixed column classes, and update container queries. Move styles to a new global mg-grid-info-panel.css, referenced in App.razor and index.html. Improve view mode styling and accessibility. Add Partner.Country column to GridPartner.razor.
2025-12-18 11:41:07 +01:00
Loretta 7f4052faa2 Refactor: decouple InfoPanel using MgGridWithInfoPanel
Major refactor to decouple InfoPanel logic from grid base. Introduces MgGridWithInfoPanel wrapper component to manage grid and InfoPanel layout and communication. InfoPanels are now customizable via Razor templates with named slots (header, footer, etc.), and grid-to-InfoPanel communication is routed through the wrapper. Removes legacy C#-only InfoPanel base classes and parameters from grid base. This improves flexibility, composability, and maintainability of grid+InfoPanel UIs.
2025-12-18 11:02:53 +01:00
Loretta 031fd9226b Add extensible InfoPanel system with custom templates
Introduce a flexible InfoPanel architecture for grid components, allowing per-grid customization via a new InfoPanelType parameter. Add MgInfoPanelTemplateBase for code-based InfoPanel templates with overridable header, content, and footer sections. Support custom templates in MgGridInfoPanel via new RenderFragment parameters. Add MgGridDataColumn for InfoPanel-specific column settings. Demonstrate usage with a custom InfoPanel for ShippingDocument grid. Maintains backward compatibility with default InfoPanel behavior.
2025-12-18 10:03:32 +01:00
Loretta 21548376ba Refactor grid toolbar and InfoPanel for reusability
Introduce reusable MgGridToolbarBase and MgGridToolbarTemplate components, replacing old toolbar implementations. InfoPanel now displays grid captions and includes a toolbar for quick actions. Refactor InfoPanel value rendering for improved DevExpress-like appearance. Update IMgGridBase and related classes to support captions and use new abstractions. Update usings and project structure accordingly.
2025-12-17 18:31:59 +01:00
Loretta 903711ed04 Refactor InfoPanel: non-generic, nested grid support
- Replace generic InfoPanel with non-generic version using IInfoPanelBase
- Add ParentGrid, GetRootGrid, and InfoPanelInstance to IMgGridBase for nested grid hierarchy
- Only root grid displays InfoPanel; nested grids inherit context
- InfoPanel now handles any data type via reflection and object
- All grid-to-InfoPanel communication routed through root grid
- Add option to show/hide readonly fields in edit mode
- Improve InfoPanel CSS for up to 4 responsive columns
- Remove redundant code and add debug output for InfoPanel data flow
2025-12-17 13:54:08 +01:00
Loretta a0f7ac6a29 Refactor grid InfoPanel: sticky, responsive, new icons
- Redesigned MgGridInfoPanel to use a sticky, scroll-aware layout via JavaScript for better UX when scrolling.
- InfoPanel now uses a responsive CSS grid layout with container queries for 1/2/3 column display based on width.
- Added new toolbar icons using SVG masks for a modern, consistent look; updated toolbar item class names.
- Added "Prev Row" and "Next Row" navigation buttons to the grid toolbar, with corresponding methods in grid base classes.
- Unified edit state enum naming to MgGridEditState and updated all references.
- Improved InfoPanel cell rendering for better text overflow handling and tooltips.
- Updated CSS for InfoPanel and grid, including sticky pane support and icon styles.
- Registered mgGridInfoPanel.js in App.razor and index.html for JS interop.
- Minor UI/UX tweaks: InfoPanel header, background colors, and panel sizing.
2025-12-17 10:20:18 +01:00
Loretta fbe09be307 Refactor grid editing, info panel, and toolbar system
- Introduce MgEditState enum and expose EditState on IMgGridBase
- Replace event-based syncing state with property-based state
- Redesign MgGridInfoPanel to support both view and edit modes with dynamic DevExpress editors and two-way binding
- Add visual distinction for edit/view modes in info panel
- Replace FruitBankToolbarTemplate with generic MgGridToolbarTemplate; toolbar adapts to grid edit/sync state
- Update all grid usages to use new toolbar
- Improve robustness, error handling, and maintainability throughout grid, info panel, and toolbar code
2025-12-17 06:21:21 +01:00
Loretta b3ddc86639 Add dynamic Info Panel to grids and update app splash image
Introduced a dynamic Info Panel to MgGridBase, allowing users to view details of the selected row in a side panel. Added the MgGridInfoPanel component with automatic form generation and styling. Updated all grid usages to use the new OnGridFocusedRowChanged event and enabled the info panel in GridPartner. Changed app logo and splash screen references in the Windows packaging manifest and added a placeholder splash image. Also included minor using fixes.
2025-12-16 16:12:38 +01:00
37 changed files with 906 additions and 345 deletions

View File

@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(dir:*)"
]
}
}

View File

@ -1,5 +1,7 @@
using AyCode.Core.Extensions;
using AyCode.Core.Loggers;
using AyCode.Core.Serializers.Binaries;
using AyCode.Core.Serializers.Jsons;
using AyCode.Models.Server.DynamicMethods;
using AyCode.Services.SignalRs;
using FruitBank.Common.Interfaces;
@ -33,6 +35,7 @@ public class DevAdminSignalRHub : AcWebSignalRHubWithSessionBase<SignalRTags, Lo
ICustomOrderSignalREndpointServer customOrderSignalREndpoint, IStockSignalREndpointServer stockSignalREndpointServer, IEnumerable<IAcLogWriterBase> logWriters)
: base(configuration, new Logger<DevAdminSignalRHub>(logWriters.ToArray()))
{
EnableBinaryDiagnostics = FruitBankConstClient.SignalRSerializerDiagnosticLog;
SerializerOptions = new AcBinarySerializerOptions();
//SerializerOptions = new AcJsonSerializerOptions();

View File

@ -22,6 +22,7 @@ public static class FruitBankConstClient
public static string DefaultHubName = "fbHub";
public static string LoggerHubName = "loggerHub";
public static bool SignalRSerializerDiagnosticLog = false;
public static long SignalRKeepAliveIntervalSecond = 60;
public static long SignarlRTimeoutIntervalSecond = 180;

View File

@ -14,6 +14,7 @@ using Mono.Cecil;
using Newtonsoft.Json;
using Nop.Core.Domain.Orders;
using System.Runtime.Serialization;
using AyCode.Core.Serializers.Jsons;
namespace FruitBankHybrid.Shared.Tests;

View File

@ -37,7 +37,7 @@ public sealed class OrderClientTests
[TestMethod]
public async Task GetAllStockTakings()
{
var stockTakings = await _signalRClient.GetStockTakings(true);
var stockTakings = await _signalRClient.GetStockTakings(false);
Assert.IsNotNull(stockTakings);
Assert.IsTrue(stockTakings.Count != 0);

View File

@ -0,0 +1,267 @@
using AyCode.Core.Extensions;
using AyCode.Core.Serializers.Binaries;
using FruitBank.Common.Entities;
using System.Reflection;
namespace FruitBankHybrid.Shared.Tests;
[TestClass]
public class StockTakingSerializerTests
{
[TestMethod]
public void StockTaking_WithNullItems_RoundTrip()
{
var stockTaking = new StockTaking
{
Id = 1,
StartDateTime = new DateTime(2025, 1, 24, 10, 0, 0, DateTimeKind.Utc),
IsClosed = false,
Creator = 6,
Created = new DateTime(2025, 1, 24, 15, 25, 0, DateTimeKind.Utc),
Modified = new DateTime(2025, 1, 24, 16, 0, 0, DateTimeKind.Utc),
StockTakingItems = null
};
// Log StockTaking properties
Console.WriteLine("=== StockTaking Properties ===");
var stProps = typeof(StockTaking).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead && p.GetIndexParameters().Length == 0)
.ToArray();
for (int i = 0; i < stProps.Length; i++)
Console.WriteLine($" [{i}] {stProps[i].Name} : {stProps[i].PropertyType.Name} (from: {stProps[i].DeclaringType?.Name})");
// Log StockTakingItem properties
Console.WriteLine("\n=== StockTakingItem Properties ===");
var stiProps = typeof(StockTakingItem).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead && p.GetIndexParameters().Length == 0)
.ToArray();
for (int i = 0; i < stiProps.Length; i++)
{
var hasIgnore = Attribute.IsDefined(stiProps[i], typeof(Newtonsoft.Json.JsonIgnoreAttribute)) ||
Attribute.IsDefined(stiProps[i], typeof(System.Text.Json.Serialization.JsonIgnoreAttribute));
Console.WriteLine($" [{i}] {stiProps[i].Name} : {stiProps[i].PropertyType.Name}{(hasIgnore ? " [IGNORED]" : "")}");
}
var binary = stockTaking.ToBinary();
Console.WriteLine($"\nBinary length: {binary.Length}");
// Parse header
var pos = 0;
var version = binary[pos++];
var marker = binary[pos++];
Console.WriteLine($"Version: {version}, Marker: 0x{marker:X2}");
if ((marker & 0x10) != 0)
{
var propCount = binary[pos++];
Console.WriteLine($"\n=== HEADER ({propCount} properties) ===");
for (int i = 0; i < propCount; i++)
{
var strLen = binary[pos++];
var propName = System.Text.Encoding.UTF8.GetString(binary, pos, strLen);
pos += strLen;
Console.WriteLine($" [{i}]: '{propName}'");
}
}
// Deserialize
try
{
var result = binary.BinaryTo<StockTaking>();
Console.WriteLine($"\n=== RESULT ===");
Console.WriteLine($"Id: {result?.Id}");
Console.WriteLine($"Creator: {result?.Creator}");
Console.WriteLine($"Created: {result?.Created}");
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Id, "Id mismatch");
Assert.AreEqual(6, result.Creator, $"Creator should be 6, got {result.Creator}");
Assert.AreEqual(stockTaking.Created, result.Created, $"Created mismatch");
}
catch (Exception ex)
{
Console.WriteLine($"\n=== ERROR ===");
Console.WriteLine(ex.Message);
throw;
}
}
[TestMethod]
public void ListOfStockTaking_WithNullItems_RoundTrip()
{
var stockTakings = new List<StockTaking>
{
new() { Id = 1, StartDateTime = DateTime.UtcNow, IsClosed = false, Creator = 6, Created = DateTime.UtcNow, Modified = DateTime.UtcNow, StockTakingItems = null },
new() { Id = 2, StartDateTime = DateTime.UtcNow, IsClosed = true, Creator = 12, Created = DateTime.UtcNow, Modified = DateTime.UtcNow, StockTakingItems = null }
};
var binary = stockTakings.ToBinary();
Console.WriteLine($"Binary length: {binary.Length}");
var result = binary.BinaryTo<List<StockTaking>>();
Assert.IsNotNull(result);
Assert.AreEqual(2, result.Count);
Assert.AreEqual(6, result[0].Creator, $"First Creator should be 6, got {result[0].Creator}");
Assert.AreEqual(12, result[1].Creator, $"Second Creator should be 12, got {result[1].Creator}");
}
[TestMethod]
public void StockTaking_DebugBodyProperties()
{
var stockTaking = new StockTaking
{
Id = 1,
StartDateTime = new DateTime(2025, 1, 24, 10, 0, 0, DateTimeKind.Utc),
IsClosed = false,
Creator = 6,
Created = new DateTime(2025, 1, 24, 15, 25, 0, DateTimeKind.Utc),
Modified = new DateTime(2025, 1, 24, 16, 0, 0, DateTimeKind.Utc),
StockTakingItems = null
};
var binary = stockTaking.ToBinary();
// Parse header
var pos = 0;
var version = binary[pos++];
var marker = binary[pos++];
var propertyNames = new List<string>();
if ((marker & 0x10) != 0)
{
var propCount = (int)binary[pos++]; // Simplified VarUInt read
for (int i = 0; i < propCount; i++)
{
var strLen = (int)binary[pos++];
var propName = System.Text.Encoding.UTF8.GetString(binary, pos, strLen);
pos += strLen;
propertyNames.Add(propName);
}
}
// Parse body
Console.WriteLine($"\n=== BODY (starts at position {pos}) ===");
var objectMarker = binary[pos++];
Console.WriteLine($"Object marker: 0x{objectMarker:X2}");
// Read ref ID (VarInt)
var refIdByte = binary[pos];
if ((refIdByte & 0x80) == 0) pos++;
else pos += 2;
// Read property count
var bodyPropCount = binary[pos++];
Console.WriteLine($"Body property count: {bodyPropCount}");
for (int i = 0; i < bodyPropCount; i++)
{
var propIndex = binary[pos++];
var propName = propIndex < propertyNames.Count ? propertyNames[propIndex] : $"UNKNOWN({propIndex})";
var valueType = binary[pos];
string valueInfo;
if (valueType == 0x14) // DateTime
{
valueInfo = "DateTime";
pos += 10;
}
else if (valueType >= 0xD0) // TinyInt
{
var tinyValue = valueType - 0xD0;
valueInfo = $"TinyInt({tinyValue})";
pos += 1;
}
else if (valueType == 0x03)
{
valueInfo = "False";
pos += 1;
}
else if (valueType == 0x02)
{
valueInfo = "True";
pos += 1;
}
else
{
valueInfo = $"0x{valueType:X2}";
break;
}
Console.WriteLine($" Body[{i}]: index={propIndex} -> '{propName}' = {valueInfo}");
}
}
[TestMethod]
public void StockTaking_ExactProductionData_RoundTrip()
{
var stockTakings = new List<StockTaking>
{
new()
{
Id = 7,
StartDateTime = new DateTime(2025, 12, 3, 8, 55, 43, 539, DateTimeKind.Utc),
IsClosed = false,
Creator = 6,
Created = new DateTime(2025, 12, 3, 7, 55, 43, 571, DateTimeKind.Utc),
Modified = new DateTime(2025, 12, 3, 7, 55, 43, 571, DateTimeKind.Utc),
StockTakingItems = null
},
new()
{
Id = 6,
StartDateTime = new DateTime(2025, 12, 2, 8, 21, 26, 439, DateTimeKind.Utc),
IsClosed = true,
Creator = 6,
Created = new DateTime(2025, 12, 2, 7, 21, 26, 468, DateTimeKind.Utc),
Modified = new DateTime(2025, 12, 2, 7, 21, 26, 468, DateTimeKind.Utc),
StockTakingItems = null
},
new()
{
Id = 3,
StartDateTime = new DateTime(2025, 11, 30, 14, 1, 55, 663, DateTimeKind.Utc),
IsClosed = true,
Creator = 6,
Created = new DateTime(2025, 11, 30, 13, 1, 55, 692, DateTimeKind.Utc),
Modified = new DateTime(2025, 11, 30, 13, 1, 55, 692, DateTimeKind.Utc),
StockTakingItems = null
},
new()
{
Id = 2,
StartDateTime = new DateTime(2025, 11, 30, 8, 20, 2, 182, DateTimeKind.Utc),
IsClosed = true,
Creator = 6,
Created = new DateTime(2025, 11, 30, 7, 20, 3, 331, DateTimeKind.Utc),
Modified = new DateTime(2025, 11, 30, 7, 20, 3, 331, DateTimeKind.Utc),
StockTakingItems = null
},
new()
{
Id = 1,
StartDateTime = new DateTime(2025, 11, 30, 8, 18, 59, 693, DateTimeKind.Utc),
IsClosed = true,
Creator = 6,
Created = new DateTime(2025, 11, 30, 7, 19, 1, 849, DateTimeKind.Utc),
Modified = new DateTime(2025, 11, 30, 7, 19, 1, 877, DateTimeKind.Utc),
StockTakingItems = null
}
};
var binary = stockTakings.ToBinary();
Console.WriteLine($"Binary length: {binary.Length}");
var result = binary.BinaryTo<List<StockTaking>>();
Assert.IsNotNull(result);
Assert.AreEqual(5, result.Count);
foreach (var item in result)
{
Assert.AreEqual(6, item.Creator,
$"StockTaking Id={item.Id}: Creator should be 6, got {item.Creator}");
}
}
}

View File

@ -1,9 +1,9 @@
@using AyCode.Core.Helpers
@using AyCode.Blazor.Components.Components.Grids
@using AyCode.Core.Helpers
@using AyCode.Utils.Extensions
@using FruitBank.Common.Dtos
@using FruitBank.Common.Models
@using FruitBankHybrid.Shared.Components.Grids.GenericAttributes
@using FruitBankHybrid.Shared.Components.Toolbars
@using FruitBankHybrid.Shared.Databases
@using FruitBankHybrid.Shared.Services.SignalRs
@ -69,7 +69,7 @@
<ToolbarTemplate>
@if (IsMasterGrid)
{
<FruitBankToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)"/>
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)"/>
}
</ToolbarTemplate>
<GroupSummary>

View File

@ -1,9 +1,9 @@
@using AyCode.Core.Helpers
@using AyCode.Blazor.Components.Components.Grids
@using AyCode.Core.Helpers
@using AyCode.Utils.Extensions
@using FruitBank.Common.Dtos
@using FruitBank.Common.Models
@using FruitBankHybrid.Shared.Components.Grids.GenericAttributes
@using FruitBankHybrid.Shared.Components.Toolbars
@using FruitBankHybrid.Shared.Databases
@using FruitBankHybrid.Shared.Services.SignalRs
@ -74,7 +74,7 @@
<ToolbarTemplate>
@if (IsMasterGrid)
{
<FruitBankToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
}
</ToolbarTemplate>
<GroupSummary>

View File

@ -1,8 +1,8 @@
@using AyCode.Utils.Extensions
@using AyCode.Blazor.Components.Components.Grids
@using AyCode.Utils.Extensions
@using FruitBank.Common.Dtos
@using FruitBank.Common.Entities
@using FruitBank.Common.Models
@using FruitBankHybrid.Shared.Components.Toolbars
@using FruitBankHybrid.Shared.Databases
@using FruitBankHybrid.Shared.Services.SignalRs
@ -37,7 +37,7 @@
<ToolbarTemplate>
@if (IsMasterGrid)
{
<FruitBankToolbarTemplate Grid="gridOrderItemPallet" OnReloadDataClick="() => ReloadDataFromDb(true)" />
<MgGridToolbarTemplate Grid="gridOrderItemPallet" OnReloadDataClick="() => ReloadDataFromDb(true)" />
}
</ToolbarTemplate>
<GroupSummary>

View File

@ -1,4 +1,5 @@
@using System.Threading
@using AyCode.Blazor.Components.Components.Grids
@using AyCode.Core.Helpers
@using AyCode.Utils.Extensions
@using DevExpress.Internal.About
@ -6,7 +7,6 @@
@using FruitBank.Common.Models
@using FruitBankHybrid.Shared.Components.Grids.GenericAttributes
@using FruitBankHybrid.Shared.Components.Grids.Products
@using FruitBankHybrid.Shared.Components.Toolbars
@using FruitBankHybrid.Shared.Databases
@using FruitBankHybrid.Shared.Services.SignalRs
@ -69,7 +69,7 @@
<ToolbarTemplate>
@if (IsMasterGrid)
{
<FruitBankToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
}
</ToolbarTemplate>
</GridProductDto>

View File

@ -1,8 +1,8 @@
@using AyCode.Utils.Extensions
@using AyCode.Blazor.Components.Components.Grids
@using AyCode.Utils.Extensions
@using FruitBank.Common.Dtos
@using FruitBank.Common.Entities
@using FruitBankHybrid.Shared.Components.Grids.ShippingDocuments
@using FruitBankHybrid.Shared.Components.Toolbars
@using FruitBankHybrid.Shared.Databases
@using FruitBankHybrid.Shared.Services.SignalRs
@using System.Text
@ -16,8 +16,10 @@
@inject IEnumerable<IAcLogWriterClientBase> LogWriters
@inject FruitBankSignalRClient FruitBankSignalRClient
<GridShippingDocumentBase @ref="Grid" CssClass="@GridCss" DataSource="@ShippingDocuments" SignalRClient="FruitBankSignalRClient" AutoSaveLayoutName="GridShippingDocument"
ParentDataItem="@ParentDataItem" Logger="_logger" ValidationEnabled="false" EditMode="GridEditMode.EditRow" FocusedRowChanged="Grid_FocusedRowChanged">
<MgGridWithInfoPanel ShowInfoPanel="@IsMasterGrid">
<GridContent>
<GridShippingDocumentBase @ref="Grid" CssClass="@GridCss" DataSource="@ShippingDocuments" SignalRClient="FruitBankSignalRClient" AutoSaveLayoutName="GridShippingDocument"
ParentDataItem="@ParentDataItem" Logger="_logger" ValidationEnabled="false" EditMode="GridEditMode.EditRow" OnGridFocusedRowChanged="Grid_FocusedRowChanged">
<Columns>
<DxGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
<DxGridDataColumn FieldName="PartnerId" Caption="Partner" Visible="@(!ParentDataItemIsPartner)" ReadOnly="@ParentDataItemIsPartner">
@ -94,29 +96,15 @@
<ToolbarTemplate>
@if (IsMasterGrid)
{
<FruitBankToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)">
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)">
<ToolbarItemsExtended>
<DxToolbarItem BeginGroup="true">
<Template Context="ctxToolbarItemFileUpload">
@* <DxUpload Name="myFile"
UploadMode="@UploadMode.Instant"
AllowMultiFileUpload="false"
ShowSelectButton="true"
MaxFileSize="15000000">
</DxUpload> *@
<FileUpload OnFileUploaded="OnFileUploaded"></FileUpload>
</Template>
</DxToolbarItem>
@* <DxToolbarItem BeginGroup="true">
<Template Context="toolbar_item_context">
<DxSearchBox @bind-Text="GridSearchText"
BindValueMode="BindValueMode.OnInput"
ClearButtonDisplayMode="DataEditorClearButtonDisplayMode.Auto"
aria-label="Search" />
</Template>
</DxToolbarItem>*@
</ToolbarItemsExtended>
</FruitBankToolbarTemplate>
</MgGridToolbarTemplate>
}
</ToolbarTemplate>
<GroupSummary>
@ -130,13 +118,14 @@
FieldName="PriceInclTax"
FooterColumnName="PriceInclTax" />
</GroupSummary>
</GridShippingDocumentBase>
</GridShippingDocumentBase>
</GridContent>
<ChildContent>
<GridShippingDocumentInfoPanel />
</ChildContent>
</MgGridWithInfoPanel>
@code {
//EditRow dblClick
//https://supportcenter.devexpress.com/ticket/details/t1097648/datagrid-for-blazor-how-to-start-editing-a-row-and-save-changes-without-the-command-column
//[Inject] public required ObjectLock ObjectLock { get; set; }
[Inject] public required DatabaseClient Database { get; set; }
[Inject] public required LoggedInModel LoggedInModel { get; set; }
@ -152,7 +141,6 @@
public bool ParentDataItemIsPartner => (ParentDataItem is Partner);
string GridCss => !IsMasterGrid ? "hide-toolbar" : string.Empty;
const string ExportFileName = "ExportResult";
string _localStorageKey = "GridShippingDocument_";
@ -229,7 +217,6 @@
if (OnUploadedFileParsed != null)
await OnUploadedFileParsed(result);
//await InvokeAsync(StateHasChanged);
}
async Task Grid_FocusedRowChanged(GridFocusedRowChangedEventArgs args)
@ -244,22 +231,5 @@
protected async Task OnActiveTabChanged(int activeTabIndex)
{
_activeTabIndex = activeTabIndex;
return;
// switch (_activeTabIndex)
// {
// case 0:
// if(ProductDtos == null)
// ProductDtos = (await FruitBankSignalRClient.GetProductDtos() ?? []); //.Where(o => o.HasMeasuringAccess(LoggedInModel.CustomerDto?.Id, LoggedInModel.IsRevisor)).OrderBy(o => o.DateOfReceipt).ToList();
// break;
// case 1:
// if(OrderDtos == null)
// OrderDtos = (await FruitBankSignalRClient.GetAllOrderDtos() ?? []).OrderByDescending(o => o.Id).ToList(); //.Where(o => o.HasMeasuringAccess(LoggedInModel.CustomerDto?.Id, LoggedInModel.IsRevisor)).OrderBy(o => o.DateOfReceipt).ToList();
// break;
// case 2:
// if (OrderItemDtos == null)
// OrderItemDtos = (await FruitBankSignalRClient.GetAllOrderItemDtos() ?? []).OrderByDescending(o => o.Id).ToList(); //.Where(o => o.HasMeasuringAccess(LoggedInModel.CustomerDto?.Id, LoggedInModel.IsRevisor)).OrderBy(o => o.DateOfReceipt).ToList();
// break;
// }
}
}

View File

@ -1,4 +1,5 @@
@using AyCode.Core.Loggers;
@using AyCode.Blazor.Components.Components.Grids
@using AyCode.Core.Loggers;
@using AyCode.Core.Extensions
@using AyCode.Core.Helpers
@using AyCode.Core.Interfaces
@ -7,7 +8,6 @@
@using FruitBank.Common.Entities
@using FruitBank.Common.Interfaces
@using FruitBankHybrid.Shared.Components.Grids.ShippingItems
@using FruitBankHybrid.Shared.Components.Toolbars
@using FruitBankHybrid.Shared.Databases
@using FruitBankHybrid.Shared.Services.Loggers;
@using FruitBankHybrid.Shared.Services.SignalRs
@ -18,7 +18,7 @@
<GridShippingItemBase @ref="Grid" ParentDataItem="ParentDataItem" DataSource="ShippingItems" AutoSaveLayoutName="GridShippingItem"
SignalRClient="FruitBankSignalRClient" Logger="_logger"
CssClass="@GridCss" ValidationEnabled="false" CustomizeElement="Grid_CustomizeElement"
FocusedRowChanged="Grid_FocusedRowChanged">
OnGridFocusedRowChanged="Grid_FocusedRowChanged">
<Columns>
<DxGridDataColumn FieldName="Id" Caption="oiId" Width="125" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
<DxGridDataColumn FieldName="ShippingDocumentId" Caption="ShippingDocument"
@ -108,7 +108,7 @@
<ToolbarTemplate>
@if (IsMasterGrid)
{
<FruitBankToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
}
</ToolbarTemplate>
<GroupSummary>

View File

@ -1,4 +1,5 @@
@using System.Collections.ObjectModel
@using AyCode.Blazor.Components.Components.Grids
@using AyCode.Core.Helpers
@using AyCode.Core.Interfaces
@using AyCode.Core.Loggers
@ -6,7 +7,6 @@
@using FruitBank.Common.Dtos
@using FruitBank.Common.Entities
@using FruitBankHybrid.Shared.Components.Grids.Shippings
@using FruitBankHybrid.Shared.Components.Toolbars
@using FruitBankHybrid.Shared.Databases
@using FruitBankHybrid.Shared.Services.Loggers
@using FruitBankHybrid.Shared.Services.SignalRs
@ -16,7 +16,7 @@
@inject FruitBankSignalRClient FruitBankSignalRClient
<GridGenericAttributeBase @ref="Grid" DataSource="GenericAttributes" AutoSaveLayoutName="GridGenericAttribute" SignalRClient="FruitBankSignalRClient" Logger="_logger"
ParentDataItem="@ParentDataItem" CssClass="@GridCss" ValidationEnabled="false" FocusedRowChanged="Grid_FocusedRowChanged">
ParentDataItem="@ParentDataItem" CssClass="@GridCss" ValidationEnabled="false" OnGridFocusedRowChanged="Grid_FocusedRowChanged">
<Columns>
<DxGridDataColumn FieldName="@nameof(GenericAttribute.Id)" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
@ -32,7 +32,7 @@
<ToolbarTemplate>
@if (IsMasterGrid)
{
<FruitBankToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
}
</ToolbarTemplate>
</GridGenericAttributeBase>

View File

@ -1,11 +1,11 @@
@using System.Collections.ObjectModel
@using AyCode.Blazor.Components.Components.Grids
@using AyCode.Core.Helpers
@using AyCode.Core.Loggers
@using AyCode.Utils.Extensions
@using FruitBank.Common.Dtos
@using FruitBank.Common.Entities
@using FruitBankHybrid.Shared.Components.Grids.Shippings
@using FruitBankHybrid.Shared.Components.Toolbars
@using FruitBankHybrid.Shared.Databases
@using FruitBankHybrid.Shared.Services.Loggers
@using FruitBankHybrid.Shared.Services.SignalRs
@ -13,9 +13,15 @@
@inject IEnumerable<IAcLogWriterClientBase> LogWriters
@inject FruitBankSignalRClient FruitBankSignalRClient
<GridPartnerBase @ref="Grid" DataSource="Partners" AutoSaveLayoutName="GridPartner" SignalRClient="FruitBankSignalRClient" Logger="_logger"
CssClass="@GridCss" ValidationEnabled="false"
FocusedRowChanged="Grid_FocusedRowChanged">
<GridPartnerBase @ref="Grid"
DataSource="Partners"
AutoSaveLayoutName="GridPartner"
SignalRClient="FruitBankSignalRClient"
Logger="_logger"
CssClass="@GridCss"
ValidationEnabled="false"
ShowInfoPanel="true"
OnGridFocusedRowChanged="Grid_FocusedRowChanged">
<Columns>
<DxGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
@ -23,6 +29,7 @@
<DxGridDataColumn FieldName="TaxId" />
<DxGridDataColumn FieldName="CertificationNumber" />
<DxGridDataColumn FieldName="PostalCode" />
<DxGridDataColumn FieldName="@nameof(Partner.Country)" />
<DxGridDataColumn FieldName="State" />
<DxGridDataColumn FieldName="County" />
<DxGridDataColumn FieldName="City" />
@ -58,7 +65,7 @@
<ToolbarTemplate>
@if (IsMasterGrid)
{
<FruitBankToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
}
</ToolbarTemplate>
</GridPartnerBase>

View File

@ -1,9 +1,9 @@
@using AyCode.Core.Interfaces
@using AyCode.Blazor.Components.Components.Grids
@using AyCode.Core.Interfaces
@using AyCode.Core.Loggers;
@using AyCode.Utils.Extensions
@using FruitBank.Common.Dtos
@using FruitBankHybrid.Shared.Components.Grids.ShippingItems
@using FruitBankHybrid.Shared.Components.Toolbars
@using FruitBankHybrid.Shared.Databases
@using FruitBankHybrid.Shared.Services.Loggers;
@using FruitBankHybrid.Shared.Services.SignalRs
@ -46,7 +46,7 @@
<ToolbarTemplate>
@if (IsMasterGrid)
{
<FruitBankToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
}
</ToolbarTemplate>
@* <GroupSummary>

View File

@ -0,0 +1,92 @@
@using AyCode.Blazor.Components.Components.Grids
@using DevExpress.Blazor
@using FruitBank.Common.Entities
@using System.IO
@inject IJSRuntime JS
<MgGridInfoPanel OnDataItemChanged="OnDataItemChangedAsync">
@* <HeaderTemplate Context="dataItem">
<div class="dxbl-grid-header-panel px-3 py-2 border-bottom d-flex align-items-center">
<span class="me-2">??</span>
<span class="fw-semibold">Szállítólevél részletei</span>
</div>
</HeaderTemplate> *@
@* <BeforeColumnsTemplate Context="dataItem">
@if (dataItem is ShippingDocument doc)
{
<div class="alert alert-info mb-2 py-1 px-2 small">
<strong>Partner:</strong> @doc.Partner?.Name
</div>
}
</BeforeColumnsTemplate> *@
<AfterColumnsTemplate Context="dataItem">
@if (dataItem is ShippingDocument doc)
{
<table class="table table-bordered table-striped" style="margin-top: 35px;">
<thead>
<tr>
<th>Név a dokumentumon</th>
<th style ="padding-left: 10px;">Termék neve</th>
<th style="text-align: right; padding-left: 10px;">Rakl.</th>
<th style="text-align: right; padding-left: 10px;">Menny.</th>
<th style="text-align: right; padding-left: 10px;">Net.súly</th>
<th style="text-align: right; padding-left: 10px;">Br.súly</th>
</tr>
</thead>
<tbody>
@foreach (var shippingItem in doc.ShippingItems)
{
<tr>
<td>@shippingItem.NameOnDocument</td>
<td style="padding-left: 10px;">@shippingItem.ProductName</td>
<td style="text-align: right; padding-left: 10px;">@shippingItem.PalletsOnDocument</td>
<td style="text-align: right; padding-left: 10px;">@shippingItem.QuantityOnDocument</td>
<td style="text-align: right; padding-left: 10px;">@shippingItem.NetWeightOnDocument</td>
<td style="text-align: right; padding-left: 10px;">@shippingItem.GrossWeightOnDocument</td>
</tr>
}
</tbody>
<tfoot>
<tr style="font-weight: bold;">
<td>TOTAL:</td>
<td style="padding-left: 10px;"></td>
<td style="text-align: right; padding-left: 10px;">@doc.ShippingItems.Sum(x => x.PalletsOnDocument)</td>
<td style="text-align: right; padding-left: 10px;">@doc.ShippingItems.Sum(x => x.QuantityOnDocument)</td>
<td style="text-align: right; padding-left: 10px;">@double.Round(doc.ShippingItems.Sum(x => x.NetWeightOnDocument), 1)</td>
<td style="text-align: right; padding-left: 10px;">@double.Round(doc.ShippingItems.Sum(x => x.GrossWeightOnDocument), 1)</td>
</tr>
</tfoot>
</table>
<div id="pdfContainer" style="width: 100%; height: 800px; overflow-y: auto; margin-top: 30px;">
</div>
}
</AfterColumnsTemplate>
@* <FooterTemplate Context="dataItem">
<div class="p-2 border-top d-flex gap-2">
<DxButton Text="Nyomtatás" IconCssClass="dx-icon dx-icon-print" RenderStyle="ButtonRenderStyle.Light" />
<DxButton Text="Export" IconCssClass="dx-icon dx-icon-export" RenderStyle="ButtonRenderStyle.Light" />
</div>
</FooterTemplate> *@
</MgGridInfoPanel>
@code
{
private readonly string[] _pdfFiles =
[
"1_Albaran_AH25007715.pdf",
"2_BANK FRA.pdf",
"3_BP-30M35_20251113_163816.pdf"
];
private async Task OnDataItemChangedAsync(object? dataItem)
{
// Véletlenszerű PDF kiválasztása minden sor váltáskor
var randomPdf = _pdfFiles[Random.Shared.Next(_pdfFiles.Length)];
var pdfUrls = new[] { $"_content/FruitBankHybrid.Shared/uploads/{randomPdf}" };
await JS.InvokeVoidAsync("pdfViewer.renderPdfs", "pdfContainer", pdfUrls);
}
}

View File

@ -1,11 +1,11 @@
@using System.Collections.ObjectModel
@using AyCode.Blazor.Components.Components.Grids
@using AyCode.Core.Helpers
@using AyCode.Core.Loggers
@using AyCode.Utils.Extensions
@using FruitBank.Common.Dtos
@using FruitBank.Common.Entities
@using FruitBankHybrid.Shared.Components.Grids.Shippings
@using FruitBankHybrid.Shared.Components.Toolbars
@using FruitBankHybrid.Shared.Databases
@using FruitBankHybrid.Shared.Services.Loggers
@using FruitBankHybrid.Shared.Services.SignalRs
@ -17,7 +17,7 @@
<GridShippingBase @ref="Grid" DataSource="Shippings" AutoSaveLayoutName="GridShipping" SignalRClient="FruitBankSignalRClient" Logger="_logger"
CssClass="@GridCss" ValidationEnabled="false"
FocusedRowChanged="Grid_FocusedRowChanged">
OnGridFocusedRowChanged="Grid_FocusedRowChanged">
<Columns>
<DxGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
@ -55,7 +55,7 @@
<ToolbarTemplate>
@if (IsMasterGrid)
{
<FruitBankToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
}
</ToolbarTemplate>
</GridShippingBase>

View File

@ -1,11 +1,11 @@
@using System.Collections.ObjectModel
@using AyCode.Blazor.Components.Components.Grids
@using AyCode.Core.Helpers
@using AyCode.Core.Loggers
@using AyCode.Utils.Extensions
@using FruitBank.Common.Dtos
@using FruitBank.Common.Entities
@using FruitBankHybrid.Shared.Components.Grids.Shippings
@using FruitBankHybrid.Shared.Components.Toolbars
@using FruitBankHybrid.Shared.Databases
@using FruitBankHybrid.Shared.Services.Loggers
@using FruitBankHybrid.Shared.Services.SignalRs
@ -14,7 +14,7 @@
@inject FruitBankSignalRClient FruitBankSignalRClient
<GridStockTakingItemBase @ref="Grid" AutoSaveLayoutName="GridStockTakingItem" SignalRClient="FruitBankSignalRClient" Logger="_logger"
CssClass="@GridCss" ValidationEnabled="false" FocusedRowChanged="Grid_FocusedRowChanged">
CssClass="@GridCss" ValidationEnabled="false" OnGridFocusedRowChanged="Grid_FocusedRowChanged">
<Columns>
<DxGridDataColumn FieldName="Id" SortIndex="0" SortOrder="GridColumnSortOrder.Descending" ReadOnly="true" />
@ -46,7 +46,7 @@
<ToolbarTemplate>
@if (IsMasterGrid)
{
<FruitBankToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
<MgGridToolbarTemplate Grid="Grid" OnReloadDataClick="() => ReloadDataFromDb(true)" />
}
</ToolbarTemplate>
</GridStockTakingItemBase>

View File

@ -1,4 +1,5 @@
using AyCode.Core.Extensions;
using AyCode.Blazor.Components.Components.Grids;
using AyCode.Core.Extensions;
using AyCode.Core.Loggers;
using AyCode.Utils.Extensions;
using DevExpress.Blazor;
@ -13,12 +14,45 @@ using System.Reflection;
namespace FruitBankHybrid.Shared.Components;
public class MgGridBase : DxGrid
public class MgGridBase : DxGrid, IMgGridBase
{
private bool _isFirstInitializeParameterCore;
private bool _isFirstInitializeParameters;
public bool PreRendered { get; set; }
/// <inheritdoc />
public bool IsSyncing => false;
/// <inheritdoc />
public MgGridEditState GridEditState { get; private set; } = MgGridEditState.None;
/// <inheritdoc />
[CascadingParameter]
public IMgGridBase? ParentGrid { get; set; }
/// <inheritdoc />
public IMgGridBase GetRootGrid()
{
var current = (IMgGridBase)this;
while (current.ParentGrid != null)
{
current = current.ParentGrid;
}
return current;
}
/// <inheritdoc />
public IInfoPanelBase? InfoPanelInstance { get; set; }
/// <inheritdoc />
public bool IsFullscreen => false;
/// <inheritdoc />
public void ToggleFullscreen()
{
// Not implemented in this legacy class - will be removed
}
[Inject] public required IEnumerable<IAcLogWriterClientBase> LogWriters { get; set; }
[Inject] public required FruitBankSignalRClient FruitBankSignalRClient { get; set; }
[Inject] public required LoggedInModel LoggedInModel { get; set; }
@ -138,6 +172,33 @@ public class MgGridBase : DxGrid
//StateHasChanged();
}
}
/// <summary>
/// Navigates to the previous row in the grid
/// </summary>
public void StepPrevRow()
{
var currentIndex = GetFocusedRowIndex();
if (currentIndex > 0)
{
SetFocusedRowIndex(currentIndex - 1);
}
}
/// <summary>
/// Navigates to the next row in the grid
/// </summary>
public void StepNextRow()
{
var currentIndex = GetFocusedRowIndex();
var visibleRowCount = GetVisibleRowCount();
if (currentIndex >= 0 && currentIndex < visibleRowCount - 1)
{
SetFocusedRowIndex(currentIndex + 1);
}
}
public bool ShowInfoPanel { get; set; } = false;
public string Caption { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
async Task Grid_LayoutAutoLoading(GridPersistentLayoutEventArgs e)
{

View File

@ -1,148 +0,0 @@
@using AyCode.Blazor.Components.Components.Grids
@using AyCode.Core.Loggers;
@using AyCode.Core.Extensions
@using AyCode.Core.Helpers
@using AyCode.Utils.Extensions
@using FruitBank.Common.Dtos
@using FruitBank.Common.Entities
@using FruitBank.Common.Models
@using FruitBankHybrid.Shared.Components.Grids.ShippingItems
@using FruitBankHybrid.Shared.Databases
@using FruitBankHybrid.Shared.Services.Loggers;
@using FruitBankHybrid.Shared.Services.SignalRs
@implements IDisposable
@inject IEnumerable<IAcLogWriterClientBase> LogWriters
@inject FruitBankSignalRClient FruitBankSignalRClient
@inject LoggedInModel LoggedInModel;
<ToolbarBase @ref="Toolbar" Grid="Grid" ItemRenderStyleMode="ToolbarRenderStyleMode.Plain">
<DxToolbarItem Text="New" Click="NewItem_Click" IconCssClass="grid-toolbar-new" Enabled="LoggedInModel.IsAdministrator" />
<DxToolbarItem Text="Edit" Click="EditItem_Click" IconCssClass="grid-toolbar-edit" Enabled="@(LoggedInModel.IsAdministrator && EditItemsEnabled)" />
<DxToolbarItem Text="Delete" Click="DeleteItem_Click" IconCssClass="grid-toolbar-delete" Enabled="@(LoggedInModel.IsDeveloper && EditItemsEnabled)" />
<DxToolbarItem Text="Column Chooser" BeginGroup="true" Click="ColumnChooserItem_Click" IconCssClass="grid-toolbar-column-chooser" />
<DxToolbarItem Text="Export" IconCssClass="grid-toolbar-export" Enabled="@(LoggedInModel.IsAdministrator && EditItemsEnabled)">
<Items>
<DxToolbarItem Text="To CSV" Click="ExportCsvItem_Click" />
<DxToolbarItem Text="To XLSX" Click="ExportXlsxItem_Click" />
<DxToolbarItem Text="To XLS" Click="ExportXlsItem_Click" />
<DxToolbarItem Text="To PDF" Click="ExportPdfItem_Click" />
</Items>
</DxToolbarItem>
<DxToolbarItem Text="Reload data" BeginGroup="true" Click="ReloadData_Click" Enabled="@BtnReloadDataEnabled" />
<DxToolbarItem BeginGroup="true">
</DxToolbarItem>
@ToolbarItemsExtended
</ToolbarBase>
@code {
[Parameter] public IGrid Grid { get; set; }
[Parameter] public RenderFragment? ToolbarItemsExtended { get; set; }
[Parameter] public EventCallback<ToolbarItemClickEventArgs> OnReloadDataClick { get; set; }
public ToolbarBase Toolbar { get; set; }
const string ExportFileName = "ExportResult";
private bool _isReloadInProgress;
private bool _isGridSyncing;
private IMgGridBase? _mgGrid;
/// <summary>
/// Reload button is enabled only when no sync operation is in progress
/// </summary>
public bool BtnReloadDataEnabled => !_isReloadInProgress && !_isGridSyncing;
public bool EditItemsEnabled { get; set; } = true;
private LoggerClient<GridShippingItemTemplate> _logger;
protected override void OnInitialized()
{
_logger = new LoggerClient<GridShippingItemTemplate>(LogWriters.ToArray());
}
protected override void OnParametersSet()
{
// Subscribe to grid syncing state changes if Grid implements IMgGridBase
if (Grid is IMgGridBase mgGrid && !ReferenceEquals(_mgGrid, mgGrid))
{
// Unsubscribe from previous grid
if (_mgGrid != null)
{
_mgGrid.OnSyncingStateChanged -= OnGridSyncingStateChanged;
}
_mgGrid = mgGrid;
_mgGrid.OnSyncingStateChanged += OnGridSyncingStateChanged;
// Get initial syncing state
_isGridSyncing = _mgGrid.IsSyncing;
}
}
private void OnGridSyncingStateChanged(bool isSyncing)
{
_isGridSyncing = isSyncing;
InvokeAsync(StateHasChanged);
}
public void Dispose()
{
if (_mgGrid != null)
{
_mgGrid.OnSyncingStateChanged -= OnGridSyncingStateChanged;
}
}
async Task ReloadData_Click(ToolbarItemClickEventArgs e)
{
_isReloadInProgress = true;
try
{
await OnReloadDataClick.InvokeAsync();
}
finally
{
_isReloadInProgress = false;
}
}
async Task NewItem_Click()
{
EditItemsEnabled = false;
await Grid.StartEditNewRowAsync();
}
async Task EditItem_Click()
{
EditItemsEnabled = false;
await Grid.StartEditRowAsync(Grid.GetFocusedRowIndex());
}
void DeleteItem_Click()
{
EditItemsEnabled = false;
Grid.ShowRowDeleteConfirmation(Grid.GetFocusedRowIndex());
}
void ColumnChooserItem_Click(ToolbarItemClickEventArgs e)
{
Grid.ShowColumnChooser();
}
async Task ExportXlsxItem_Click()
{
await Grid.ExportToXlsxAsync(ExportFileName);
}
async Task ExportXlsItem_Click()
{
await Grid.ExportToXlsAsync(ExportFileName);
}
async Task ExportCsvItem_Click()
{
await Grid.ExportToCsvAsync(ExportFileName);
}
async Task ExportPdfItem_Click()
{
await Grid.ExportToPdfAsync(ExportFileName);
}
}

View File

@ -1,17 +0,0 @@
using DevExpress.Blazor;
using DevExpress.Blazor.Navigation.Internal;
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FruitBankHybrid.Shared.Components.Toolbars
{
public class ToolbarBase : DxToolbar
{
[Parameter] public IGrid Grid { get; set; }
[Parameter] public Func<ToolbarItemClickEventArgs, Task> RefreshClick { get; set; }
}
}

View File

@ -68,4 +68,8 @@
<HintPath>..\..\NopCommerce.Common\4.70\Libraries\Mango.Nop.Core\bin\FruitBank\Debug\net9.0\Mango.Nop.Core.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="Components\Toolbars\" />
</ItemGroup>
</Project>

View File

@ -61,7 +61,8 @@ public partial class MainLayout : LayoutComponentBase
}
else if (LoggedInModel.IsLoggedIn)
{
StateHasChanged(); // Refresh UI after successful auto-login
RefreshMainLayout();
//StateHasChanged(); // Refresh UI after successful auto-login
}
}

View File

@ -33,7 +33,7 @@ namespace FruitBankHybrid.Shared.Services.SignalRs
// .WithStatefulReconnect()
// .WithKeepAliveInterval(TimeSpan.FromSeconds(60))
// .WithServerTimeout(TimeSpan.FromSeconds(120))
EnableBinaryDiagnostics = FruitBankConstClient.SignalRSerializerDiagnosticLog;
ConstHelper.NameByValue<SignalRTags>(0);
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,154 @@
window.pdfViewer = {
_pdfCache: new Map(),
_resizeObserver: null,
_currentContainerId: null,
_currentPdfUrls: null,
_renderTimeout: null,
_lastRenderedUrls: null, // Track what was last rendered
renderPdfs: async function (containerId, pdfUrls) {
// Wait for container to be available
let container = null;
for (let i = 0; i < 50; i++) {
container = document.getElementById(containerId);
if (container) break;
await new Promise(resolve => setTimeout(resolve, 100));
}
if (!container) {
console.error('Container not found after waiting:', containerId);
return;
}
// Check if URLs changed - if same, skip render (use cache)
const urlsKey = JSON.stringify(pdfUrls);
if (this._lastRenderedUrls === urlsKey && this._currentContainerId === containerId) {
console.log('[PDF] Same URLs, skipping render (cached)');
return;
}
console.log('[PDF] New URLs detected, rendering:', pdfUrls);
// Store for resize handling
this._currentContainerId = containerId;
this._currentPdfUrls = pdfUrls;
this._lastRenderedUrls = urlsKey;
// Setup resize observer
this._setupResizeObserver(container);
// Render
await this._doRender(container, pdfUrls);
},
_setupResizeObserver: function(container) {
// Clean up previous observer
if (this._resizeObserver) {
this._resizeObserver.disconnect();
}
let lastWidth = container.clientWidth;
this._resizeObserver = new ResizeObserver((entries) => {
const newWidth = entries[0].contentRect.width;
// Only re-render if width changed significantly (more than 5px)
if (Math.abs(newWidth - lastWidth) > 5) {
lastWidth = newWidth;
// Debounce the re-render
if (this._renderTimeout) {
clearTimeout(this._renderTimeout);
}
this._renderTimeout = setTimeout(() => {
this._doRender(container, this._currentPdfUrls);
}, 150);
}
});
this._resizeObserver.observe(container);
},
_doRender: async function(container, pdfUrls) {
container.innerHTML = '';
if (typeof pdfjsLib === 'undefined') {
console.error('PDF.js not loaded');
container.innerHTML = '<p style="color:red;">PDF.js nincs betöltve</p>';
return;
}
const pixelRatio = window.devicePixelRatio || 1;
const scrollbarWidth = 17;
const containerWidth = container.clientWidth - scrollbarWidth;
if (containerWidth <= 0) {
console.log('Container width is 0, skipping render');
return;
}
console.log('[PDF] Rendering at width:', containerWidth, 'URLs:', pdfUrls);
for (const url of pdfUrls) {
try {
// Use cached PDF document if available (PDF.js document cache)
let pdf = this._pdfCache.get(url);
if (!pdf) {
console.log('[PDF] Loading new PDF:', url);
const loadingTask = pdfjsLib.getDocument(url);
pdf = await loadingTask.promise;
this._pdfCache.set(url, pdf);
} else {
console.log('[PDF] Using cached PDF:', url);
}
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
const page = await pdf.getPage(pageNum);
const viewport = page.getViewport({ scale: 1 });
const scale = containerWidth / viewport.width;
const displayHeight = viewport.height * scale;
const scaledViewport = page.getViewport({ scale: scale * pixelRatio });
const canvas = document.createElement('canvas');
canvas.width = scaledViewport.width;
canvas.height = scaledViewport.height;
canvas.style.width = containerWidth + 'px';
canvas.style.height = displayHeight + 'px';
canvas.style.marginBottom = '8px';
canvas.style.display = 'block';
container.appendChild(canvas);
const context = canvas.getContext('2d');
await page.render({
canvasContext: context,
viewport: scaledViewport
}).promise;
}
} catch (error) {
console.error('[PDF] Error rendering:', url, error);
const errorDiv = document.createElement('div');
errorDiv.textContent = 'Hiba a PDF betöltésekor: ' + url + ' - ' + error.message;
errorDiv.style.color = 'red';
errorDiv.style.marginBottom = '8px';
container.appendChild(errorDiv);
}
}
},
dispose: function() {
if (this._resizeObserver) {
this._resizeObserver.disconnect();
this._resizeObserver = null;
}
if (this._renderTimeout) {
clearTimeout(this._renderTimeout);
}
// Keep PDF cache for performance, only clear on full dispose
this._pdfCache.clear();
this._lastRenderedUrls = null;
},
// Clear only the render cache (not PDF documents)
clearRenderCache: function() {
this._lastRenderedUrls = null;
}
};

View File

@ -0,0 +1,46 @@
Fecha Cliente Albarán FRUITBANK KFT (BONFRED MERCA-BARNA)
HU14902170
12/11/2025 506619 AH25007715 MERCA-BARNA
Trasportista: HNOS. GUERRERO HARO S.L. 08040-BARCELONA
Barcelona
Matrícula: / Porte: Origen ESPAÑA
Incoterm: CPT Volumen:1,0 Palet
Fecha Entrega: 13/11/2025
Descripción Bultos Piezas K.Neto Precio Suma %Iva
6,25B
AGUACATE BACON GLOBALG.A.P. CAL. 14 77 481,25 0
Envase: CT MANZANO NEGRO 40*30*9.5
CAT: I - Origen: ESPAÑA
AGUACATE BACON GLOBALG.A.P. CAL. 20 115 6,25B 718,75 0
Envase: CT MANZANO NEGRO 40*30*9.5
CAT: I - Origen: ESPAÑA
AGUACATE HASS GLOBALG.A.P. CAL. 20 48 13,50B 648,00 0
Envase: CT MANZANO NEGRO 40*30*9.5
CAT: I - Origen: PERU
Producto Certif. GLOBALG.A.P. GGN: 8436025340012
Bultos Sumas Dto 1 Dto 2 B.Imponible %Iva Iva Total
240 0,00 0,00% 0,00 1.848,00 0% 0,00 1.848,00
1.848,00 0,00%
FRUTAS RAFAEL MANZANO E HIJOS S.L. no se responsabiliza de ninguna reclamación por no conformidad de la mercancía, que no haya sido comunicada
por escrito en un plazo máximo de DOS DÍAS desde la recepción de la misma, debiendo acompañar justificación de la no conformidad (informe pericial
efectuado por profesional acreditado) además del detalle de los números de lotes y cajas no conformes.
En las ventas a consignación, FRUTAS RAFAEL MANZANO E HIJOS S.L. establece un período de tiempo no superior a 20 DÍAS para acordar el precio de
facturación de la mercancía enviada. Transcurrido éste plazo son acordarlo, será facturada al valor del precio medio de mercado que tuviese el producto
el día en que se recepcionó la mercancía, incrementando el importe del transporte , si es por cuenta de FRUTAS RAFAEL MANZANO E HIJOS S.L.
Nº Registro de Productores de Producto: ENV/2023/000016586.

View File

@ -0,0 +1,38 @@
F A C T U R A de V E N T A
Victor i Merce , S.L. FECHA NUMERO CIF / NIF PÁGINA
Mercabarna, Pab.F-6035-6036 13/11/2025 FV-015444 HU14902170 1 de 1
08040 Barcelona Barcelona (Spain)
C.I.F.: ESB61478095 FRUIT BANK KFT
Tlf: 93 335 02 44 RIPPL-RONAI UTCA 18
comercialventas@victorimerce.com 1068 BUDAPEST
HUNGRIA (HU)
IBAN: ES08 2100 0749 16 0200163991
BIC:CAIXESBBXXX CAIXESBBXXX
IBAN:
BIC:CAIXESBBXXX
LOTE ARTICULO ENV BULTOS BRUTO TARA NETO PRECIO IMPORTE
3501544401003 PERA WILLIAMS I *16* ESP. 192 927,00 0,40 850,20 1,90 1.615,38
3501544402000 PERA WILLIAMS I *16* ESP. 0,40 832,20 1,90 1.581,18
3501544403007 PERA WILLIAMS I *18* ESP. ES 192 909,00 0,40 863,20 1,80 1.553,76
3501544404004 PERA WILLIAMS I *20* ESP. 0,40 643,20 1,70 1.093,44
192 940,00
192 720,00
Recibí: Total Mercancía 5.843,76
Fianza por envases 0,00
Vencimiento a: 30 días Base Imponible
IVA al 0,00% 5.843,76
R. Equivalencia al 0,00% 0,00
0,00
Total
5.843,76
Información Básica Sobre Protección De Datos - Gestión económica y administrativa. Responsable: VICTOR I MERCE FRUITS SL. Finalidad: Gestión administrativa, facturación, contabilidad y obligaciones legales. Derechos: Acceder,
rectificar y suprimir los datos, así como otros derechos, como se explica en la información adicional. Información adicional. Puede consultar la información adicional y detallada sobre Protección de Datos en la siguiente dirección de
correo electrónico gerencia@victorimerce.com.

View File

@ -0,0 +1,74 @@
MANIPULADO TERCEROS
CARRETERA N-340, KM. 415 . EL EJIDO . 04710 . ALMERÍA . ESPAÑA
. Tels. +34 950 347300 . Fax +34 950 581627
E-mail:terceros@hcostadealmeria.es . Web:http://www.hcostadealmeria.es
FRUITBANK KFT ALBARÁN FECHA fra_26008338
RIPPL-RONAI U.18 6 - 749 12/11/2025
BUDAPEST 1068 FACTURA
HUNGRIA FECHA N.I.F. 06-549
HUNGRÍA 12/11/2025 HU14902170
CLIENTE
6705
Descripción Bultos Piezas K.netos Precio Importe
PRODUCTO GLOBALG.A.P. Y SPRING GGN 8429302390008 70 0
220 0
Origen del producto: ESPAÑA 40 0
Albaran: 749
PTO CALIFORNIA VERDE 400X300X185/19 I GG QDELICIAS 350 1,7000 595,00
1.100 2,4500 2.695,00
PTO CALIFORNIA ROJO 400X300X185 I GG QDELICIAS 1,9000 380,00
200
PTO CALIFORNIA AMARILLO 400X300X185 I GG QDELICIAS
Observaciones: 3 PALETS 100X120
330 0 1.650 3.670,00
3.670,00€
CONDICIONES DE COMPRA-VENTA Base imponible % Importe I.V.A. % Importe R.E.
3.670,00€
1º Recibí, con completa conformidad mercacías, precios, 0,00 0,00 0,00 0,00
envases e importe total de compra aquí realizada. 3.670,00 / TOTAL FACTURA
2º El envasado y carga de la mercancía son a cargo del EXW
comprador.
3º En caso de litigio el comprador renuncia a su propio
fuero y se someta a la competecia de los Juzgados de Almería.
4º Se trata de venta en origen, recordamos de su exigencia
de normalizar la venta de destino según la legislación vigente
(R.D. 2192/84). El comprado o persona autorizada
5º MANIPULADO TERCEROS, no se responsa- Matricula vehiculo:
biliza de ninguna reclamación que no haya sido comunicada CLAÚSULA INCOTERMS:
por escrito en un plazo máximo de 48 horas desde la
recepción de la mercancía.
FACTURA FECHA CLIENTE INSTRUCCIONES DE PAGO:
549 12/11/2025 6705 - FRUITBANK KFT A Vencimiento, sirvase ingresar en nuestra cuenta, como sigue:
ENVASES -Banco: CAJAMAR (CAJA RURAL INTERMEDITERRÁNEA S.C.C)
ENTREGA RETIRA
-Entidad/Sucursal: 3058-0040
-IBAN: ES6730580040381011800052
-Swift Code: CCRIES2A
-Beneficiario: HORTOFRUTICOLA COSTA DE ALMERIA, S.L.
CIF:B04257028
DIRECCIÓN FISCAL: HORTOFRUTÍCOLA COSTA DE ALMERÍA, S.L.; PLAZA HUERTA DE EUROPA, 1; 04740; ROQUETAS DE MAR; ALMERÍA; ESPAÑA; TELS. +34950326232; INFO@HCOSTADEALMERIA.ES

View File

@ -0,0 +1,45 @@
Invoice To Sales order Confirmation
Fruitbank Kft
Rippl - Ronai 18 Invoice Date : 13/11/2025
1068 BUDAPEST Delivery Date : 13/11/2025
HUNGARY Currency : EUR
Order Number : 637462
Delivered To Order Reference :
Fruitbank KFT Customer VAT No
Rippl - Ronai 18 Payment Terms : HU14902170
1068 BUDAPEST : 21 Days after invoice date
HUNGARY
Gross Net Unit VAT Total
Product Variety Size C.O Qty Weight Weight Price (%) Price
Blackberries -
Blueberries - 12x125g NL 10 20,00 15,00 30,00 0 300,00
Limes Tahiti
Mango by air Kent 12x125g 18+ PE 390 752,00 585,00 11,00 0 4.290,00
Red currant -
42 BR 240 1.067,00 960,00 6,75 0 1.620,00
Total
9 BR 15 99,70 90,00 33,00 0 495,00
12x125g NL 20 42,50 30,00 17,50 0 350,00
675 1.981,20 1.680,00 7.055,00
Roveg Fruit B.V. Roveg Tel +31 (0)180-635700 Handelsregister 30166109 EURO/GBP/USD: NL59 RABO 0316 9215 64
Nijverheidsweg 20 Roveg Fax +31 (0)180-635750 BTW nr NL808838210B01
2742 RG Waddinxveen Email db@roveg.nl Swift: RABONL2U
PO Box 309 www.roveg.nl
2740 AH Waddinxveen Global GAP CoC 8713807000006
Global GAP NG: Non Global GAP
NL-BIO-01 SKAL 018903
The terms and conditions of Roveg Fruit B.V., with its registered office in Waddinxveen, are filed at the Chamber of Commerce
Rotterdam No 30166109. The terms and conditions apply to all transactions and are available on request.
The exporter of the products covered by this document (customs authorization NL/311/01/826) declares that, except where otherwise
clearly indicated, these products are of EU preferential origin
Pagina 1 van 1

View File

@ -1,4 +1,6 @@
using AyCode.Core.Loggers;
using AyCode.Services.SignalRs;
using FruitBank.Common;
using FruitBank.Common.Loggers;
using FruitBank.Common.Models;
using FruitBank.Common.Services;
@ -29,4 +31,11 @@ builder.Services.AddSingleton<DatabaseClient>();
builder.Services.AddSingleton<IAcLogWriterClientBase, SignaRClientLogItemWriter>();
#if DEBUG
if (FruitBankConstClient.SignalRSerializerDiagnosticLog)
{
SignalResponseDataMessage.DiagnosticLogger = message => { Console.WriteLine(message); };
}
#endif
await builder.Build().RunAsync();

View File

@ -15,6 +15,7 @@
<link href=@AppendVersion("_content/FruitBankHybrid.Shared/bootstrap/bootstrap.min.css") rel="stylesheet" />
<link href=@AppendVersion("_content/FruitBankHybrid.Shared/app.css") rel="stylesheet" />
<link href=@AppendVersion("_content/AyCode.Blazor.Components/css/mg-grid-info-panel.css") rel="stylesheet" />
<link href=@AppendVersion("FruitBankHybrid.Web.styles.css") rel="stylesheet" />
<ImportMap />
@ -24,6 +25,12 @@
<body class="dxbl-theme-fluent">
<Routes @rendermode="InteractiveWebAssembly" />
<script src="_content/AyCode.Blazor.Components/js/mgGridInfoPanel.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<script>
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
</script>
<script src="_content/FruitBankHybrid.Shared/js/pdfViewer.js"></script>
<script src="@Assets["_framework/blazor.web.js"]"></script>
</body>

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 B

View File

@ -13,7 +13,7 @@
<Properties>
<DisplayName>$placeholder$</DisplayName>
<PublisherDisplayName>User Name</PublisherDisplayName>
<Logo>$placeholder$.png</Logo>
<Logo>Assets\splashSplashScreen.png</Logo>
</Properties>
<Dependencies>
@ -30,11 +30,11 @@
<uap:VisualElements
DisplayName="$placeholder$"
Description="$placeholder$"
Square150x150Logo="$placeholder$.png"
Square44x44Logo="$placeholder$.png"
Square150x150Logo="Assets\splashSplashScreen.png"
Square44x44Logo="Assets\splashSplashScreen.png"
BackgroundColor="transparent">
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
<uap:SplashScreen Image="$placeholder$.png" />
<uap:DefaultTile Square71x71Logo="Assets\splashSplashScreen.png" Wide310x150Logo="Assets\splashSplashScreen.png" Square310x310Logo="Assets\splashSplashScreen.png" />
<uap:SplashScreen Image="Assets\splashSplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
@ -44,3 +44,8 @@
</Capabilities>
</Package>

View File

@ -0,0 +1 @@
Placeholder PNG file for packaging. Replace with a real image file at this path (Platforms/Windows/splashSplashScreen.png).

View File

@ -10,8 +10,10 @@
<link href="_content/DevExpress.Blazor.Themes.Fluent/global.min.css" data-theme-id="fluent-blueLight" rel="stylesheet" />
<link href="_content/DevExpress.Blazor.Themes.Fluent/modes/light.min.css" data-theme-id="fluent-blueLight" rel="stylesheet" />
<link href="_content/DevExpress.Blazor.Themes.Fluent/accents/blue.min.css" data-theme-id="fluent-blueLight" rel="stylesheet" />
<link href="_content/DevExpress.Blazor/dx-blazor.css" rel="stylesheet" />
<link href="_content/FruitBankHybrid.Shared/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="_content/FruitBankHybrid.Shared/app.css" rel="stylesheet" />
<link href="_content/AyCode.Blazor.Components/css/mg-grid-info-panel.css" rel="stylesheet" />
<link href="app.css" rel="stylesheet" />
<link href="FruitBankHybrid.styles.css" rel="stylesheet" />
<link rel="icon" href="data:,">