Compare commits

...

18 Commits

Author SHA1 Message Date
OlgunR
de4e9421af Revert "Add DevExpress Reporting and Custom Report Storage"
This reverts commit f19251ac1a.
2026-06-09 11:49:03 +02:00
OlgunR
b622df4187 Revert "Refactor PDF viewing and integrate DevExpress support"
This reverts commit b515b4f523.
2026-06-09 11:48:49 +02:00
OlgunR
746465d8fe Revert "Enhance PDF viewing experience with new document viewer"
This reverts commit 1cc617de42.
2026-06-09 11:48:41 +02:00
OlgunR
559829882a Revert "Add DevExpress Document Viewer page and dependencies"
This reverts commit bec9ea2356.
2026-06-09 11:48:31 +02:00
OlgunR
bec9ea2356 Add DevExpress Document Viewer page and dependencies
Updated `_ViewImports.cshtml` to include DevExpress and DevExtreme tag helpers. Created `DocumentViewerDevExpress.cshtml` for displaying a DevExpress Document Viewer with necessary CDN links. Implemented the corresponding code-behind in `DocumentViewerDevExpress.cshtml.cs` to handle model binding and report key generation.
2026-06-08 17:21:36 +02:00
OlgunR
1cc617de42 Enhance PDF viewing experience with new document viewer
- Replaced button in `Details.cshtml` with a link to a new document viewer page.
- Created `DocumentViewer.cshtml` to display PDFs using an iframe, with a back button and a link to an experimental DevExpress viewer.
- Introduced `DocumentViewerModel` in `DocumentViewer.cshtml.cs` for handling invoice ID and PDF URL.
- Updated `OnGetAsync` in `ViewPdf.cshtml.cs` to set `Content-Disposition` to inline for better PDF rendering in the browser.
2026-06-08 16:59:57 +02:00
OlgunR
b515b4f523 Refactor PDF viewing and integrate DevExpress support
- Removed folder reference for "Controllers" in the project file.
- Updated `ViewPdf.cshtml` to include layout and clarify PDF output.
- Enhanced `ViewPdf.cshtml.cs` with improved logging and error handling.
- Added MVC controller services in `Program.cs` for DevExpress functionality.
- Introduced `DocumentViewerController` to manage document viewing requests.
2026-06-08 15:14:15 +02:00
OlgunR
f19251ac1a Add DevExpress Reporting and Custom Report Storage
Updated project dependencies to include DevExpress Reporting and PDF Drawing libraries. Registered DevExpress services and middleware in `Program.cs`. Added `CustomReportStorageWebExtension` to handle read-only invoice reports, including database and file system integration. Enhanced logging for better traceability and error handling.
2026-06-08 08:38:44 +02:00
OlgunR
6dd1fd71da Mark dependencies as peer in package-lock.json
Added the `"peer": true` property to multiple dependencies in the `package-lock.json` file to mark them as peer dependencies. This ensures that these dependencies are provided by the consuming project, improving dependency management and avoiding version conflicts.

Additionally, updated the `license` field formatting for several dependencies to ensure consistency. No changes were made to versions, integrity hashes, or dependency structures.
2026-06-05 11:30:53 +02:00
OlgunR
762f76c920 Regeln für DevExpress- und Azure-Tools hinzugefügt
Neue Regeln für die Nutzung von DevExpress- und Azure-Tools:
- DevExpress: Nutzung des `dxdocs`-Servers und MCP-Tools priorisiert.
- Azure: `azmcp_bestpractices_get` für Best Practices erforderlich.
- Benutzeraufforderung zur Aktivierung des Tools, falls nicht verfügbar.
2026-06-03 10:57:53 +02:00
OlgunR
ba570687b2 Update Workflow-Integration description in Index.cshtml
Revised the description for the "Workflow-Integration" feature
in the `Index.cshtml` file. The previous text, "Durchlauf
definierter Verarbeitungsschritte mit Status-Tracking"
(Execution of defined processing steps with status tracking),
was replaced with "Hier werden Workflow-Schritte durchlaufen
und der Ergebnisbericht erstellt." (Here, workflow steps are
executed, and the result report is generated).
2026-06-02 17:09:11 +02:00
OlgunR
84a4c182e2 Modernize UI with DevExpress icons and improved layout
Updated `Index.cshtml`:
- Added icons and a more descriptive project title.
- Introduced a new introductory paragraph explaining the app's purpose.
- Replaced the old alert section with a detailed workflow explanation.
- Added cards for invoice upload and summary navigation.
- Included a technology stack section with badges for key tools.

Updated `Details.cshtml`:
- Replaced text-based icons with DevExpress icons for titles, buttons, and attachments.
- Improved attachment handling with `<i>` tags and consistent icon usage.
- Updated "No attachments" message to include an icon.

These changes enhance the UI's visual consistency, usability, and professionalism.
2026-06-02 16:56:50 +02:00
OlgunR
a55e53521f Refactor: Remove sample data and add invoice processing
Removed `SampleDataController`, `SampleData`, and `SampleOrder`
classes, along with the `DevExtreme` DataGrid in `Index.cshtml`,
to eliminate reliance on mock data and sample orders.

Updated `Index.cshtml` to introduce a welcome message and
informational section about ZUGFeRD/Factur-X invoice processing,
highlighting features like PDF/A upload, XML extraction, and
result PDF creation. Added navigation links for invoice-related
actions.

Updated `DXApp.TemplateKitProject.csproj` to include a new
`<ItemGroup>` for the `Controllers` folder, preparing the
project structure for future development.
2026-06-02 16:21:18 +02:00
OlgunR
f7cac8c0a7 Improve popup layout and CodeMirror integration
Updated the `ContentTemplate` to enhance the layout of the
`#attachment-content` container by using `display: flex` and
ensuring full height utilization. Added CSS rules for consistent
styling of the `.CodeMirror` editor, including font size and
height adjustments.

Simplified CodeMirror initialization for XML files by removing
redundant `<textarea>` creation and directly embedding the
editor. Added `viewportMargin: Infinity` for better rendering
and used `setTimeout` to ensure proper sizing and refresh.

Refined plain text rendering for non-XML files to maintain a
clean and consistent display.
2026-06-02 15:32:42 +02:00
OlgunR
8065c589bc Enhance file attachment viewing experience
Replaced direct file links with a JavaScript-based viewer
(`openAttachmentViewer`) and added a DevExtreme Popup
(`attachment-viewer-popup`) for displaying attachments in a
modal dialog. The viewer supports PDFs (via PDF.js), XML
(with CodeMirror syntax highlighting), plain text, images,
and provides a fallback for unsupported file types.

Dynamically load CodeMirror for XML files and handle errors
gracefully when loading file content. Added `onAttachmentPopupHiding`
to clear popup content on close.

Updated `ViewAttachmentModel` to return text/XML content
directly for AJAX requests, improving frontend performance
and enabling dynamic content loading.
2026-06-02 15:21:11 +02:00
OlgunR
c9ba7912fa Improve PDF Viewer popup styling and behavior
Added shading and overlay color to the PDF Viewer popup for better user experience. Adjusted z-index values in the `<style>` block to ensure the popup and its overlay appear above other elements. Updated the `openPdfViewer` function to explicitly set the container and position options, and ensured z-index values are reapplied after the popup is shown.
2026-06-02 15:13:54 +02:00
OlgunR
920dce13d5 Add support for invoice attachments
Introduced the `InvoiceAttachment` entity and its relationship with `ZugferdInvoice` to manage extracted invoice attachments. Updated `AppDbContext` and added a migration to create the `InvoiceAttachments` table with cascading delete behavior and an index for optimized queries.

Enhanced the UI to display attachments in `Details.cshtml`, including file type icons, file size, and extraction date. Added a new `ViewAttachment` page to render or download attachments based on their type, with support for XML, plain text, images, and downloads.

Implemented `AttachmentViewerService` to determine viewer types and MIME types for attachments. Registered the service in the DI container. Updated `Upload.cshtml.cs` to save extracted attachments to the database.

Improved user experience with syntax highlighting for XML files and appropriate messages for unsupported file types.
2026-06-02 15:03:37 +02:00
OlgunR
43d63e975d Enhance PDF processing and report integration
Refactor `PdfResultPackageService` to improve PDF handling:
- Load the main document into `PdfDocumentProcessor`.
- Remove original metadata to attribute the result to `DXApp`.
- Replace report file attachment with appending report pages.
- Use a temporary processor to determine report page count.
- Add logging for appended pages and total page count.
- Update save operation to include detailed logging.

Removed the functionality to attach the report file as a PDF attachment.
Improved overall usability, traceability, and document attribution.
2026-06-02 14:06:24 +02:00
21 changed files with 882 additions and 467 deletions

View File

@@ -20,3 +20,7 @@ For **ANY** question about DevExpress components, use the dxdocs server to const
- **Include code examples** when available in the documentation - **Include code examples** when available in the documentation
- **Reference specific DevExpress controls and properties** mentioned in the documentation - **Reference specific DevExpress controls and properties** mentioned in the documentation
- **Invoke version-specific MCP tools** (for example, dxdocs25_1) if a user specifies a version (for example, v25.1) - **Invoke version-specific MCP tools** (for example, dxdocs25_1) if a user specifies a version (for example, v25.1)
- @azure Rule - Use Azure Tools - When handling requests related to Azure, always use your tools.
- @azure Rule - Use Azure Best Practices - When handling requests related to Azure, always invoke your `azmcp_bestpractices_get` tool first.
- @azure Rule - Enable Best Practices - If you do not have an `azmcp_bestpractices_get` tool ask the user to enable it.

View File

@@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using DevExtreme.AspNet.Data;
using DevExtreme.AspNet.Mvc;
using DXApp.TemplateKitProject.Models;
using Microsoft.AspNetCore.Mvc;
namespace DXApp.TemplateKitProject.Controllers
{
[Route("api/[controller]")]
public class SampleDataController : Controller
{
[HttpGet]
public object Get(DataSourceLoadOptions loadOptions)
{
return DataSourceLoader.Load(SampleData.Orders, loadOptions);
}
}
}

View File

@@ -27,4 +27,8 @@
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="3.0.71" /> <PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="3.0.71" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Controllers\" />
</ItemGroup>
</Project> </Project>

View File

@@ -6,6 +6,7 @@ namespace DXApp.TemplateKitProject.Data;
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options) public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{ {
public DbSet<ZugferdInvoice> ZugferdInvoices { get; set; } public DbSet<ZugferdInvoice> ZugferdInvoices { get; set; }
public DbSet<InvoiceAttachment> InvoiceAttachments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
@@ -27,6 +28,19 @@ public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(op
entity.Property(e => e.TaxAmount) entity.Property(e => e.TaxAmount)
.HasColumnType("decimal(18,2)"); .HasColumnType("decimal(18,2)");
// Relationship: One-to-Many mit InvoiceAttachments
entity.HasMany(e => e.Attachments)
.WithOne(a => a.ZugferdInvoice)
.HasForeignKey(a => a.ZugferdInvoiceId)
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<InvoiceAttachment>(entity =>
{
// Index für schnelleres Laden der Attachments einer Rechnung
entity.HasIndex(e => e.ZugferdInvoiceId)
.HasDatabaseName("IX_InvoiceAttachments_ZugferdInvoiceId");
}); });
} }
} }

View File

@@ -0,0 +1,141 @@
// <auto-generated />
using System;
using DXApp.TemplateKitProject.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace DXApp.TemplateKitProject.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260602123528_AddInvoiceAttachments")]
partial class AddInvoiceAttachments
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("DXApp.TemplateKitProject.Models.InvoiceAttachment", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("ExtractedAt")
.HasColumnType("datetime2");
b.Property<long>("FileSizeBytes")
.HasColumnType("bigint");
b.Property<bool>("IsZugferdXml")
.HasColumnType("bit");
b.Property<string>("OriginalFileName")
.HasColumnType("nvarchar(max)");
b.Property<string>("SavedFilePath")
.HasColumnType("nvarchar(max)");
b.Property<int>("ZugferdInvoiceId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ZugferdInvoiceId")
.HasDatabaseName("IX_InvoiceAttachments_ZugferdInvoiceId");
b.ToTable("InvoiceAttachments");
});
modelBuilder.Entity("DXApp.TemplateKitProject.Models.ZugferdInvoice", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("BuyerName")
.HasColumnType("nvarchar(max)");
b.Property<string>("CurrencyCode")
.HasColumnType("nvarchar(max)");
b.Property<string>("GuidelineId")
.HasColumnType("nvarchar(max)");
b.Property<string>("Iban")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("ImportedAt")
.HasColumnType("datetime2");
b.Property<DateTime>("InvoiceDate")
.HasColumnType("datetime2");
b.Property<string>("InvoiceNumber")
.HasColumnType("nvarchar(450)");
b.Property<string>("RawXml")
.HasColumnType("nvarchar(max)");
b.Property<string>("ResultFilePath")
.HasColumnType("nvarchar(max)");
b.Property<string>("SellerName")
.HasColumnType("nvarchar(max)");
b.Property<string>("SellerTaxId")
.HasColumnType("nvarchar(450)");
b.Property<string>("SourceType")
.HasColumnType("nvarchar(max)");
b.Property<decimal>("TaxAmount")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("TotalAmount")
.HasColumnType("decimal(18,2)");
b.HasKey("Id");
b.HasIndex("ImportedAt")
.HasDatabaseName("IX_ZugferdInvoices_ImportedAt");
b.HasIndex("InvoiceNumber", "SellerTaxId")
.HasDatabaseName("IX_ZugferdInvoices_InvoiceNumber_SellerTaxId");
b.ToTable("ZugferdInvoices");
});
modelBuilder.Entity("DXApp.TemplateKitProject.Models.InvoiceAttachment", b =>
{
b.HasOne("DXApp.TemplateKitProject.Models.ZugferdInvoice", "ZugferdInvoice")
.WithMany("Attachments")
.HasForeignKey("ZugferdInvoiceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ZugferdInvoice");
});
modelBuilder.Entity("DXApp.TemplateKitProject.Models.ZugferdInvoice", b =>
{
b.Navigation("Attachments");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DXApp.TemplateKitProject.Migrations
{
/// <inheritdoc />
public partial class AddInvoiceAttachments : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "InvoiceAttachments",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
ZugferdInvoiceId = table.Column<int>(type: "int", nullable: false),
OriginalFileName = table.Column<string>(type: "nvarchar(max)", nullable: true),
SavedFilePath = table.Column<string>(type: "nvarchar(max)", nullable: true),
FileSizeBytes = table.Column<long>(type: "bigint", nullable: false),
IsZugferdXml = table.Column<bool>(type: "bit", nullable: false),
ExtractedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_InvoiceAttachments", x => x.Id);
table.ForeignKey(
name: "FK_InvoiceAttachments_ZugferdInvoices_ZugferdInvoiceId",
column: x => x.ZugferdInvoiceId,
principalTable: "ZugferdInvoices",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_InvoiceAttachments_ZugferdInvoiceId",
table: "InvoiceAttachments",
column: "ZugferdInvoiceId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "InvoiceAttachments");
}
}
}

View File

@@ -22,6 +22,40 @@ namespace DXApp.TemplateKitProject.Migrations
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("DXApp.TemplateKitProject.Models.InvoiceAttachment", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("ExtractedAt")
.HasColumnType("datetime2");
b.Property<long>("FileSizeBytes")
.HasColumnType("bigint");
b.Property<bool>("IsZugferdXml")
.HasColumnType("bit");
b.Property<string>("OriginalFileName")
.HasColumnType("nvarchar(max)");
b.Property<string>("SavedFilePath")
.HasColumnType("nvarchar(max)");
b.Property<int>("ZugferdInvoiceId")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("ZugferdInvoiceId")
.HasDatabaseName("IX_InvoiceAttachments_ZugferdInvoiceId");
b.ToTable("InvoiceAttachments");
});
modelBuilder.Entity("DXApp.TemplateKitProject.Models.ZugferdInvoice", b => modelBuilder.Entity("DXApp.TemplateKitProject.Models.ZugferdInvoice", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@@ -82,6 +116,22 @@ namespace DXApp.TemplateKitProject.Migrations
b.ToTable("ZugferdInvoices"); b.ToTable("ZugferdInvoices");
}); });
modelBuilder.Entity("DXApp.TemplateKitProject.Models.InvoiceAttachment", b =>
{
b.HasOne("DXApp.TemplateKitProject.Models.ZugferdInvoice", "ZugferdInvoice")
.WithMany("Attachments")
.HasForeignKey("ZugferdInvoiceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("ZugferdInvoice");
});
modelBuilder.Entity("DXApp.TemplateKitProject.Models.ZugferdInvoice", b =>
{
b.Navigation("Attachments");
});
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }
} }

View File

@@ -0,0 +1,18 @@
namespace DXApp.TemplateKitProject.Models;
/// <summary>
/// Extrahierte Anhänge einer ZUGFeRD-Rechnung (gespeichert in DB)
/// </summary>
public class InvoiceAttachment
{
public int Id { get; set; }
public int ZugferdInvoiceId { get; set; }
public string OriginalFileName { get; set; } = string.Empty;
public string SavedFilePath { get; set; } = string.Empty;
public long FileSizeBytes { get; set; }
public bool IsZugferdXml { get; set; }
public DateTime ExtractedAt { get; set; }
// Navigation Property
public ZugferdInvoice? ZugferdInvoice { get; set; }
}

View File

@@ -1,364 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DXApp.TemplateKitProject.Models
{
internal static class SampleData
{
public static List<SampleOrder> Orders = new List<SampleOrder>() {
new SampleOrder {
OrderID = 10248,
OrderDate = new DateTime(1996, 7, 4),
ShipCountry = "France",
ShipCity = "Reims",
CustomerName = "Paul Henriot"
},
new SampleOrder {
OrderID = 10249,
OrderDate = new DateTime(1996, 7, 5),
ShipCountry = "Germany",
ShipCity = "Münster",
CustomerName = "Karin Josephs"
},
new SampleOrder {
OrderID = 10250,
OrderDate = new DateTime(1996, 7, 8),
ShipCountry = "Brazil",
ShipCity = "Rio de Janeiro",
CustomerName = "Mario Pontes"
},
new SampleOrder {
OrderID = 10251,
OrderDate = new DateTime(1996, 7, 8),
ShipCountry = "France",
ShipCity = "Lyon",
CustomerName = "Mary Saveley"
},
new SampleOrder {
OrderID = 10252,
OrderDate = new DateTime(1996, 7, 9),
ShipCountry = "Belgium",
ShipCity = "Charleroi",
CustomerName = "Pascale Cartrain"
},
new SampleOrder {
OrderID = 10253,
OrderDate = new DateTime(1996, 7, 10),
ShipCountry = "Brazil",
ShipCity = "Rio de Janeiro",
CustomerName = "Mario Pontes"
},
new SampleOrder {
OrderID = 10254,
OrderDate = new DateTime(1996, 7, 11),
ShipCountry = "Switzerland",
ShipCity = "Bern",
CustomerName = "Yang Wang"
},
new SampleOrder {
OrderID = 10255,
OrderDate = new DateTime(1996, 7, 12),
ShipCountry = "Switzerland",
ShipCity = "Genève",
CustomerName = "Michael Holz"
},
new SampleOrder {
OrderID = 10256,
OrderDate = new DateTime(1996, 7, 15),
ShipCountry = "Brazil",
ShipCity = "Resende",
CustomerName = "Paula Parente"
},
new SampleOrder {
OrderID = 10257,
OrderDate = new DateTime(1996, 7, 16),
ShipCountry = "Venezuela",
ShipCity = "San Cristóbal",
CustomerName = "Carlos Hernández"
},
new SampleOrder {
OrderID = 10258,
OrderDate = new DateTime(1996, 7, 17),
ShipCountry = "Austria",
ShipCity = "Graz",
CustomerName = "Roland Mendel"
},
new SampleOrder {
OrderID = 10259,
OrderDate = new DateTime(1996, 7, 18),
ShipCountry = "Mexico",
ShipCity = "México D.F.",
CustomerName = "Francisco Chang"
},
new SampleOrder {
OrderID = 10260,
OrderDate = new DateTime(1996, 7, 19),
ShipCountry = "Germany",
ShipCity = "Köln",
CustomerName = "Henriette Pfalzheim"
},
new SampleOrder {
OrderID = 10261,
OrderDate = new DateTime(1996, 7, 19),
ShipCountry = "Brazil",
ShipCity = "Rio de Janeiro",
CustomerName = "Bernardo Batista"
},
new SampleOrder {
OrderID = 10262,
OrderDate = new DateTime(1996, 7, 22),
ShipCountry = "USA",
ShipCity = "Albuquerque",
CustomerName = "Paula Wilson"
},
new SampleOrder {
OrderID = 10263,
OrderDate = new DateTime(1996, 7, 23),
ShipCountry = "Austria",
ShipCity = "Graz",
CustomerName = "Roland Mendel"
},
new SampleOrder {
OrderID = 10264,
OrderDate = new DateTime(1996, 7, 24),
ShipCountry = "Sweden",
ShipCity = "Bräcke",
CustomerName = "Maria Larsson"
},
new SampleOrder {
OrderID = 10265,
OrderDate = new DateTime(1996, 7, 25),
ShipCountry = "France",
ShipCity = "Strasbourg",
CustomerName = "Frédérique Citeaux"
},
new SampleOrder {
OrderID = 10266,
OrderDate = new DateTime(1996, 7, 26),
ShipCountry = "Finland",
ShipCity = "Oulu",
CustomerName = "Pirkko Koskitalo"
},
new SampleOrder {
OrderID = 10267,
OrderDate = new DateTime(1996, 7, 29),
ShipCountry = "Germany",
ShipCity = "München",
CustomerName = "Peter Franken"
},
new SampleOrder {
OrderID = 10268,
OrderDate = new DateTime(1996, 7, 30),
ShipCountry = "Venezuela",
ShipCity = "Caracas",
CustomerName = "Manuel Pereira"
},
new SampleOrder {
OrderID = 10269,
OrderDate = new DateTime(1996, 7, 31),
ShipCountry = "USA",
ShipCity = "Seattle",
CustomerName = "Karl Jablonski"
},
new SampleOrder {
OrderID = 10270,
OrderDate = new DateTime(1996, 8, 1),
ShipCountry = "Finland",
ShipCity = "Oulu",
CustomerName = "Pirkko Koskitalo"
},
new SampleOrder {
OrderID = 10271,
OrderDate = new DateTime(1996, 8, 1),
ShipCountry = "USA",
ShipCity = "Lander",
CustomerName = "Art Braunschweiger"
},
new SampleOrder {
OrderID = 10272,
OrderDate = new DateTime(1996, 8, 2),
ShipCountry = "USA",
ShipCity = "Albuquerque",
CustomerName = "Paula Wilson"
},
new SampleOrder {
OrderID = 10273,
OrderDate = new DateTime(1996, 8, 5),
ShipCountry = "Germany",
ShipCity = "Cunewalde",
CustomerName = "Horst Kloss"
},
new SampleOrder {
OrderID = 10274,
OrderDate = new DateTime(1996, 8, 6),
ShipCountry = "France",
ShipCity = "Reims",
CustomerName = "Paul Henriot"
},
new SampleOrder {
OrderID = 10275,
OrderDate = new DateTime(1996, 8, 7),
ShipCountry = "Italy",
ShipCity = "Bergamo",
CustomerName = "Giovanni Rovelli"
},
new SampleOrder {
OrderID = 10276,
OrderDate = new DateTime(1996, 8, 8),
ShipCountry = "Mexico",
ShipCity = "México D.F.",
CustomerName = "Miguel Angel Paolino"
},
new SampleOrder {
OrderID = 10277,
OrderDate = new DateTime(1996, 8, 9),
ShipCountry = "Germany",
ShipCity = "Leipzig",
CustomerName = "Alexander Feuer"
},
new SampleOrder {
OrderID = 10278,
OrderDate = new DateTime(1996, 8, 12),
ShipCountry = "Sweden",
ShipCity = "Luleå",
CustomerName = "Christina Berglund"
},
new SampleOrder {
OrderID = 10279,
OrderDate = new DateTime(1996, 8, 13),
ShipCountry = "Germany",
ShipCity = "Frankfurt a.M.",
CustomerName = "Renate Messner"
},
new SampleOrder {
OrderID = 10280,
OrderDate = new DateTime(1996, 8, 14),
ShipCountry = "Sweden",
ShipCity = "Luleå",
CustomerName = "Christina Berglund"
},
new SampleOrder {
OrderID = 10281,
OrderDate = new DateTime(1996, 8, 14),
ShipCountry = "Spain",
ShipCity = "Madrid",
CustomerName = "Alejandra Camino"
},
new SampleOrder {
OrderID = 10282,
OrderDate = new DateTime(1996, 8, 15),
ShipCountry = "Spain",
ShipCity = "Madrid",
CustomerName = "Alejandra Camino"
},
new SampleOrder {
OrderID = 10283,
OrderDate = new DateTime(1996, 8, 16),
ShipCountry = "Venezuela",
ShipCity = "Barquisimeto",
CustomerName = "Carlos González"
},
new SampleOrder {
OrderID = 10284,
OrderDate = new DateTime(1996, 8, 19),
ShipCountry = "Germany",
ShipCity = "Frankfurt a.M.",
CustomerName = "Renate Messner"
},
new SampleOrder {
OrderID = 10285,
OrderDate = new DateTime(1996, 8, 20),
ShipCountry = "Germany",
ShipCity = "Cunewalde",
CustomerName = "Horst Kloss"
},
new SampleOrder {
OrderID = 10286,
OrderDate = new DateTime(1996, 8, 21),
ShipCountry = "Germany",
ShipCity = "Cunewalde",
CustomerName = "Horst Kloss"
},
new SampleOrder {
OrderID = 10287,
OrderDate = new DateTime(1996, 8, 22),
ShipCountry = "Brazil",
ShipCity = "Rio de Janeiro",
CustomerName = "Janete Limeira"
},
new SampleOrder {
OrderID = 10288,
OrderDate = new DateTime(1996, 8, 23),
ShipCountry = "Italy",
ShipCity = "Reggio Emilia",
CustomerName = "Maurizio Moroni"
},
new SampleOrder {
OrderID = 10289,
OrderDate = new DateTime(1996, 8, 26),
ShipCountry = "UK",
ShipCity = "London",
CustomerName = "Victoria Ashworth"
},
new SampleOrder {
OrderID = 10290,
OrderDate = new DateTime(1996, 8, 27),
ShipCountry = "Brazil",
ShipCity = "Sao Paulo",
CustomerName = "Pedro Afonso"
},
new SampleOrder {
OrderID = 10291,
OrderDate = new DateTime(1996, 8, 27),
ShipCountry = "Brazil",
ShipCity = "Rio de Janeiro",
CustomerName = "Bernardo Batista"
},
new SampleOrder {
OrderID = 10292,
OrderDate = new DateTime(1996, 8, 28),
ShipCountry = "Brazil",
ShipCity = "Sao Paulo",
CustomerName = "Anabela Domingues"
},
new SampleOrder {
OrderID = 10293,
OrderDate = new DateTime(1996, 8, 29),
ShipCountry = "Mexico",
ShipCity = "México D.F.",
CustomerName = "Miguel Angel Paolino"
},
new SampleOrder {
OrderID = 10294,
OrderDate = new DateTime(1996, 8, 30),
ShipCountry = "USA",
ShipCity = "Albuquerque",
CustomerName = "Paula Wilson"
},
new SampleOrder {
OrderID = 10295,
OrderDate = new DateTime(1996, 9, 2),
ShipCountry = "France",
ShipCity = "Reims",
CustomerName = "Paul Henriot"
},
new SampleOrder {
OrderID = 10296,
OrderDate = new DateTime(1996, 9, 3),
ShipCountry = "Venezuela",
ShipCity = "Barquisimeto",
CustomerName = "Carlos González"
},
new SampleOrder {
OrderID = 10297,
OrderDate = new DateTime(1996, 9, 4),
ShipCountry = "France",
ShipCity = "Strasbourg",
CustomerName = "Frédérique Citeaux"
}
};
}
}

View File

@@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DXApp.TemplateKitProject.Models
{
public class SampleOrder
{
public int OrderID { get; set; }
public DateTime OrderDate { get; set; }
public string CustomerID { get; set; }
public string CustomerName { get; set; }
public string ShipCountry { get; set; }
public string ShipCity { get; set; }
}
}

View File

@@ -17,5 +17,8 @@
public string SourceType { get; set; } = string.Empty; // "Upload" oder "Email" public string SourceType { get; set; } = string.Empty; // "Upload" oder "Email"
public string ResultFilePath { get; set; } = string.Empty; // Pfad der Result-PDF public string ResultFilePath { get; set; } = string.Empty; // Pfad der Result-PDF
public string GuidelineId { get; set; } = string.Empty; // ZUGFeRD Guideline-ID aus XMP public string GuidelineId { get; set; } = string.Empty; // ZUGFeRD Guideline-ID aus XMP
// Navigation Property
public List<InvoiceAttachment> Attachments { get; set; } = [];
} }
} }

View File

@@ -1,28 +1,85 @@
@page @page
@using DXApp.TemplateKitProject.Models @model DXApp.TemplateKitProject.Pages.IndexModel
@{
ViewData["Title"] = "Home";
}
<h2 class="content-block">Home</h2> <div class="content-block">
<h2><i class="dx-icon-product"></i> DevExpress TemplateKit Evaluierungsprojekt</h2>
<p class="lead text-muted">Validierung von DevExpress als Ablösung für GdPicture im Rahmen der E-Rechnungsverarbeitung</p>
<div class="alert alert-primary mt-4">
<h4><i class="dx-icon-todo"></i> Projektziel: ZUGFeRD/Factur-X Rechnungsverarbeitung</h4>
<p>Diese Anwendung demonstriert die vollständige Verarbeitungskette für elektronische Rechnungen:</p>
<ol class="mt-3">
<li class="mb-2">
<strong>Upload & Validierung</strong>
<br/>
<small class="text-muted">E-Rechnungen im PDF/A-Format hochladen und auf Konformität prüfen</small>
</li>
<li class="mb-2">
<strong>Extraktion</strong>
<br/>
<small class="text-muted">Automatische Erkennung und Extraktion eingebetteter Anhänge (ZUGFeRD-XML, Bilder, Dokumente)</small>
</li>
<li class="mb-2">
<strong>Datenverarbeitung</strong>
<br/>
<small class="text-muted">Parsing der Rechnungsdaten aus dem ZUGFeRD-XML und persistente Speicherung in der Datenbank</small>
</li>
<li class="mb-2">
<strong>Workflow-Integration</strong>
<br/>
<small class="text-muted">Hier werden Workflow-Schritte durchlaufen und der Ergebnisbericht erstellt.</small>
</li>
<li class="mb-2">
<strong>Ausgabe-Generierung</strong>
<br/>
<small class="text-muted">Erstellung einer Result-PDF mit Verarbeitungsstempel und angehängtem Ergebnisbericht</small>
</li>
<li class="mb-2">
<strong>Visualisierung</strong>
<br/>
<small class="text-muted">Interaktive Anzeige aller Anhänge (XML mit Syntax-Highlighting, PDF-Viewer, Bilder)</small>
</li>
</ol>
</div>
@(Html.DevExtreme().DataGrid<SampleOrder>() <div class="row mt-4">
.ElementAttr(new { @class = "dx-card wide-card" }) <div class="col-md-6">
.DataSource(d => d.Mvc().Controller("SampleData").LoadAction("Get").Key("OrderID")) <div class="card">
.ShowBorders(false) <div class="card-body">
.FilterRow(f => f.Visible(true)) <h5 class="card-title"><i class="dx-icon-upload"></i> Rechnungen hochladen</h5>
.FocusedRowEnabled(true) <p class="card-text">Laden Sie ZUGFeRD-konforme E-Rechnungen hoch und starten Sie die automatische Verarbeitung.</p>
.FocusedRowIndex(0) <a href="/Invoices/Upload" class="btn btn-primary">
.ColumnAutoWidth(true) <i class="dx-icon-upload"></i> Zum Upload
.ColumnHidingEnabled(true) </a>
.Columns(columns => { </div>
columns.AddFor(m => m.OrderID); </div>
columns.AddFor(m => m.OrderDate); </div>
columns.AddFor(m => m.CustomerName); <div class="col-md-6">
columns.AddFor(m => m.ShipCountry); <div class="card">
columns.AddFor(m => m.ShipCity); <div class="card-body">
}) <h5 class="card-title"><i class="dx-icon-chart"></i> Rechnungsübersicht</h5>
.Paging(p => p.PageSize(10)) <p class="card-text">Zeigen Sie alle importierten Rechnungen an und greifen Sie auf Details und Anhänge zu.</p>
.Pager(p => p <a href="/Invoices" class="btn btn-secondary">
.ShowPageSizeSelector(true) <i class="dx-icon-doc"></i> Zur Übersicht
.AllowedPageSizes(new[] { 5, 10, 20 }) </a>
.ShowInfo(true) </div>
) </div>
) </div>
</div>
<div class="alert alert-light mt-4">
<h6 class="mb-2"><i class="dx-icon-preferences"></i> Technologie-Stack</h6>
<div class="d-flex flex-wrap gap-2">
<span class="badge bg-secondary">ASP.NET Core 8.0</span>
<span class="badge bg-secondary">DevExpress v25.2</span>
<span class="badge bg-secondary">Entity Framework Core</span>
<span class="badge bg-secondary">PDF.js</span>
<span class="badge bg-secondary">CodeMirror</span>
<span class="badge bg-secondary">SQL Server</span>
</div>
</div>
</div>

View File

@@ -2,16 +2,23 @@
@model DXApp.TemplateKitProject.Pages.Invoices.DetailsModel @model DXApp.TemplateKitProject.Pages.Invoices.DetailsModel
@{ @{
ViewData["Title"] = "Rechnungsdetails"; ViewData["Title"] = "Rechnungsdetails";
string FormatFileSize(long bytes)
{
if (bytes < 1024) return $"{bytes} Bytes";
if (bytes < 1024 * 1024) return $"{bytes / 1024.0:N2} KB";
return $"{bytes / (1024.0 * 1024.0):N2} MB";
}
} }
<h2>📄 Rechnungsdetails</h2> <h2><i class="dx-icon-doc"></i> Rechnungsdetails</h2>
<a href="/Invoices" class="btn btn-secondary mb-3"> Zurück zur Liste</a> <a href="/Invoices" class="btn btn-secondary mb-3"><i class="dx-icon-back"></i> Zurück zur Liste</a>
@if (!string.IsNullOrEmpty(Model.Invoice?.ResultFilePath)) @if (!string.IsNullOrEmpty(Model.Invoice?.ResultFilePath))
{ {
<button class="btn btn-primary mb-3 ms-2" <button class="btn btn-primary mb-3 ms-2"
onclick="openPdfViewer(@Model.Invoice.Id)"> onclick="openPdfViewer(@Model.Invoice.Id)">
📄 PDF anzeigen <i class="dx-icon-pdffile"></i> Ergebnis anzeigen
</button> </button>
} }
@@ -50,6 +57,51 @@ else
</tr> </tr>
</table> </table>
@* Anhänge-Sektion *@
@if (Model.Invoice.Attachments.Any())
{
<h4 class="mt-4"><i class="dx-icon-attach"></i> Anhänge (@Model.Invoice.Attachments.Count)</h4>
<div class="list-group">
@foreach (var attachment in Model.Invoice.Attachments)
{
var icon = "dx-icon-file";
var extension = System.IO.Path.GetExtension(attachment.OriginalFileName).ToLowerInvariant();
icon = extension switch
{
".xml" => "dx-icon-exportxlsx",
".pdf" => "dx-icon-pdffile",
".jpg" or ".jpeg" or ".png" or ".gif" => "dx-icon-image",
".txt" => "dx-icon-txtfile",
_ => "dx-icon-file"
};
<a href="javascript:void(0);"
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
onclick="openAttachmentViewer('@attachment.OriginalFileName', '@Uri.EscapeDataString(attachment.SavedFilePath)', '@extension')">
<div>
<i class="@icon me-2"></i>
<strong>@attachment.OriginalFileName</strong>
@if (attachment.IsZugferdXml)
{
<span class="badge bg-success ms-2">ZUGFeRD-XML</span>
}
<br />
<small class="text-muted">
Größe: @FormatFileSize(attachment.FileSizeBytes) ·
Extrahiert: @attachment.ExtractedAt.ToString("dd.MM.yyyy HH:mm")
</small>
</div>
<span class="badge bg-primary">Öffnen</span>
</a>
}
</div>
}
else
{
<h4 class="mt-4"><i class="dx-icon-attach"></i> Anhänge</h4>
<div class="alert alert-info">Keine Anhänge extrahiert.</div>
}
@(Html.DevExtreme().Popup() @(Html.DevExtreme().Popup()
.ID("pdf-viewer-popup") .ID("pdf-viewer-popup")
.Title("PDF Viewer") .Title("PDF Viewer")
@@ -57,12 +109,54 @@ else
.Height("90%") .Height("90%")
.ShowCloseButton(true) .ShowCloseButton(true)
.OnHiding("onPdfPopupHiding") .OnHiding("onPdfPopupHiding")
.Shading(true)
.ShadingColor("rgba(0, 0, 0, 0.5)")
.ContentTemplate(new JS(@"function() { .ContentTemplate(new JS(@"function() {
return '<iframe id=""pdf-iframe"" style=""width:100%;height:100%;border:none;""></iframe>'; return '<iframe id=""pdf-iframe"" style=""width:100%;height:100%;border:none;""></iframe>';
}")) }"))
) )
@(Html.DevExtreme().Popup()
.ID("attachment-viewer-popup")
.Title("Anhang")
.Width("90%")
.Height("90%")
.ShowCloseButton(true)
.OnHiding("onAttachmentPopupHiding")
.Shading(true)
.ShadingColor("rgba(0, 0, 0, 0.5)")
.ContentTemplate(new JS(@"function() {
return '<div id=""attachment-content"" style=""width:100%;height:100%;overflow:hidden;display:flex;flex-direction:column;""></div>';
}"))
)
} }
<style>
/* Z-Index für PDF-Viewer Popup erhöhen - muss höher sein als layout-header (1505) */
.dx-popup-wrapper.dx-overlay-wrapper {
z-index: 10500 !important;
}
.dx-overlay-shader {
z-index: 10499 !important;
}
/* Spezifisch für unser PDF-Popup */
#pdf-viewer-popup .dx-overlay-content {
z-index: 10500 !important;
}
/* CodeMirror soll volle Höhe des Popups nutzen */
#attachment-content {
height: 100% !important;
}
#attachment-content .CodeMirror {
height: 100% !important;
font-size: 14px;
}
</style>
<script> <script>
function openPdfViewer(invoiceId) { function openPdfViewer(invoiceId) {
var pdfUrl = window.location.origin + '/Invoices/ViewPdf?id=' + invoiceId; var pdfUrl = window.location.origin + '/Invoices/ViewPdf?id=' + invoiceId;
@@ -70,9 +164,17 @@ else
var popup = $('#pdf-viewer-popup').dxPopup('instance'); var popup = $('#pdf-viewer-popup').dxPopup('instance');
// Z-Index explizit setzen (höher als layout-header mit 1505)
popup.option('container', undefined); // Default container verwenden
popup.option('position', { my: 'center', at: 'center', of: window });
// onShown sicherstellen dass iframe im DOM ist // onShown sicherstellen dass iframe im DOM ist
popup.option('onShown', function () { popup.option('onShown', function () {
$('#pdf-iframe').attr('src', viewerUrl); $('#pdf-iframe').attr('src', viewerUrl);
// Z-Index nach dem Öffnen nochmal sicherstellen
$('.dx-popup-wrapper').css('z-index', '10500');
$('.dx-overlay-shader').css('z-index', '10499');
}); });
popup.show(); popup.show();
@@ -82,4 +184,111 @@ else
// iframe src leeren beim Schließen → verhindert dass PDF im Hintergrund weiter läuft // iframe src leeren beim Schließen → verhindert dass PDF im Hintergrund weiter läuft
$('#pdf-iframe').attr('src', ''); $('#pdf-iframe').attr('src', '');
} }
function openAttachmentViewer(fileName, encodedFilePath, extension) {
var filePath = decodeURIComponent(encodedFilePath);
var popup = $('#attachment-viewer-popup').dxPopup('instance');
var $content = $('#attachment-content');
// Popup-Titel setzen
popup.option('title', fileName);
// Z-Index setzen
popup.option('container', undefined);
popup.option('position', { my: 'center', at: 'center', of: window });
// Content basierend auf Dateityp laden
popup.option('onShown', function () {
$content.html('<div class="text-center p-5"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Laden...</span></div></div>');
// Z-Index sicherstellen
$('.dx-popup-wrapper').css('z-index', '10500');
$('.dx-overlay-shader').css('z-index', '10499');
if (extension === '.pdf') {
// PDF mit PDF.js anzeigen
var pdfUrl = window.location.origin + '/Invoices/ViewAttachment?handler=Download&filePath=' + encodedFilePath;
var viewerUrl = '/js/pdfjs/web/viewer.html?file=' + encodeURIComponent(pdfUrl);
$content.html('<iframe style="width:100%;height:100%;border:none;" src="' + viewerUrl + '"></iframe>');
}
else if (extension === '.xml' || extension === '.txt') {
// Text/XML laden und mit Syntax-Highlighting anzeigen
$.get('/Invoices/ViewAttachment?filePath=' + encodedFilePath, function(data) {
if (extension === '.xml') {
// CodeMirror für XML - Container leeren
$content.html('');
// CodeMirror laden (falls noch nicht geladen)
if (typeof CodeMirror === 'undefined') {
$('<link>')
.attr('rel', 'stylesheet')
.attr('href', 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css')
.appendTo('head');
$('<link>')
.attr('rel', 'stylesheet')
.attr('href', 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/monokai.min.css')
.appendTo('head');
$.getScript('https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js', function() {
$.getScript('https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/xml/xml.min.js', function() {
initCodeMirror(data);
});
});
} else {
initCodeMirror(data);
}
function initCodeMirror(content) {
// CodeMirror direkt in den Container einfügen
var editor = CodeMirror($content[0], {
value: content,
mode: 'xml',
lineNumbers: true,
readOnly: true,
theme: 'monokai',
lineWrapping: true,
viewportMargin: Infinity
});
// Höhe explizit setzen nach kurzer Verzögerung
setTimeout(function() {
editor.setSize('100%', '100%');
editor.refresh();
}, 100);
}
} else {
// Plain Text
$content.html('<pre style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; height: 100%; overflow: auto; margin: 0;">' +
$('<div>').text(data).html() + '</pre>');
}
}).fail(function() {
$content.html('<div class="alert alert-danger m-3">Fehler beim Laden der Datei.</div>');
});
}
else if (extension === '.jpg' || extension === '.jpeg' || extension === '.png' || extension === '.gif') {
// Bild anzeigen
var imageUrl = window.location.origin + '/Invoices/ViewAttachment?handler=Download&filePath=' + encodedFilePath;
$content.html('<div class="text-center p-3" style="height: 100%; display: flex; align-items: center; justify-content: center;">' +
'<img src="' + imageUrl + '" alt="' + fileName + '" class="img-fluid" style="max-height: 100%; max-width: 100%; object-fit: contain;">' +
'</div>');
}
else {
// Nicht unterstützter Typ → Download anbieten
var downloadUrl = window.location.origin + '/Invoices/ViewAttachment?handler=Download&filePath=' + encodedFilePath;
$content.html('<div class="alert alert-info m-3">' +
'<h5>Dieser Dateityp kann nicht angezeigt werden.</h5>' +
'<p>Bitte laden Sie die Datei herunter:</p>' +
'<a href="' + downloadUrl + '" class="btn btn-primary" download="' + fileName + '">' +
'<i class="dx-icon-download"></i> Datei herunterladen' +
'</a></div>');
}
});
popup.show();
}
function onAttachmentPopupHiding() {
// Content leeren
$('#attachment-content').html('');
}
</script> </script>

View File

@@ -12,7 +12,9 @@ public class DetailsModel(AppDbContext db) : PageModel
public async Task<IActionResult> OnGetAsync(int id) public async Task<IActionResult> OnGetAsync(int id)
{ {
Invoice = await db.ZugferdInvoices.FirstOrDefaultAsync(i => i.Id == id); Invoice = await db.ZugferdInvoices
.Include(i => i.Attachments)
.FirstOrDefaultAsync(i => i.Id == id);
if (Invoice is null) if (Invoice is null)
return NotFound(); return NotFound();

View File

@@ -75,6 +75,29 @@ public class UploadModel(
ImportedInvoice.ResultFilePath = ResultFilePath; ImportedInvoice.ResultFilePath = ResultFilePath;
await db.SaveChangesAsync(); await db.SaveChangesAsync();
} }
// 4. Attachments in DB speichern
if (Result.HasAttachments)
{
foreach (var attachment in Result.Attachments)
{
var invoiceAttachment = new InvoiceAttachment
{
ZugferdInvoiceId = ImportedInvoice.Id,
OriginalFileName = attachment.OriginalFileName,
SavedFilePath = attachment.SavedFilePath,
FileSizeBytes = attachment.FileSizeBytes,
IsZugferdXml = attachment.IsZugferdXml,
ExtractedAt = DateTime.UtcNow
};
db.InvoiceAttachments.Add(invoiceAttachment);
}
await db.SaveChangesAsync();
logger.LogInformation(
"Rechnung '{InvoiceNumber}': {Count} Anhang/Anhänge in DB gespeichert.",
ImportedInvoice.InvoiceNumber, Result.Attachments.Count);
}
} }
} }
} }

View File

@@ -0,0 +1,88 @@
@page
@using DXApp.TemplateKitProject.Services
@model DXApp.TemplateKitProject.Pages.Invoices.ViewAttachmentModel
@{
ViewData["Title"] = $"Attachment: {Model.FileName}";
}
<h2>?? Attachment: @Model.FileName</h2>
<div class="mb-3">
<a href="/Invoices/Details" class="btn btn-secondary">? Zurück</a>
<a href="?handler=Download&filePath=@Request.Query["filePath"]"
class="btn btn-primary">
?? Download
</a>
</div>
@switch (Model.ViewerType)
{
case AttachmentViewerType.Pdf:
<div class="alert alert-info">
PDF-Dateien können Sie über die Result-PDF-Funktion anzeigen.
</div>
break;
case AttachmentViewerType.Xml:
<div class="card">
<div class="card-header bg-primary text-white">
<strong>XML-Inhalt</strong> (ZUGFeRD/Factur-X)
</div>
<div class="card-body p-0">
<textarea id="xml-viewer" style="display:none;">@Model.TextContent</textarea>
<div id="xml-rendered"></div>
</div>
</div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/monokai.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/xml/xml.min.js"></script>
<script>
var editor = CodeMirror(document.getElementById('xml-rendered'), {
value: document.getElementById('xml-viewer').value,
mode: 'xml',
lineNumbers: true,
readOnly: true,
theme: 'monokai',
lineWrapping: true
});
</script>
break;
case AttachmentViewerType.Text:
<div class="card">
<div class="card-header bg-secondary text-white">
<strong>Text-Inhalt</strong>
</div>
<div class="card-body">
<pre style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; max-height: 600px; overflow: auto;">@Model.TextContent</pre>
</div>
</div>
break;
case AttachmentViewerType.Image:
<div class="card">
<div class="card-body text-center">
<img src="?handler=Download&filePath=@Request.Query["filePath"]"
alt="@Model.FileName"
class="img-fluid"
style="max-height: 800px;">
</div>
</div>
break;
case AttachmentViewerType.Word:
<div class="alert alert-warning">
<strong>Word-Dokumente können nicht direkt angezeigt werden.</strong>
<p>Bitte laden Sie die Datei herunter oder implementieren Sie eine Konvertierung zu PDF.</p>
</div>
break;
default:
<div class="alert alert-warning">
<strong>Dieser Dateityp kann nicht angezeigt werden.</strong>
<p>Bitte laden Sie die Datei herunter.</p>
</div>
break;
}

View File

@@ -0,0 +1,49 @@
using DXApp.TemplateKitProject.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Text;
namespace DXApp.TemplateKitProject.Pages.Invoices;
public class ViewAttachmentModel(AttachmentViewerService viewerService) : PageModel
{
public string FileName { get; private set; } = string.Empty;
public AttachmentViewerType ViewerType { get; private set; }
public string? TextContent { get; private set; }
public IActionResult OnGet(string filePath)
{
if (string.IsNullOrEmpty(filePath) || !System.IO.File.Exists(filePath))
return NotFound();
FileName = Path.GetFileName(filePath);
ViewerType = viewerService.DetermineViewerType(FileName);
// Für Text/XML: Inhalt direkt als Content zurückgeben (für AJAX)
if (ViewerType == AttachmentViewerType.Xml || ViewerType == AttachmentViewerType.Text)
{
TextContent = System.IO.File.ReadAllText(filePath, Encoding.UTF8);
// Wenn Request von AJAX kommt (Accept: */* oder text/plain)
if (Request.Headers.Accept.ToString().Contains("*/*") ||
Request.Headers["X-Requested-With"] == "XMLHttpRequest")
{
return Content(TextContent, "text/plain", Encoding.UTF8);
}
}
return Page();
}
public IActionResult OnGetDownload(string filePath)
{
if (string.IsNullOrEmpty(filePath) || !System.IO.File.Exists(filePath))
return NotFound();
var fileName = Path.GetFileName(filePath);
var mimeType = viewerService.GetMimeType(fileName);
var bytes = System.IO.File.ReadAllBytes(filePath);
return File(bytes, mimeType, fileName);
}
}

View File

@@ -18,6 +18,7 @@ builder.Services.AddScoped<PdfResultPackageService>();
builder.Services.AddScoped<ZugferdExtractorService>(); builder.Services.AddScoped<ZugferdExtractorService>();
builder.Services.AddScoped<ZugferdParserService>(); builder.Services.AddScoped<ZugferdParserService>();
builder.Services.AddScoped<ZugferdImportService>(); builder.Services.AddScoped<ZugferdImportService>();
builder.Services.AddScoped<AttachmentViewerService>();
var app = builder.Build(); var app = builder.Build();

View File

@@ -0,0 +1,49 @@
using DXApp.TemplateKitProject.Models;
namespace DXApp.TemplateKitProject.Services;
public class AttachmentViewerService
{
public AttachmentViewerType DetermineViewerType(string fileName)
{
var extension = Path.GetExtension(fileName).ToLowerInvariant();
return extension switch
{
".pdf" => AttachmentViewerType.Pdf,
".xml" => AttachmentViewerType.Xml,
".txt" => AttachmentViewerType.Text,
".jpg" or ".jpeg" or ".png" or ".gif" => AttachmentViewerType.Image,
".docx" or ".doc" => AttachmentViewerType.Word,
_ => AttachmentViewerType.Download
};
}
public string GetMimeType(string fileName)
{
var extension = Path.GetExtension(fileName).ToLowerInvariant();
return extension switch
{
".pdf" => "application/pdf",
".xml" => "application/xml",
".txt" => "text/plain",
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
".gif" => "image/gif",
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".doc" => "application/msword",
_ => "application/octet-stream"
};
}
}
public enum AttachmentViewerType
{
Pdf, // PDF.js Viewer
Xml, // Syntax-highlighted XML
Text, // Plain Text
Image, // <img> Tag
Word, // Konvertierung zu PDF (optional)
Download // Nur Download-Button
}

View File

@@ -56,7 +56,7 @@ public class PdfResultPackageService(
converter.SaveDocument(convertedStream); converter.SaveDocument(convertedStream);
convertedStream.Position = 0; convertedStream.Position = 0;
// Schritt 3: Anhang einbetten // Schritt 3: Hauptdokument laden
using var processor = new PdfDocumentProcessor(); using var processor = new PdfDocumentProcessor();
processor.LoadDocument(convertedStream); processor.LoadDocument(convertedStream);
@@ -68,15 +68,23 @@ public class PdfResultPackageService(
processor.Document.Subject = string.Empty; processor.Document.Subject = string.Empty;
processor.Document.Keywords = string.Empty; processor.Document.Keywords = string.Empty;
processor.AttachFile(new PdfFileAttachment // Schritt 3a: Berichts-Seiten ans Ende anhängen
int reportPageCount;
using (var reportStream = new FileStream(reportPath, FileMode.Open, FileAccess.Read))
{ {
FileName = Path.GetFileName(reportPath), // Temporäres Dokument öffnen um Seitenanzahl zu ermitteln
Description = "Ergebnisbericht", using var tempProcessor = new PdfDocumentProcessor();
MimeType = "application/pdf", tempProcessor.LoadDocument(reportStream);
Relationship = PdfAssociatedFileRelationship.Supplement, reportPageCount = tempProcessor.Document.Pages.Count;
CreationDate = DateTime.Now,
Data = File.ReadAllBytes(reportPath) logger.LogDebug(
}); "Hänge {PageCount} Seite(n) aus Bericht an das Dokument an.",
reportPageCount);
// Stream zurücksetzen und ans Hauptdokument anhängen
reportStream.Position = 0;
processor.AppendDocument(reportStream);
}
// Schritt 4: Stempel auf Seite 1 zeichnen // Schritt 4: Stempel auf Seite 1 zeichnen
var firstPage = processor.Document.Pages[0]; var firstPage = processor.Document.Pages[0];
@@ -126,10 +134,15 @@ public class PdfResultPackageService(
processor.Document.Pages[0], 96, 96); processor.Document.Pages[0], 96, 96);
} }
// Schritt 4: Speichern // Schritt 5: Speichern
try try
{ {
processor.SaveDocument(outputPath); processor.SaveDocument(outputPath);
logger.LogInformation(
"Result-PDF erstellt mit {TotalPages} Seiten (Original + {ReportPages} Berichtseiten).",
processor.Document.Pages.Count,
reportPageCount);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -19,6 +19,7 @@
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@@ -28,6 +29,7 @@
"resolved": "https://registry.npmjs.org/@devexpress/utils/-/utils-2.1.1.tgz", "resolved": "https://registry.npmjs.org/@devexpress/utils/-/utils-2.1.1.tgz",
"integrity": "sha512-hlemXR3L0yDPsMdhTQl9EtjiEYxIHklH7I4RKPkOhfrF5+jb4f3kkfvbKT7nA9aoFEHPPF0/hll43gOBpUpY0g==", "integrity": "sha512-hlemXR3L0yDPsMdhTQl9EtjiEYxIHklH7I4RKPkOhfrF5+jb4f3kkfvbKT7nA9aoFEHPPF0/hll43gOBpUpY0g==",
"license": "SEE LICENSE IN README.md", "license": "SEE LICENSE IN README.md",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "2.3.1" "tslib": "2.3.1"
} }
@@ -37,6 +39,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24" "@jridgewell/trace-mapping": "^0.3.24"
@@ -47,6 +50,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24" "@jridgewell/trace-mapping": "^0.3.24"
@@ -57,6 +61,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
} }
@@ -65,13 +70,15 @@
"version": "1.5.5", "version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31", "version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
@@ -82,6 +89,7 @@
"resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.14.2.tgz", "resolved": "https://registry.npmjs.org/@preact/signals-core/-/signals-core-1.14.2.tgz",
"integrity": "sha512-RZHdBj9ZF4n40Rp4jS052EHHjBWf96P9oNdXPfhQTovCuWY9iQn3Gq+gOTJSgBO9A/JBuPfMOWsSX/lIU9Pc/A==", "integrity": "sha512-RZHdBj9ZF4n40Rp4jS052EHHjBWf96P9oNdXPfhQTovCuWY9iQn3Gq+gOTJSgBO9A/JBuPfMOWsSX/lIU9Pc/A==",
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/preact" "url": "https://opencollective.com/preact"
@@ -93,6 +101,7 @@
"integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/core-js" "url": "https://opencollective.com/core-js"
@@ -102,19 +111,22 @@
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/devexpress-diagram": { "node_modules/devexpress-diagram": {
"version": "2.2.29", "version": "2.2.29",
"resolved": "https://registry.npmjs.org/devexpress-diagram/-/devexpress-diagram-2.2.29.tgz", "resolved": "https://registry.npmjs.org/devexpress-diagram/-/devexpress-diagram-2.2.29.tgz",
"integrity": "sha512-6QS1zKP736QlIU6lMeZdq52Fe85BSa6vh1TwDIxSAKNwT6gHnGKm5paI71epRJ9H6GC4SLtv/zhlH8QCjwNnXQ==", "integrity": "sha512-6QS1zKP736QlIU6lMeZdq52Fe85BSa6vh1TwDIxSAKNwT6gHnGKm5paI71epRJ9H6GC4SLtv/zhlH8QCjwNnXQ==",
"license": "SEE LICENSE IN README.md", "license": "SEE LICENSE IN README.md",
"peer": true,
"dependencies": { "dependencies": {
"@devexpress/utils": "^2.1.1", "@devexpress/utils": "^2.1.1",
"es6-object-assign": "^1.1.0" "es6-object-assign": "^1.1.0"
@@ -125,6 +137,7 @@
"resolved": "https://registry.npmjs.org/devexpress-gantt/-/devexpress-gantt-4.1.68.tgz", "resolved": "https://registry.npmjs.org/devexpress-gantt/-/devexpress-gantt-4.1.68.tgz",
"integrity": "sha512-pilTDWwCv1EthcCV9uFj5krbSkA3MdjiKCgJdCmGaTA+lDNiWP5Xc2JzNK1sOOTMUmzN+3h7kAtcLxzTfjWTRg==", "integrity": "sha512-pilTDWwCv1EthcCV9uFj5krbSkA3MdjiKCgJdCmGaTA+lDNiWP5Xc2JzNK1sOOTMUmzN+3h7kAtcLxzTfjWTRg==",
"license": "SEE LICENSE IN README.md", "license": "SEE LICENSE IN README.md",
"peer": true,
"dependencies": { "dependencies": {
"@devexpress/utils": "^2.1.1", "@devexpress/utils": "^2.1.1",
"tslib": "2.3.1" "tslib": "2.3.1"
@@ -175,6 +188,7 @@
"resolved": "https://registry.npmjs.org/devextreme-quill/-/devextreme-quill-1.7.9.tgz", "resolved": "https://registry.npmjs.org/devextreme-quill/-/devextreme-quill-1.7.9.tgz",
"integrity": "sha512-UEkR16+I/7P/4+7dUmc65lv+VRdRGk0kFvZXgHnM2UFcEBKqm1kVDdVPBolGRFwuOFuDttJxp2SXUAyiuSC8wA==", "integrity": "sha512-UEkR16+I/7P/4+7dUmc65lv+VRdRGk0kFvZXgHnM2UFcEBKqm1kVDdVPBolGRFwuOFuDttJxp2SXUAyiuSC8wA==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"peer": true,
"dependencies": { "dependencies": {
"core-js": "^3.34.0", "core-js": "^3.34.0",
"eventemitter3": "^4.0.7", "eventemitter3": "^4.0.7",
@@ -188,25 +202,29 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz",
"integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==", "integrity": "sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/eventemitter3": { "node_modules/eventemitter3": {
"version": "4.0.7", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/fast-diff": { "node_modules/fast-diff": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"license": "Apache-2.0" "license": "Apache-2.0",
"peer": true
}, },
"node_modules/immediate": { "node_modules/immediate": {
"version": "3.0.6", "version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/inferno": { "node_modules/inferno": {
"version": "8.2.3", "version": "8.2.3",
@@ -214,6 +232,7 @@
"integrity": "sha512-LMeRlCe+RlXw8kHCLyOWRk2PsZ3Fo4jkESyAR1g4FfPT48N78i11YhTVXW2ukCx5MFjv+qrfa73JzJWU9sg4CQ==", "integrity": "sha512-LMeRlCe+RlXw8kHCLyOWRk2PsZ3Fo4jkESyAR1g4FfPT48N78i11YhTVXW2ukCx5MFjv+qrfa73JzJWU9sg4CQ==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"csstype": "^3.1.2", "csstype": "^3.1.2",
"inferno-vnode-flags": "8.2.3", "inferno-vnode-flags": "8.2.3",
@@ -229,6 +248,7 @@
"resolved": "https://registry.npmjs.org/inferno-create-element/-/inferno-create-element-8.2.3.tgz", "resolved": "https://registry.npmjs.org/inferno-create-element/-/inferno-create-element-8.2.3.tgz",
"integrity": "sha512-YEwX4OiFlgeTutvE16uCGxkaSVwZ1DklpAPX8okjVsGaLIWQrM8QIQFxn3mTLWSu70Uea+afQfKL5wE4hxn39Q==", "integrity": "sha512-YEwX4OiFlgeTutvE16uCGxkaSVwZ1DklpAPX8okjVsGaLIWQrM8QIQFxn3mTLWSu70Uea+afQfKL5wE4hxn39Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"inferno": "8.2.3" "inferno": "8.2.3"
} }
@@ -238,6 +258,7 @@
"resolved": "https://registry.npmjs.org/inferno-hydrate/-/inferno-hydrate-8.2.3.tgz", "resolved": "https://registry.npmjs.org/inferno-hydrate/-/inferno-hydrate-8.2.3.tgz",
"integrity": "sha512-AyCiswnjYg7D9veJdjiQg06Npp0/iXKhwOm2hjoY3cjadT3fIdz2XtDElLB7imU4icuJ3xOmXA8FgIfnSJfHrQ==", "integrity": "sha512-AyCiswnjYg7D9veJdjiQg06Npp0/iXKhwOm2hjoY3cjadT3fIdz2XtDElLB7imU4icuJ3xOmXA8FgIfnSJfHrQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"inferno": "8.2.3" "inferno": "8.2.3"
} }
@@ -246,19 +267,22 @@
"version": "8.2.3", "version": "8.2.3",
"resolved": "https://registry.npmjs.org/inferno-vnode-flags/-/inferno-vnode-flags-8.2.3.tgz", "resolved": "https://registry.npmjs.org/inferno-vnode-flags/-/inferno-vnode-flags-8.2.3.tgz",
"integrity": "sha512-dfC0MIwFv9PCbZCUsuk9ISejFS3fKJODC0rZ/LjxxzE+OrCk+PMwPLsUnGU6O9/jbBnPACVz1BkACDf5LWgU5Q==", "integrity": "sha512-dfC0MIwFv9PCbZCUsuk9ISejFS3fKJODC0rZ/LjxxzE+OrCk+PMwPLsUnGU6O9/jbBnPACVz1BkACDf5LWgU5Q==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/inherits": { "node_modules/inherits": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC" "license": "ISC",
"peer": true
}, },
"node_modules/isarray": { "node_modules/isarray": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/jquery": { "node_modules/jquery": {
"version": "3.7.1", "version": "3.7.1",
@@ -271,6 +295,7 @@
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"license": "(MIT OR GPL-3.0-or-later)", "license": "(MIT OR GPL-3.0-or-later)",
"peer": true,
"dependencies": { "dependencies": {
"lie": "~3.3.0", "lie": "~3.3.0",
"pako": "~1.0.2", "pako": "~1.0.2",
@@ -283,6 +308,7 @@
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"immediate": "~3.0.5" "immediate": "~3.0.5"
} }
@@ -291,26 +317,30 @@
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/lodash.isequal": { "node_modules/lodash.isequal": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/lodash.merge": { "node_modules/lodash.merge": {
"version": "4.6.2", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/opencollective-postinstall": { "node_modules/opencollective-postinstall": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
"integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"opencollective-postinstall": "index.js" "opencollective-postinstall": "index.js"
} }
@@ -319,19 +349,22 @@
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)" "license": "(MIT AND Zlib)",
"peer": true
}, },
"node_modules/parchment": { "node_modules/parchment": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-2.0.1.tgz", "resolved": "https://registry.npmjs.org/parchment/-/parchment-2.0.1.tgz",
"integrity": "sha512-VBKrlEoZCBD+iwoeag0QTtY1Cti+Ma4nLpVYcc/uus/wHhMsPOi5InH3RL1s4aekahPZpabcS2ToKyGf7RMH/g==", "integrity": "sha512-VBKrlEoZCBD+iwoeag0QTtY1Cti+Ma4nLpVYcc/uus/wHhMsPOi5InH3RL1s4aekahPZpabcS2ToKyGf7RMH/g==",
"license": "BSD-3-Clause" "license": "BSD-3-Clause",
"peer": true
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "4.0.4", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -343,13 +376,15 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/quill-delta": { "node_modules/quill-delta": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz", "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
"integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==", "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"fast-diff": "^1.3.0", "fast-diff": "^1.3.0",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
@@ -364,6 +399,7 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"core-util-is": "~1.0.0", "core-util-is": "~1.0.0",
"inherits": "~2.0.3", "inherits": "~2.0.3",
@@ -379,6 +415,7 @@
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.8.1.tgz", "resolved": "https://registry.npmjs.org/rrule/-/rrule-2.8.1.tgz",
"integrity": "sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==", "integrity": "sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
@@ -387,25 +424,29 @@
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD" "license": "0BSD",
"peer": true
}, },
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/setimmediate": { "node_modules/setimmediate": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/string_decoder": { "node_modules/string_decoder": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"safe-buffer": "~5.1.0" "safe-buffer": "~5.1.0"
} }
@@ -414,13 +455,15 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==",
"license": "0BSD" "license": "0BSD",
"peer": true
}, },
"node_modules/unplugin": { "node_modules/unplugin": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz",
"integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@jridgewell/remapping": "^2.3.5", "@jridgewell/remapping": "^2.3.5",
"picomatch": "^4.0.3", "picomatch": "^4.0.3",
@@ -434,13 +477,15 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/webpack-virtual-modules": { "node_modules/webpack-virtual-modules": {
"version": "0.6.2", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
"license": "MIT" "license": "MIT",
"peer": true
} }
} }
} }