173 Commits

Author SHA1 Message Date
fierke b3ab21ae38 [fix?:] windows paths (ai-based since windows is boring) 2026-05-18 11:42:32 +02:00
fierke 67d007bb31 [fix:] inconsistency with config.json 2026-05-18 11:37:10 +02:00
fierke bf28ba1914 [chore:] logging for StableFontResolver.cs 2026-05-16 18:19:50 +02:00
fierke 40630dee35 [chore:] logging for PdfBuilder.cs 2026-05-16 18:19:36 +02:00
fierke c6baa6a187 [chore:] logging for MarkdownRenderer.cs 2026-05-16 18:19:27 +02:00
fierke 557052bbc7 [chore:] logging for EditorWindow.axaml.cs 2026-05-16 18:19:19 +02:00
fierke 696d0f8fcb [chore:] logging for DataImport.cs 2026-05-16 18:19:09 +02:00
fierke 201b19cbb5 [chore:] logging for CsvBuilder.cs 2026-05-16 18:18:58 +02:00
fierke d7d4b3b31b [file:] removing docs.tex since we're using a markdown wiki 2026-05-16 16:14:31 +00:00
fierke fa10b5c7d0 [chore:] logging for ResultWindow.axaml.cs 2026-05-16 15:10:55 +02:00
fierke 85685d95bb [chore:] logging for NamingWindow.axaml.cs 2026-05-16 15:10:49 +02:00
fierke 8a42f8dc7d [chore:] logging for CombineAddresses.cs 2026-05-16 15:09:55 +02:00
fierke cdc4fb70cd [chore:] logging for AddressShortener.cs 2026-05-16 15:09:50 +02:00
fierke 10dfd1e080 [chore:] logging for AddressRepair.cs 2026-05-16 15:09:45 +02:00
fierke 4b98f53881 [chore:] logging for AddressPatch.cs 2026-05-16 15:09:38 +02:00
fierke 5f79df55d2 [chore:] logging for AddressCreation.cs 2026-05-16 15:09:32 +02:00
fierke 2d33326bab [chore:] logging for AddressCheck.cs 2026-05-16 15:09:23 +02:00
fierke b48652910e [fix:] format errors 2026-05-16 14:44:38 +02:00
fierke a74a97559b [fix:] typo 2026-05-16 14:42:28 +02:00
fierke b5b6dc8de7 [chore:] basic saving logging 2026-05-16 14:41:28 +02:00
fierke 6dfab5e73a [chore:] basic logging 2026-05-16 14:41:16 +02:00
fierke 6f03a26c29 [fix:] console logging printed an array 👀 2026-05-16 14:41:06 +02:00
fierke 7c81920e84 [init:] logging system 2026-05-16 14:12:16 +02:00
fierke d337f94851 [fix?:] trying 2026-05-15 18:56:55 +02:00
fierke 1922b30ada [fix?:] will this work? 2026-05-15 18:41:45 +02:00
fierke 68541621d9 [fix?:] windows file path for label export? 2026-05-15 18:34:25 +02:00
fierke 474b628f0b [chore:] renaming address sets now gives the current name as a base 2026-05-15 17:51:35 +02:00
fierke 7fe45ce4e3 [feat:] csv-divider-buttons 2026-05-06 07:07:41 +02:00
fierke a46539c3cc [fix:] wrong sorting of imported address sets 2026-05-06 06:35:23 +02:00
fierke 72a5630db1 [chore:] enable shortener usage 2026-05-06 06:34:48 +02:00
fierke 7697edac5e [fix:] shorten-button was enabled too early 2026-05-06 06:33:55 +02:00
fierke 028b9793db [chore:] basic address shortener 2026-05-06 06:33:37 +02:00
fierke 2c5f2ed48b [feat:] (opt) delete original sets after merging 2026-05-03 09:21:57 +02:00
fierke f0598a39ec [chore:] listbox item type is no string anymore (hopefully I tested everything?) 2026-05-02 16:27:43 +02:00
fierke f9e419d573 [chore:] cleanup 2026-05-02 16:20:18 +02:00
fierke 56233e6f5c [chore:] tiny improvements for address check 2026-05-02 15:39:42 +02:00
fierke 488830cdad [fix:] multiple little fixed for the combining methods 2026-05-02 12:56:27 +02:00
fierke 52fbefb803 [chore:] added missing help- and about-links 2026-04-29 14:58:53 +02:00
fierke c5aabc2a02 [fix:] window focus 2026-04-28 11:00:51 +02:00
fierke 4a9f9a1ff0 [fix:] startup location 2026-04-28 11:00:40 +02:00
fierke fab14eb107 [fix:] naming window for rest-set not visible if not necessary 2026-04-28 11:00:26 +02:00
fierke 2dcc1bd657 Merge remote-tracking branch 'origin/main' 2026-04-28 10:54:09 +02:00
fierke 3767fece48 [fix:] make customer delete button working 2026-04-28 10:53:53 +02:00
fierke 842608e96f Merge remote-tracking branch 'origin/main'
# Conflicts:
#	MainWindow.axaml
#	MainWindow.axaml.cs
2026-04-27 17:39:19 +02:00
fierke d9ee3e2fc9 [chore:] deleting address sets 2026-04-27 17:39:08 +02:00
fierke 80d1498cc7 [chore:] if it happens it happens 2026-04-27 13:07:22 +02:00
fierke de2f453553 [chore:] editor window margins 2026-04-27 13:07:01 +02:00
fierke 7e168c4d0f [fix:] line breaks in editor window now possible 2026-04-27 13:06:29 +02:00
fierke 9becedbd97 [chore:] better file- and folder creating 2026-04-27 09:04:16 +02:00
fierke 16982c3d95 [fix:] adding ".md" if not added by user (wiki) 2026-04-27 08:49:53 +02:00
fierke 93771dd110 [fix:] trying to fix one-item-too-much-issue 2026-04-27 08:49:27 +02:00
fierke 3e14731429 Merge pull request 'export-margin-options implementation' (#33) from export-margin-options into main
Reviewed-on: #33
2026-04-23 14:22:58 +00:00
fierke df6c187a00 [chore:] combining export margin options gui with settings and PdfBuilder.cs 2026-04-23 12:30:35 +02:00
fierke 98b5198f6f [chore:] added smallFontSize and fixed usage of fontSize and smallFontSize 2026-04-23 09:03:25 +02:00
fierke 1a6459d60b [chore:] grid visibility (calc man :D) 2026-04-23 09:03:00 +02:00
fierke 43e6c35beb [gui:] export margin options gui 2026-04-23 09:02:45 +02:00
fierke 9777c6b5a2 [fix:] naming window showed nothing 2026-04-22 08:09:28 +02:00
fierke 9ad378c800 [fix:] temporarily fiex application crash if no customer is selected when start combining (more than union, which was fixed before) 2026-04-16 13:29:59 +02:00
fierke 5ccd4a4e99 [fix:] csv-import now recognizes quotation marks 2026-04-16 12:49:45 +02:00
fierke ac7b23cc28 [fix:] temporarily fixed application crash if no customer is seleceted when starting 2026-04-16 12:49:10 +02:00
fierke 16ed64dbf4 [chore:] there was a space missing that triggered my monk 2026-04-16 11:44:10 +02:00
fierke cc48c0ae2c [chore:] separator option (it's better like that :D) 2026-03-19 14:39:54 +01:00
fierke ba640c602a [file:] better folder structuring 2026-03-19 14:34:36 +01:00
fierke e8738a2eab [fix:] now, they get unique id's when importing 2026-03-19 14:20:59 +01:00
Elias Fierke afbb4626a0 [feat:] implemented renaming functionality 2026-01-19 16:25:33 +01:00
Elias Fierke ee83aca490 [feat:] included context menu for Address-SetListBox 2026-01-19 16:25:20 +01:00
Elias Fierke b40ddfbf2e [fix:] NamingWindow.axaml had a wrong size 2026-01-19 16:17:06 +01:00
Elias Fierke 5172de332c [feat:] added "new file" button for wiki and therefore
implemented the usage of NamingWindow
2026-01-18 16:15:25 +01:00
Elias Fierke d478fd5129 [feat:] simple MessageBox-lik naming window 2026-01-18 16:14:37 +01:00
Elias Fierke 5dcb44aa2a [fix:] added default config- and wiki-paths 2026-01-18 16:13:40 +01:00
Elias Fierke ea31637bdb [feat:] implemented a basic editor for the wiki 2026-01-18 14:36:52 +01:00
Elias Fierke 2c909820d3 Merge remote-tracking branch 'origin/main' 2026-01-18 14:36:07 +01:00
Elias Fierke 96eb122ff8 [fix:] fixed an issue where new lines were rendered as "MarkDig.Blah.Sülz.LineBreakInline" 2026-01-18 14:35:59 +01:00
fierke fed817a6dc [fix:] corrected account information 2026-01-18 13:03:42 +00:00
fierke d5073465b2 [file:] added README 2026-01-18 11:39:49 +00:00
Elias Fierke 10b0eb5bcd [chore:] font resolver included + various changes 2026-01-18 11:38:16 +01:00
Elias Fierke 013bd4a070 [file:] added font-asset with example font 2026-01-18 11:36:41 +01:00
Elias Fierke 2c4eb1fcef [proj:] including assets/fonts to build directory 2026-01-18 11:35:15 +01:00
Elias Fierke 0395537a55 [init:] added StableFontResolver.cs 2026-01-18 09:20:13 +01:00
Elias Fierke 313cd58fc7 [feat:] filter-button to prevent live reloading 2026-01-15 11:35:08 +01:00
Elias Fierke 723722ba47 [chore:] introducing KasPerson.id (refsid remains but isn't used in the mgmt-backend anymore) 2026-01-15 11:21:08 +01:00
Elias Fierke 58964896ad [fix:] fixed creation of empty lines 2026-01-15 10:12:40 +01:00
Elias Fierke af1c3ff8cc [fix:] single address lines were too long 2026-01-14 11:47:54 +01:00
Elias Fierke 48852e4505 [chore:] removed unused methods 2026-01-14 11:35:37 +01:00
Elias Fierke 60cde86efe [fix:] one-line addresses were visible in pdf creation 2026-01-14 11:29:21 +01:00
Elias Fierke 67cfe10f5f [chore:] removed unused methods 2026-01-14 11:24:18 +01:00
Elias Fierke e186070f05 [chore:] ui consistency improvements 2026-01-13 21:05:41 +01:00
Elias Fierke d36314b724 [chore:] text change in EditorWindow.axaml 2026-01-13 20:48:27 +01:00
Elias Fierke beed5decbf [fix:] wrong ui implementation of the wiki-path-elements 2026-01-13 20:47:57 +01:00
Elias Fierke 71859fa978 [chore:] code cleanup by IDE 2026-01-13 20:47:19 +01:00
Elias Fierke f05470249a [chore:] code cleanup 2026-01-13 20:46:22 +01:00
Elias Fierke f4918aa8ee [chore:] removed ArticleStore (wanted to do it another way) 2026-01-13 20:45:49 +01:00
Elias Fierke 4e1f08883a [chore:] removed NamePromtWindow which was useless 2026-01-13 20:45:23 +01:00
fierke 99b35c0aaf [feat:] forgot to git-add the wiki source files... here they are 2026-01-13 18:33:35 +01:00
fierke aeb4092f28 [feat:] initial wiki service implementation (basic, no editing, wrong directory- and saving-logic) 2026-01-13 18:31:41 +01:00
fierke c760ef0936 [chore:] added refsid to pdf-creation 2026-01-13 18:30:58 +01:00
fierke e750b4c757 [chore:] initial article store implementation for Settings.cs 2026-01-13 18:30:12 +01:00
Elias Fierke 3877d91af4 [chore:] usage of Country (and possible import of ISO3166-Countries) 2026-01-02 18:02:08 +01:00
Elias Fierke 7af6444da2 [file:] imported ISO3166-dll from nuget 2026-01-02 16:50:15 +01:00
Elias Fierke 4cfbcd0ab4 [feat:] added Country implementation for country code editing (unused) 2026-01-01 14:45:57 +01:00
Elias Fierke b82473ada2 [chore:] PdfBuilder.cs skips empty addresses 2025-12-21 11:43:41 +01:00
Elias Fierke 1cba67253a [chore:] selectable pdf file path for label creation 2025-12-21 11:32:38 +01:00
Elias Fierke 63c1559110 [chore:] added "Postfach" to address creation 2025-12-21 11:25:48 +01:00
Elias Fierke b670ba11fa [chore:] sender address usage in PdfBuilder.cs 2025-12-15 10:17:10 +01:00
Elias Fierke 1ad57543d1 [chore:] sender address editing 2025-12-15 10:04:33 +01:00
Elias Fierke 6cd4ea2df6 [chore:] added sender address field 2025-12-15 10:01:07 +01:00
Elias Fierke 30e42afe35 [chore:] rearrangend some result window features for debugging purposes (unfinal) 2025-12-14 14:33:55 +01:00
Elias Fierke 70e127b2f0 [init:] Initialized PDF Creation 2025-12-14 14:33:25 +01:00
Elias Fierke 7c73170b46 [chore:] implemented label generation button handler 2025-12-14 14:33:07 +01:00
Elias Fierke 290f69e976 [chore:] the IDE wanted to add some spaces 2025-12-14 14:32:45 +01:00
Elias Fierke 6ce08d7d4a [gui:] added a few tabs that will be used later 2025-12-14 14:32:02 +01:00
Elias Fierke 86df6f6a63 [file:] imported PdfSharp 2025-12-14 14:31:33 +01:00
Elias Fierke eae0568ae0 [feat:] added KasPersonError.GetString() 2025-12-14 14:31:20 +01:00
Elias Fierke 4ebd6bc407 [fix:] fixed some logical errors in address creation 2025-12-14 14:30:53 +01:00
Elias Fierke 2c22306fef [fix:] plz check was bad 2025-12-14 14:30:33 +01:00
Elias Fierke 174223ba9e [fix:] Address Creation used an empty address instead of the one from the address set 2025-12-14 14:30:07 +01:00
Elias Fierke 8e5709c215 [feat:] initial fully implemented address creation 2025-12-07 13:30:52 +01:00
Elias Fierke b70bd5e324 [struc:] moved KasPersonError-Instance from addressset to KasPerson-Instance 2025-12-07 13:30:25 +01:00
fierke 8c56717b9c [fix:] plz's and pplz's CAN be alphanumeric 2025-11-27 14:48:24 +01:00
fierke 7cd02456bc [init:] AddressCreator with first string address alternative 2025-11-27 14:47:48 +01:00
fierke 88bbc9644c [chore:] applied improved set naming 2025-11-17 13:28:40 +01:00
fierke 743c3aacd1 [chore:] added basic name-generation-function for address-sets 2025-11-17 13:14:26 +01:00
fierke ac8362d9ba [feat:] added unprocessed-item-to-new-set-Option to address-combining 2025-11-17 13:13:30 +01:00
fierke 0c11cb2819 [chore:] added "-new"-part to names of AddressSets if the name already exists (will be improved further) 2025-11-11 11:16:06 +01:00
fierke f057226a5f [fix:] bad refsid creation on address-patch-import 2025-11-11 10:50:36 +01:00
Elias Fierke a70948599f [fix:] fixed new customer issue (new customer creation was terrible) 2025-11-04 14:43:11 +01:00
Elias Fierke fb671e3526 [docs:] removed unrelated comment from another document which provided the template 2025-11-04 14:20:47 +01:00
Elias Fierke a2af60a808 [chore:] implemented symmetric difference (combiner) and its usage 2025-10-23 20:49:19 +02:00
Elias Fierke 34c4dd8df1 [init:] initialized logof client documentation (tex) 2025-10-23 16:49:54 +02:00
Elias Fierke bed0ebaf16 [chore:] implemented intersection (combiner) and its usage 2025-10-23 16:43:23 +02:00
Elias Fierke d0ecfda404 [chore:] implemented union (combiner) and its usage 2025-10-23 13:54:48 +02:00
Elias Fierke f28ac1a4b9 [chore:] implemented difference (combiner) and its usage 2025-10-23 13:32:26 +02:00
Elias Fierke 09bbdfdfde [feat:] added address-getter 2025-10-23 13:31:31 +02:00
Elias Fierke 6efafb7cc2 [init:] initialized new combine-functions 2025-10-23 10:40:25 +02:00
Elias Fierke f93d9aada8 [fix:] refsid creation for non-refsid address-sets 2025-10-23 10:40:01 +02:00
Elias Fierke f27adffe41 [chore:] added loading.mp4 to project resources 2025-10-23 10:39:27 +02:00
Elias Fierke 48b0500bfe [chore:] initial splash preparation, which will not be used for now 2025-10-23 10:39:04 +02:00
Elias Fierke c6da9ac416 [file:] some ui layout for a not-shown window yeah 2025-10-23 10:38:32 +02:00
Elias Fierke d02d5199c3 [file:] added new window for splash screen 2025-10-09 11:22:17 +02:00
Elias Fierke b4430ae424 [file:] added loading animation 2025-10-09 11:19:15 +02:00
Elias Fierke 52c028cc8a [chore:] added link to git 2025-10-09 10:26:28 +02:00
Elias Fierke af8a74c2c3 [chore:] added ID to addres-sets (yes, there was no -_-) 2025-10-09 10:22:22 +02:00
Elias Fierke 63d430dd72 [chore:] combine split into four possible variants of merging 2025-10-09 10:21:42 +02:00
Elias Fierke c4663e489e [fix:] changed Shwo to Show 👍 2025-10-09 10:19:27 +02:00
Elias Fierke f3ec633c53 [file:] deleted /obj 2025-10-09 08:58:39 +02:00
Elias Fierke 6d3ea7fd2d [fix:] added /obj/* to .gitignore 2025-10-09 08:53:58 +02:00
Elias Fierke fc69c8cd6d [fix:] added /obj/ to .gitignore 2025-10-09 08:53:18 +02:00
Elias Fierke 69e9b84812 [fix:] forgot to use the separator in last commit 2025-10-09 08:51:25 +02:00
Elias Fierke c7740a48dc stuff 2025-10-09 08:40:24 +02:00
Elias Fierke 3cf1fb3012 [chore:] remove unused code for now 2025-10-09 08:40:09 +02:00
Elias Fierke 316c9511d3 [chore:] added CSV-Separator Settings.cs 2025-10-09 08:39:49 +02:00
Elias Fierke 4ee852b37c [chore:] icon path changed 2025-10-09 08:39:14 +02:00
Elias Fierke cd78eea228 [file:] moved icons 2025-10-09 08:37:52 +02:00
Elias Fierke df7b653218 [file:] imported calc man 2025-10-09 08:37:42 +02:00
Elias Fierke 842cb3f0f7 [file:] imported icon 2025-10-07 18:33:04 +02:00
Elias Fierke 1c54fa8937 [chore:] minor consistency improvements to AddressCheck.cs 2025-10-07 13:04:32 +02:00
Elias Fierke f33c69afcb [fix:] integrated refsid to data import 2025-10-06 18:16:51 +02:00
Elias Fierke 69c4957b62 stuff 2025-10-04 14:29:11 +02:00
Elias Fierke 8af9856afa [fix:] Import with AddressPatch hasn't been called 2025-10-04 14:29:02 +02:00
Elias Fierke 47942ddd78 [fix:] import using AddressPatch now working 2025-10-04 14:28:23 +02:00
Elias Fierke 081a705d2d [file:] oh, i removed a line break. nice. 2025-10-04 09:48:53 +02:00
Elias Fierke d77b89a9f2 [file:] better kas address list name 2025-10-03 16:39:18 +02:00
Elias Fierke d131b90103 [file:] added Netwonsonft.Json package 2025-10-03 16:39:02 +02:00
Elias Fierke badeafe042 [chore:] kas address list improvement (more information) 2025-10-03 16:38:17 +02:00
Elias Fierke 138ab7e6c7 [chore:] force light theme due to unreadability issues (temp) 2025-10-03 16:37:47 +02:00
Elias Fierke cdb669d261 [chore:] address list name got prettier 2025-10-03 16:37:17 +02:00
Elias Fierke bb65df9ea2 [chore:] added address patch 2025-10-03 16:36:22 +02:00
Elias Fierke afc3a78358 [chore:] improved gui 2025-09-21 18:09:21 +02:00
fierke 6b2eeda29a Update .idea/.idea.Logof Client.dir/.idea/.gitignore 2025-09-21 15:03:51 +00:00
Elias Fierke 55b091b2cc [chore:] kram 2025-09-21 16:58:32 +02:00
Elias Fierke 615ef22a55 [chore:] moved files for better structure 2025-09-21 16:10:21 +02:00
Elias Fierke a32c96a325 [chore:] massive ui changes 2025-09-21 16:10:08 +02:00
fierke f6952288d7 [feat:] added TabControl for Multi-Feature App-Design 2025-09-18 15:56:35 +02:00
63 changed files with 5261 additions and 2865 deletions
+1
View File
@@ -0,0 +1 @@
/obj/*
+2
View File
@@ -11,3 +11,5 @@
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
/obj
/obj/*
-235
View File
@@ -1,235 +0,0 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Threading;
namespace Logof_Client;
public class AddressCheck
{
public enum ErrorTypes
{
PlzTooShort,
PlzTooLong,
PPlzTooShort,
PPlzTooLong,
// empty,
FullAddressTooLong,
DoubledRefsid,
MayBeSameAddress,
NoPLZorPPLZ
}
public enum WarningTypes
{
NoCity,
NoStreet,
NoLastName,
NoFirstName,
NoStreetNumber,
NoPLZ,
NoPPLZ
}
private readonly ProgressWindow _progress;
public AddressCheck(ProgressWindow progressWindow)
{
_progress = progressWindow;
}
public async Task<List<(int, List<ErrorTypes>, List<WarningTypes>)>> Perform(KasAddressList addresses)
{
var failed_refsids = new List<(int, List<ErrorTypes>, List<WarningTypes>)>();
var total = addresses.KasPersons.Count;
var current = 0;
await Task.Run(async () =>
{
foreach (var person in addresses.KasPersons)
{
var errors = new List<ErrorTypes>();
var warnings = new List<WarningTypes>();
var hasFaults = false;
var address_component_count = 2; // cause anrede and name are first
// PLZ-Prüfung
if (person.plz == 0 || person.plz == null)
{
hasFaults = true;
warnings.Add(WarningTypes.NoPLZ);
}
else
{
if ((person.plz < 10000 && string.IsNullOrWhiteSpace(person.land)) ||
(person.plz < 10000 && person.land == "GER") ||
(person.plz < 10000 && person.land == "DE"))
{
hasFaults = true;
errors.Add(ErrorTypes.PlzTooShort);
}
else if ((person.plz > 99999 && string.IsNullOrWhiteSpace(person.land)) ||
(person.plz > 99999 && person.land == "GER") ||
(person.plz > 99999 && person.land == "DE"))
{
hasFaults = true;
errors.Add(ErrorTypes.PlzTooLong);
}
}
// PPLZ-Prüfung
if (person.pplz == 0 || person.pplz == null)
{
hasFaults = true;
warnings.Add(WarningTypes.NoPPLZ);
}
else
{
if ((person.pplz < 10000 && string.IsNullOrWhiteSpace(person.land)) ||
(person.pplz < 10000 && person.land == "GER") ||
(person.pplz < 10000 && person.land == "DE"))
{
hasFaults = true;
errors.Add(ErrorTypes.PPlzTooShort);
}
else if ((person.pplz > 99999 && string.IsNullOrWhiteSpace(person.land)) ||
(person.pplz > 99999 && person.land == "GER") ||
(person.pplz > 99999 && person.land == "DE"))
{
hasFaults = true;
errors.Add(ErrorTypes.PPlzTooLong);
}
}
if (warnings.Contains(WarningTypes.NoPLZ) && warnings.Contains(WarningTypes.NoPPLZ))
{
hasFaults = true;
errors.Add(ErrorTypes.NoPLZorPPLZ);
}
// Ort-Prüfung
if (string.IsNullOrWhiteSpace(person.ort))
{
hasFaults = true;
warnings.Add(WarningTypes.NoCity);
}
else
{
address_component_count++;
}
// Street-Number
var street = person.strasse.ToCharArray();
var intcount = 0;
foreach (var c in street)
{
int maybe;
if (int.TryParse(c.ToString(), out maybe)) intcount++;
}
if (intcount == 0)
{
hasFaults = true;
warnings.Add(WarningTypes.NoStreetNumber);
}
// Last-Name
if (string.IsNullOrWhiteSpace(person.name))
{
hasFaults = true;
warnings.Add(WarningTypes.NoLastName);
}
// First-Name
if (string.IsNullOrWhiteSpace(person.vorname))
{
hasFaults = true;
warnings.Add(WarningTypes.NoFirstName);
}
// Street-Check
if (string.IsNullOrWhiteSpace(person.strasse))
{
hasFaults = true;
warnings.Add(WarningTypes.NoStreet);
}
else
{
address_component_count++;
}
// Address-Component-Count
if (!string.IsNullOrWhiteSpace(person.strasse2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.land)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name1)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name3)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name4)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name5)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktion)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktion2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktionad)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.abteilung)) address_component_count++;
// Double-Refsid or DoubleAddresses
foreach (var person2 in addresses.KasPersons)
{
if (addresses.KasPersons.IndexOf(person) == addresses.KasPersons.IndexOf(person2)) continue;
if (person.refsid == person2.refsid)
{
hasFaults = true;
errors.Add(ErrorTypes.DoubledRefsid);
}
if (person.name == person2.name &&
person.strasse == person2.strasse &&
person.vorname == person2.vorname &&
person.ort == person2.ort &&
person.funktion == person2.funktion &&
person.funktion2 == person2.funktion2 &&
person.funktionad == person2.funktionad &&
person.abteilung == person2.abteilung &&
person.name1 == person2.name1 &&
person.name2 == person2.name2 &&
person.name3 == person2.name3 &&
person.name4 == person2.name4 &&
person.name5 == person2.name5)
{
hasFaults = true;
errors.Add(ErrorTypes.MayBeSameAddress);
}
}
// Adressen-Länge
if (address_component_count > 10)
{
hasFaults = true;
errors.Add(ErrorTypes.FullAddressTooLong);
}
if (hasFaults)
lock (failed_refsids)
{
failed_refsids.Add((person.refsid, errors, warnings));
}
// Fortschritt aktualisieren
Interlocked.Increment(ref current);
var percent = current / (double)total * 100;
await Dispatcher.UIThread.InvokeAsync(() =>
{
if (hasFaults)
_progress.AddToLog($"Person mit refsid {person.refsid} ist fehlerhaft");
_progress.ChangePercentage(percent);
});
}
});
return failed_refsids;
}
}
+3 -3
View File
@@ -1,11 +1,11 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Logof_Client.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
RequestedThemeVariant="Light">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
</Application.Styles>
</Application>
-94
View File
@@ -1,94 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Threading;
namespace Logof_Client;
public class CombineAddresses
{
private readonly ProgressWindow _progress;
public CombineAddresses(ProgressWindow progressWindow)
{
_progress = progressWindow;
}
public async Task<KasAddressList> Perform(List<KasAddressList> address_lists)
{
KasAddressList result = new();
await Task.Run(async () =>
{
for (var i = 0; i < address_lists.Count; i++)
if (i == 0)
lock (result)
{
result = address_lists[i];
}
else
lock (result)
{
result = Merge(result, address_lists[i], i + 1, address_lists.Count).Result;
}
});
return result;
}
private async Task<KasAddressList> Merge(KasAddressList first, KasAddressList second, int num, int total)
{
foreach (var sec in second.KasPersons)
{
var is_new = true;
foreach (var fi in first.KasPersons)
{
if (fi.refsid == sec.refsid)
{
is_new = false;
break;
}
if (fi.name == sec.name &&
fi.anrede == sec.anrede &&
fi.anredzus == sec.anredzus &&
fi.namezus == sec.namezus &&
fi.titel == sec.titel &&
fi.adel == sec.adel &&
fi.strasse == sec.strasse &&
fi.strasse2 == sec.strasse2 &&
fi.vorname == sec.vorname &&
fi.ort == sec.ort &&
fi.land == sec.land &&
fi.plz == sec.plz &&
fi.pplz == sec.pplz &&
fi.funktion == sec.funktion &&
fi.funktion2 == sec.funktion2 &&
fi.funktionad == sec.funktionad &&
fi.abteilung == sec.abteilung &&
fi.postfach == sec.postfach &&
fi.name1 == sec.name1 &&
fi.name2 == sec.name2 &&
fi.name3 == sec.name3 &&
fi.name4 == sec.name4 &&
fi.name5 == sec.name5)
{
is_new = false;
break;
}
}
if (is_new) first.KasPersons.Add(sec);
var subperc = second.KasPersons.IndexOf(sec) / second.KasPersons.Count;
var percent = (num + (double)subperc) / total * 100;
await Dispatcher.UIThread.InvokeAsync(() =>
{
if (is_new)
_progress.AddToLog($"Person mit refsid {sec.refsid} ergänzt");
else
_progress.AddToLog($"Person mit refsid {sec.refsid} bereits vorhanden");
_progress.ChangePercentage(percent);
});
}
return first;
}
}
-60
View File
@@ -1,60 +0,0 @@
using System.Collections.Generic;
using System.Text;
namespace Logof_Client;
public class CsvBuilder
{
private readonly string Header;
private readonly List<object> Instances;
private readonly KasAddressList KasAddressList;
public CsvBuilder(string header, List<object> instances)
{
Header = header;
Instances = instances;
}
public CsvBuilder(string header, KasAddressList instances)
{
Header = header;
KasAddressList = instances;
}
public string? BuildKas()
{
var result = new StringBuilder();
result.AppendLine(Header);
foreach (var l in KasAddressList.KasPersons)
result.AppendLine(
l.refsid + "," +
l.anrede + "," +
l.titel + "," +
l.vorname + "," +
l.adel + "," +
l.name + "," +
l.namezus + "," +
l.anredzus + "," +
l.strasse + "," +
l.strasse2 + "," +
l.plz + "," +
l.ort + "," +
l.land + "," +
l.pplz + "," +
l.postfach + "," +
l.name1 + "," +
l.name2 + "," +
l.name3 + "," +
l.name4 + "," +
l.name5 + "," +
l.funktion + "," +
l.funktion2 + "," +
l.abteilung + "," +
l.funktionad);
// weitere Cases
return result.ToString();
}
}
-84
View File
@@ -1,84 +0,0 @@
using System;
using System.IO;
namespace Logof_Client;
public class DataImport
{
public static (bool, KasAddressList) ImportKasAddressList(Uri pathToCsv, char separator = ',')
{
if (!File.Exists(pathToCsv.LocalPath))
{
Console.WriteLine($"File not found: {pathToCsv}");
return (false, null);
}
using var reader = new StreamReader(pathToCsv.LocalPath);
var headerLine = reader.ReadLine();
if (headerLine == null)
{
Console.WriteLine("File is empty.");
return (false, null);
}
var imported = new KasAddressList();
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line))
continue;
var parts = line.Split(separator);
if (parts.Length < 24)
{
Console.WriteLine($"No enough columns in line: {line}");
continue;
}
try
{
var person = new KasPerson(
ParseInt(parts[0]),
parts[1],
parts[2],
parts[3],
parts[4],
parts[5],
parts[6],
parts[7],
parts[8],
parts[9],
ParseInt(parts[10]),
parts[11],
parts[12],
ParseInt(parts[13]),
parts[14],
parts[15],
parts[16],
parts[17],
parts[18],
parts[19],
parts[20],
parts[21],
parts[22],
parts[23]
);
imported.KasPersons.Add(person);
}
catch (Exception ex)
{
Console.WriteLine($"Error while parsing line: {line} - {ex.Message}");
Console.WriteLine($"{ex.StackTrace}");
return (false, null);
}
}
return (true, imported);
}
private static int ParseInt(string input)
{
return int.TryParse(input, out var result) ? result : 0;
}
}
+237
View File
@@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Logof_Client;
public class KasAddressList //Address-Set
{
//public List<KasPersonError> errors = new();
public List<KasPerson> KasPersons;
public KasAddressList(string name)
{
KasPersons = new List<KasPerson>();
Name = name;
foreach (var set in Settings._instance.addressSets.addresses)
if (Name == set.Name)
Name = name + "-new";
var highest = 0;
foreach (var k in Settings._instance.addressSets.addresses)
if (highest <= k.ID)
highest = k.ID + 1;
ID = highest;
}
public string Name { get; set; } = "Neues Address-Set";
public int owner_id { get; set; }
public int ID { get; }
public void SetOwner(int owner_id)
{
this.owner_id = owner_id;
}
public static async Task<string> GenerateName(string basic_type, bool? is_rest = false)
{
string pre = "";
if (is_rest == true)
return basic_type + " - " + DateTime.Now.ToShortDateString() + " - Rest";
pre = basic_type + " - " + DateTime.Now.ToShortDateString();
var result = await NamingWindow.Show(MainWindow._instance, pre);
return string.IsNullOrWhiteSpace(result) ? pre : result;
}
// public void UpdateErrorList(List<KasPersonError> errorList)
// {
// //errors.Clear();
// // foreach (KasPersonError err in errorList)
// // {
// //
// // }
// //foreach (var error in errorList) errors.Add(new KasPersonError(error));
// }
public static int GetIDByAddressSetListItem(string listItemName)
{
var id = listItemName.Split(" - ")[0];
return int.Parse(id);
}
public override string ToString()
{
return Name + " (" + KasPersons.Count + " Einträge)";
}
}
public class KasPerson
{
public KasPersonError PersonError = null;
public KasPerson()
{
id = GenerateNewID(0);
refsid = 0;
anrede = "";
titel = "";
vorname = "";
adel = "";
name = "";
namezus = "";
anredzus = "";
strasse = "";
strasse2 = "";
plz = "";
ort = "";
land = "";
pplz = "";
postfach = "";
name1 = "";
name2 = "";
name3 = "";
name4 = "";
name5 = "";
funktion = "";
funktion2 = "";
abteilung = "";
funktionad = "";
}
public KasPerson(int id, int refsid,
string anrede,
string titel,
string vorname,
string adel,
string name,
string namezus,
string anredzus,
string strasse,
string strasse2,
string plz,
string ort,
string land,
string pplz,
string postfach,
string name1,
string name2,
string name3,
string name4,
string name5,
string funktion, // ignorieren
string funktion2, // ignorieren
string abteilung,
string funktionad)
{
this.id = id;
this.refsid = refsid;
this.anrede = anrede;
this.titel = titel;
this.vorname = vorname;
this.adel = adel;
this.name = name;
this.namezus = namezus;
this.anredzus = anredzus;
this.strasse = strasse;
this.strasse2 = strasse2;
this.plz = plz;
this.ort = ort;
this.land = land;
this.pplz = pplz;
this.postfach = postfach;
this.name1 = name1;
this.name2 = name2;
this.name3 = name3;
this.name4 = name4;
this.name5 = name5;
this.funktion = funktion;
this.funktion2 = funktion2;
this.abteilung = abteilung;
this.funktionad = funktionad;
}
public int id { get; set; }
public int refsid { get; set; }
public string anrede { get; set; }
public string titel { get; set; }
public string vorname { get; set; }
public string adel { get; set; }
public string name { get; set; }
public string namezus { get; set; }
public string anredzus { get; set; }
public string strasse { get; set; }
public string strasse2 { get; set; }
public string plz { get; set; }
public string ort { get; set; }
public string land { get; set; }
public string pplz { get; set; }
public string postfach { get; set; }
public string name1 { get; set; }
public string name2 { get; set; }
public string name3 { get; set; }
public string name4 { get; set; }
public string name5 { get; set; }
public string funktion { get; set; }
public string funktion2 { get; set; }
public string abteilung { get; set; }
public string funktionad { get; set; }
public static int GenerateNewID(int base_id)
{
//var newid = 100000 + base_id;
int highest = 0;
foreach (var set in Settings._instance.addressSets.addresses)
{
foreach (var add in set.KasPersons)
{
if(add.id >= highest) highest = add.id+1;
}
}
return highest + base_id + 1;
}
public override string ToString()
{
if (refsid != null && refsid != 0)
{
return refsid + " - " + name;
}
else
{
return id + " - " + name;
}
}
}
public class KasPersonError
{
public KasPersonError((List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>) single_result)
{
//refsid = single_result.Item1;
errors = single_result.Item1;
warnings = single_result.Item2;
}
//public int refsid { get; set; }
public List<AddressCheck.ErrorTypes> errors { get; set; } = new();
public List<AddressCheck.WarningTypes> warnings { get; set; } = new();
public string GetString()
{
var output = "";
foreach (var error in errors) output += error + ", ";
foreach (var warning in warnings) output += warning + ", ";
return output;
}
public string ToString(KasPerson person)
{
return "ID:"+person.id + "; Name: " +person.name +"; Errors: " + GetString();
}
}
+226
View File
@@ -0,0 +1,226 @@
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
namespace Logof_Client;
public class Settings
{
public static Settings _instance = new();
public AddressSets addressSets = new();
public Customers customers = new();
public PdfExportSettings pdfExport { get; set; } = new();
// public string settingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
// "logofclient", "config.json");
public Settings()
{
_instance = this;
}
public static void Save()
{
if (!Directory.Exists(Global._instance.config_path))
Directory.CreateDirectory(Global._instance.config_path);
// if (!string.IsNullOrEmpty(Global._instance.config_path)) _instance.settingsPath = Global._instance.config_path;
var json = JsonConvert.SerializeObject(_instance);
File.WriteAllText(Path.Combine(Global._instance.config_path,"config.json"), json);
}
public static void Load()
{
//if (!string.IsNullOrEmpty(Global._instance.config_path)) _instance.settingsPath = Global._instance.config_path;
try
{
var contents = File.ReadAllText(Path.Combine(Global._instance.config_path, "config.json"));
_instance = JsonConvert.DeserializeObject<Settings>(contents);
MainWindow._instance.RefreshCustomerItems();
}
catch (Exception ex)
{
Logger.Log($"Error while reading settings. Generating new... {ex.Message}", Logger.LogType.Warning);
_instance = new Settings();
}
}
}
public class PdfExportSettings
{
public double cellPaddingTopMm { get; set; } = 5;
public double cellPaddingBottomMm { get; set; } = 5;
public double cellPaddingLeftMm { get; set; } = 5;
public double cellPaddingRightMm { get; set; } = 5;
public double pageMarginTopMm { get; set; } = 0;
public double pageMarginBottomMm { get; set; } = 0;
public double pageMarginLeftMm { get; set; } = 0;
public double pageMarginRightMm { get; set; } = 0;
public int rowsPerPage { get; set; } = 7;
public int columnsPerPage { get; set; } = 3;
public double fontSize { get; set; } = 9;
public double smallFontSize { get; set; } = 6;
}
public class Global
{
public static Global _instance;
public Global()
{
_instance = this;
}
public string config_path { get; set; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient");
public void SetConfigPath(string path)
{
if (!string.IsNullOrWhiteSpace(path))
config_path = PathUtilities.NormalizeFileSystemPath(path);
}
public string wiki_storage_path { get; set; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient",
"wiki");
public List<Country> countries { get; set; } = new();
public string font_path { get; set; } = Path.Combine(AppContext.BaseDirectory, "assets", "fonts");
public static void Save()
{
if (!Directory.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient")))
Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient"));
var json = JsonConvert.SerializeObject(_instance, Formatting.Indented);
File.WriteAllText(
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient",
"global.config"), json);
}
public static void Load()
{
// if (!File.Exists(Path.Combine(
// Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient",
// "global.config")))
// File.Create(Path.Combine(
// Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient",
// "global.config"));
try
{
var contents = File.ReadAllText(Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "logofclient",
"global.config"));
_instance = JsonConvert.DeserializeObject<Global>(contents) ?? new Global();
_instance.NormalizePaths();
}
catch (Exception ex)
{
Logger.Log($"Error while reading global settings. Generating new... {ex.Message}", Logger.LogType.Warning);
_instance = new Global();
Save();
}
}
private void NormalizePaths()
{
config_path = PathUtilities.NormalizeFileSystemPath(config_path);
wiki_storage_path = PathUtilities.NormalizeFileSystemPath(wiki_storage_path);
font_path = PathUtilities.NormalizeFileSystemPath(font_path);
}
}
public class Customers
{
public List<Customer> customers = new();
public Customer current { get; set; } = null;
}
public class Customer
{
public Customer()
{
var highestID = 0;
foreach (var customer in Settings._instance.customers.customers)
if (customer.ID > highestID)
highestID = customer.ID;
ID = highestID + 1;
}
public override string ToString()
{
return name;
}
public string name { get; set; } = "";
public string description { get; set; } = "";
public string sender_address { get; set; } = "";
public AddressPatch patch { get; set; }
public char separator { get; set; } = ',';
public int ID { get; }
// public static int GetIDByCustomerListItem(string item_content)
// {
// var id = item_content.Split(" - ")[0];
// return int.Parse(id);
// }
}
public class AddressSets
{
public List<KasAddressList> addresses = new();
public KasAddressList GetAddressSetByID(int ID)
{
foreach (var i in addresses)
if (i.ID == ID)
return i;
return null;
}
}
public class Country
{
public Country(string name, string translation, List<string> alternatives)
{
this.name = name;
this.translation = translation;
this.alternatives = alternatives;
}
public Country(string name)
{
this.name = name;
translation = "";
alternatives = new List<string>();
}
public Country()
{
}
public string? name { get; set; }
public string translation { get; set; }
public List<string> alternatives { get; set; }
public static Country GetByName(string name)
{
foreach (var country in Global._instance.countries)
if (country.name == name)
return country;
return null;
}
}
-156
View File
@@ -1,156 +0,0 @@
using System;
using System.Collections.Generic;
namespace Logof_Client;
public class KasAddressList
{
public List<KasPerson> KasPersons;
public KasAddressList()
{
KasPersons = new List<KasPerson>();
}
}
public class KasPerson
{
public KasPerson()
{
refsid = 0;
anrede = "";
titel = "";
vorname = "";
adel = "";
name = "";
namezus = "";
anredzus = "";
strasse = "";
strasse2 = "";
plz = 0;
ort = "";
land = "";
pplz = 0;
postfach = "";
name1 = "";
name2 = "";
name3 = "";
name4 = "";
name5 = "";
funktion = "";
funktion2 = "";
abteilung = "";
funktionad = "";
}
public KasPerson(int refsid,
string anrede,
string titel,
string vorname,
string adel,
string name,
string namezus,
string anredzus,
string strasse,
string strasse2,
int plz,
string ort,
string land,
int pplz,
string postfach,
string name1,
string name2,
string name3,
string name4,
string name5,
string funktion, // ignorieren
string funktion2, // ignorieren
string abteilung,
string funktionad)
{
this.refsid = refsid;
this.anrede = anrede;
this.titel = titel;
this.vorname = vorname;
this.adel = adel;
this.name = name;
this.namezus = namezus;
this.anredzus = anredzus;
this.strasse = strasse;
this.strasse2 = strasse2;
this.plz = plz;
this.ort = ort;
this.land = land;
this.pplz = pplz;
this.postfach = postfach;
this.name1 = name1;
this.name2 = name2;
this.name3 = name3;
this.name4 = name4;
this.name5 = name5;
this.funktion = funktion;
this.funktion2 = funktion2;
this.abteilung = abteilung;
this.funktionad = funktionad;
}
public int refsid { get; set; }
public string anrede { get; set; }
public string titel { get; set; }
public string vorname { get; set; }
public string adel { get; set; }
public string name { get; set; }
public string namezus { get; set; }
public string anredzus { get; set; }
public string strasse { get; set; }
public string strasse2 { get; set; }
public int plz { get; set; }
public string ort { get; set; }
public string land { get; set; }
public int pplz { get; set; }
public string postfach { get; set; }
public string name1 { get; set; }
public string name2 { get; set; }
public string name3 { get; set; }
public string name4 { get; set; }
public string name5 { get; set; }
public string funktion { get; set; }
public string funktion2 { get; set; }
public string abteilung { get; set; }
public string funktionad { get; set; }
}
public class KasPersonError
{
public KasPersonError((int, List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>) single_result)
{
refsid = single_result.Item1;
try
{
foreach (var err in single_result.Item2) errors += err + ", ";
errors = errors.Trim();
errors = errors.TrimEnd(',');
}
catch
{
}
try
{
if (single_result.Item3 != null)
{
foreach (var err in single_result.Item3) warnings += err + ", ";
warnings = warnings.Trim();
warnings = warnings.TrimEnd(',');
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
public int refsid { get; set; }
public string errors { get; set; } = "";
public string warnings { get; set; } = "";
}
+40
View File
@@ -0,0 +1,40 @@
using System;
using System.IO;
using Newtonsoft.Json;
namespace Logof_Client;
public static class Logger
{
public static void Log(string text, LogType logType = LogType.Info)
{
try
{
string config_path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient");
if (!Directory.Exists(config_path))
Directory.CreateDirectory(config_path);
string log_path = Path.Combine(config_path, $"log-{DateTime.Now:dd-MM-yy}.log");
if(!File.Exists(log_path))
File.Create(log_path).Close();
string line = $"[{DateTime.Now:dd.MM.yyyy - hh:mm:ss}]: ({logType.ToString()}) {text}";
Console.WriteLine(line);
File.AppendAllLines(log_path, [line]);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
public enum LogType
{
Error,
Warning,
Info
}
}
+58 -21
View File
@@ -1,24 +1,61 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.2" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.2" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.2" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.2" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.2" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.2">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="Lucide.Avalonia" Version="0.1.35" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.2"/>
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.2"/>
<PackageReference Include="Avalonia.Desktop" Version="11.3.2"/>
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.2"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.2"/>
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.2">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="ISO3166" Version="1.0.4"/>
<PackageReference Include="Lucide.Avalonia" Version="0.1.35"/>
<PackageReference Include="Newtonsoft.Json" Version="13.0.4"/>
<PackageReference Include="PdfSharp" Version="6.1.1"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Markdig" Version="0.30.3"/>
<PackageReference Include="AvaloniaEdit" Version="0.10.12"/>
</ItemGroup>
<ItemGroup>
<None Include="wiki\**\*.md">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Remove="assets\icon.ico"/>
<AvaloniaResource Include="assets\icon.ico">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AvaloniaResource>
<None Remove="assets\calc_man.png"/>
<AvaloniaResource Include="assets\calc_man.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AvaloniaResource>
<None Remove="assets\loading.mp4"/>
<AvaloniaResource Include="assets\loading.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</AvaloniaResource>
</ItemGroup>
<ItemGroup>
<Content Include="assets\fonts\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Folder Include="assets\fonts\"/>
</ItemGroup>
</Project>
+635 -82
View File
@@ -1,82 +1,635 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Logof_Client.MainWindow"
Title="Logof Client">
<Border>
<Grid RowDefinitions="30,*,*">
<Menu Background="#50888888">
<MenuItem Header="Datei">
<MenuItem Click="MnuSettings_OnClick" x:Name="MnuSettings" Header="Einstellungen" />
<Separator />
<MenuItem Click="MnuExit_OnClick" x:Name="MnuExit" Header="Beenden" />
</MenuItem>
<MenuItem Header="Hilfe">
<MenuItem Header="Onlinehilfe" x:Name="MnuHelp" Click="MnuHelp_OnClick" />
<MenuItem Header="Github" x:Name="MnuGithub" Click="MnuGithub_OnClick" />
<MenuItem Header="Über" x:Name="MnuAbout" Click="MnuAbout_OnClick" />
</MenuItem>
</Menu>
<Grid Grid.Row="1">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBox x:Name="TbFilename" Watermark="Dateipfad" Width="400" VerticalContentAlignment="Center" />
<Button x:Name="BtnChooseFile" Margin="10,0,0,0" Click="BtnChooseFile_OnClick">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="FolderOpen" Width="16" Height="16" Size="16" />
<Label Content="Öffnen" VerticalContentAlignment="Center" />
</StackPanel>
</Button>
</StackPanel>
</Grid>
<Grid Grid.ColumnDefinitions="*,*" Grid.Row="2">
<StackPanel Grid.Column="0" Width="250" Orientation="Vertical" HorizontalAlignment="Right"
Margin="0,0,5,0">
<Button HorizontalAlignment="Stretch" HorizontalContentAlignment="Center" Margin="0,0,0,10"
x:Name="BtnCheck" Click="BtnCheck_OnClick">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="SpellCheck" Width="36" Height="36" />
<Label Content="Prüfen" VerticalContentAlignment="Center" FontSize="15" FontWeight="Bold" />
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" IsEnabled="True" HorizontalContentAlignment="Center"
Click="BtnCombine_OnClick" x:Name="BtnCombine"
Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Combine" Width="36" Height="36" />
<Label Content="Zusammenführen" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" IsEnabled="False" HorizontalContentAlignment="Center"
Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Hammer" Width="36" Height="36" />
<Label Content="Reparieren" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel>
</Button>
</StackPanel>
<StackPanel Grid.Column="1" Width="250" Orientation="Vertical" HorizontalAlignment="Left"
Margin="5,0,0,0">
<Button HorizontalAlignment="Stretch" IsEnabled="False" HorizontalContentAlignment="Center"
Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="ListX" Width="36" Height="36" />
<Label Content="Kürzen" VerticalContentAlignment="Center" FontSize="15" FontWeight="Bold" />
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" IsEnabled="False" HorizontalContentAlignment="Center"
Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Tags" Width="36" Height="36" />
<Label Content="Etiketten generieren" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel>
</Button>
</StackPanel>
</Grid>
</Grid>
</Border>
</Window>
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
MinWidth="1000" MinHeight="600" IsVisible="False"
x:Class="Logof_Client.MainWindow" WindowState="Maximized" Icon="assets/icon.ico"
Title="Logof Client">
<Border>
<Grid RowDefinitions="30,*">
<Menu Background="#50888888">
<MenuItem Header="Datei">
<!-- <MenuItem Click="MnuSettings_OnClick" x:Name="MnuSettings" Header="Einstellungen" /> -->
<!-- <Separator /> -->
<MenuItem Click="MnuExit_OnClick" x:Name="MnuExit" Header="Beenden" />
</MenuItem>
<MenuItem Header="Hilfe">
<MenuItem Header="Onlinehilfe" x:Name="MnuHelp" Click="MnuHelp_OnClick" />
<MenuItem Header="Git" x:Name="MnuGit" Click="MnuGit_OnClick" />
<MenuItem Header="Über" x:Name="MnuAbout" Click="MnuAbout_OnClick" />
</MenuItem>
</Menu>
<TabControl Grid.Row="1">
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="MapPinHouse" Width="32" Height="32" Size="32" />
<Label FontSize="20" Content="Addressverwaltung" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
<Grid RowDefinitions="3*,80,2*">
<Grid ColumnDefinitions="*,*" Grid.Row="0">
<!-- Kunden -->
<Grid Margin="30,30,10,30">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<LucideIcon Kind="CircleUser" Width="16" Height="16" Size="16" />
<Label Content="Kunden" VerticalContentAlignment="Center" />
</StackPanel>
<ListBox Grid.Row="1"
Margin="0,5,0,0"
x:Name="LstCustomers"
Background="AliceBlue" SelectionChanged="LstCustomers_OnSelectionChanged"
SelectionMode="Single" />
</Grid>
<!-- Address-Sets -->
<Grid Margin="10,30,30,30" Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<LucideIcon Kind="NotebookTabs" Width="16" Height="16" Size="16" />
<Label Content="Address-Sets" VerticalContentAlignment="Center" />
</StackPanel>
<ListBox Grid.Row="1"
x:Name="LstCustomerAdressSets"
SelectionChanged="LstCustomerAdressSets_OnSelectionChanged"
Background="AliceBlue"
Margin="0,5,0,5"
SelectionMode="Multiple,Toggle">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem x:Name="MnIAdSetRename" Click="MnIAdSetRename_OnClick">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Pencil" Width="12" Height="12" Size="12" />
<Label Content="Umbenennen" VerticalContentAlignment="Center" />
</StackPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem x:Name="MnIAdSetDelete" Click="MnIAdSetRename_OnClick" IsEnabled="False">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Trash" Width="12" Height="12" Size="12" />
<Label Content="Löschen" VerticalContentAlignment="Center" />
</StackPanel>
</MenuItem.Header>
</MenuItem>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
<Button Grid.Row="2" HorizontalAlignment="Stretch" x:Name="BtnCustomerAddressSetImport"
Click="BtnCustomerAddressSetImport_OnClick">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Plus" Width="16" Height="16" Size="16" />
<Label Content="Importieren" VerticalContentAlignment="Center" />
</StackPanel>
</Button>
</Grid>
</Grid>
<!-- <Grid ColumnDefinitions="*,*,*" Grid.Row="1"> -->
<StackPanel Grid.Row="1" Orientation="Horizontal" Spacing="10" HorizontalAlignment="Center"
Margin="0,0,5,0">
<Button Width="250" HorizontalContentAlignment="Center"
Margin="0,0,0,10" IsEnabled="False"
x:Name="BtnCheck" Click="BtnCheck_OnClick">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="SpellCheck" Width="36" Height="36" />
<Label Content="Prüfen" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel>
</Button>
<Button Width="250" IsEnabled="False"
HorizontalContentAlignment="Center"
Click="BtnCombine_OnClick" x:Name="BtnCombine"
Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Combine" Width="36" Height="36" />
<Label Content="Zusammenführen" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel>
</Button>
<Button Width="250" IsEnabled="False" Click="BtnShorten_OnClick"
HorizontalContentAlignment="Center" x:Name="BtnShorten"
Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="ListX" Width="36" Height="36" />
<Label Content="Kürzen" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel>
</Button>
<Button Width="250" IsEnabled="False"
Click="BtnGenerateLabels_OnClick"
HorizontalContentAlignment="Center" x:Name="BtnGenerateLabels"
Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Tags" Width="36" Height="36" />
<Label Content="Etiketten generieren" VerticalContentAlignment="Center"
FontSize="15"
FontWeight="Bold" />
</StackPanel>
</Button>
<Button Width="250" IsEnabled="False"
HorizontalContentAlignment="Center" x:Name="BtnRepair"
Margin="0,0,0,10">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Hammer" Width="36" Height="36" />
<Label Content="Reparieren" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel>
</Button>
</StackPanel>
<!-- </Grid> -->
<Grid Grid.Row="2" Margin="20" IsVisible="True" x:Name="GrdCalcMan">
<Image Source="assets/calc_man.png" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
<Grid Grid.Row="2" Margin="20" IsVisible="False" x:Name="GrdCombineTypes">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="10"
Margin="5,0,0,0">
<Button HorizontalAlignment="Stretch" MinWidth="240"
HorizontalContentAlignment="Center" x:Name="BtnCombineUnion"
Click="BtnCombineUnion_OnClick"
Margin="0,0,0,10">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="SquaresUnite" Width="36" Height="36" />
<Label Content="Vereinigung" VerticalContentAlignment="Center"
FontSize="15"
FontWeight="Bold" />
</StackPanel>
<Label FontSize="9"
Content="Fügt Elemente beider Mengen in eine Menge zusammen" />
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" MinWidth="240"
HorizontalContentAlignment="Center" x:Name="BtnCombineIntersect"
Click="BtnCombineIntersection_OnClick"
Margin="0,0,0,10">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="SquaresIntersect" Width="36" Height="36" />
<Label Content="Schnittmenge" VerticalContentAlignment="Center"
FontSize="15"
FontWeight="Bold" />
</StackPanel>
<Label FontSize="9"
Content="Überträgt nur doppelte Elemente in die neue Menge" />
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" MinWidth="240"
HorizontalContentAlignment="Center" x:Name="BtnCombineDifference"
Click="BtnCombineDifference_OnClick"
Margin="0,0,0,10">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="SquaresSubtract" Width="36" Height="36" />
<Label Content="Differenz" VerticalContentAlignment="Center"
FontSize="15"
FontWeight="Bold" />
</StackPanel>
<Label FontSize="9"
Content="Elemente der ersten Menge ohne Elemente der zweiten Menge" />
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" MinWidth="240"
HorizontalContentAlignment="Center" x:Name="BtnCombineSymmetric"
Click="BtnCombineSymmetricDifference_OnClick"
Margin="0,0,0,10">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="SquaresExclude" Width="36" Height="36" />
<Label Content="Symmetrische Differenz"
VerticalContentAlignment="Center"
FontSize="15"
FontWeight="Bold" />
</StackPanel>
<Label FontSize="9" Content="Nur Elemente, die NICHT doppelt sind" />
</StackPanel>
</Button>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Spacing="50">
<StackPanel Orientation="Vertical" >
<RadioButton Content="Vergleiche nach refsid" IsChecked="True" x:Name="RbComprefsid"></RadioButton>
<RadioButton Content="Vergleiche nach finaler Adresse" IsChecked="False" x:Name="RbCompfinAd"></RadioButton>
</StackPanel>
<StackPanel Orientation="Vertical">
<CheckBox HorizontalAlignment="Left" x:Name="CbMergeExportUnmerged" IsChecked="False">Speichere Unverarbeitete in neuem Verteiler</CheckBox>
<CheckBox HorizontalAlignment="Left" x:Name="CbMergeDeleteOld" IsChecked="False">Lösche ursprüngliche Sets</CheckBox>
</StackPanel>
</StackPanel>
</StackPanel>
</Grid>
<Grid Grid.Row="2" Margin="20" ColumnDefinitions="*,5*,*" IsVisible="False" x:Name="GrdExportMarginOptions">
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="20">
<Grid ColumnDefinitions="*,*,*" ColumnSpacing="20">
<StackPanel Orientation="Vertical" Spacing="10">
<Grid ColumnDefinitions="*,*">
<Label Grid.Column="0" Content="Zellenrand oben (mm)"></Label>
<NumericUpDown Grid.Column="1" x:Name="NudExpMargCellPaddingTop" Minimum="0" Maximum="20" Value="5"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Grid.Column="0" Content="Zellenrand unten (mm)"></Label>
<NumericUpDown Grid.Column="1" x:Name="NudExpMargCellPaddingBot" Minimum="0" Maximum="20" Value="5"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Grid.Column="0" Content="Zellenrand links (mm)"></Label>
<NumericUpDown Grid.Column="1" x:Name="NudExpMargCellPaddingLeft" Minimum="0" Maximum="20" Value="5"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Grid.Column="0" Content="Zellenrand rechts (mm)"></Label>
<NumericUpDown Grid.Column="1" x:Name="NudExpMargCellPaddingRight" Minimum="0" Maximum="20" Value="5"></NumericUpDown>
</Grid>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="10">
<Grid ColumnDefinitions="*,*">
<Label Content="Zellenabstand oben"></Label>
<NumericUpDown Grid.Column="1" x:Name="TbExpMargMarginTop" Minimum="0" Maximum="20" Value="0"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Content="Zellenabstand unten"></Label>
<NumericUpDown Grid.Column="1" x:Name="TbExpMargMarginBottom" Minimum="0" Maximum="20" Value="0"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Content="Zellenabstand rechts"></Label>
<NumericUpDown Grid.Column="1" x:Name="TbExpMargMarginRight" Minimum="0" Maximum="20" Value="0"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Content="Zellenabstand links"></Label>
<NumericUpDown Grid.Column="1" x:Name="TbExpMargMarginLeft" Minimum="0" Maximum="20" Value="0"></NumericUpDown>
</Grid>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Vertical" Spacing="10">
<Grid ColumnDefinitions="*,*">
<Label Content="Zeilen pro Seite"></Label>
<NumericUpDown Grid.Column="1" x:Name="TbExpMargRowsPerPage" Minimum="1" Maximum="10" Value="7"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Grid.Column="0" Content="Spalten pro Seite"></Label>
<NumericUpDown Grid.Column="1" x:Name="NudExpMargColumnsPerPage" Minimum="1" Maximum="8" Value="3"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Grid.Column="0" Content="Schriftgröße (groß)"></Label>
<NumericUpDown Grid.Column="1" x:Name="NudExpMargFontSize" Minimum="5" Maximum="30" Value="9"></NumericUpDown>
</Grid>
<Grid ColumnDefinitions="*,*">
<Label Grid.Column="0" Content="Schriftgröße (klein)"></Label>
<NumericUpDown Grid.Column="1" x:Name="NudExpMargSmallFontSize" Minimum="3" Maximum="30" Value="6"></NumericUpDown>
</Grid>
</StackPanel>
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="Start" Click="BtnStartGenerateLabels_OnClick"></Button>
</StackPanel>
</StackPanel>
</Grid>
</Grid>
</TabItem>
<TabItem IsEnabled="False">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Users" Width="32" Height="32" Size="32" />
<Label FontSize="20" Content="Personalverwaltung" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
</TabItem>
<TabItem IsEnabled="False">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="BadgeEuro" Width="32" Height="32" Size="32" />
<Label FontSize="20" Content="Finanzverwaltung" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
</TabItem>
<TabItem IsEnabled="False">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="ReceiptEuro" Width="32" Height="32" Size="32" />
<Label FontSize="20" Content="Preisverwaltung" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
</TabItem>
<TabItem IsEnabled="True">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="LibraryBig" Width="32" Height="32" Size="32" />
<Label FontSize="20" Content="Wiki" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
<Grid ColumnDefinitions="300,*">
<Border Grid.Column="0" Background="#FFF" BorderBrush="#DDD" BorderThickness="0,0,1,0">
<StackPanel>
<StackPanel Spacing="10" Orientation="Horizontal" Margin="10">
<Button Content="+ Datei" x:Name="BtnWikiAddFile"
Click="BtnWikiAddFile_OnClick" />
<Button Content="+ Ordner" x:Name="BtnWikiAddFolder"
Click="BtnWikiAddFolder_OnClick" />
</StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<TreeView Name="NavTree" Margin="10" />
</ScrollViewer>
</StackPanel>
</Border>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="8" Grid.Row="0">
<Button Name="EditButton" Content="Edit" HorizontalContentAlignment="Center"
IsEnabled="False" Width="80" Margin="0,0,8,0" />
<Button Name="OpenFolderButton" HorizontalContentAlignment="Center"
Content="Open Folder" Width="100" />
</StackPanel>
<Border Grid.Row="1" Margin="8" BorderBrush="#DDD" BorderThickness="1" CornerRadius="4">
<ScrollViewer>
<StackPanel Name="PreviewPanel" Margin="8" />
</ScrollViewer>
</Border>
</Grid>
</Grid>
</TabItem>
<TabItem IsEnabled="True">
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Settings" Width="32" Height="32" Size="32" />
<Label FontSize="20" Content="Einstellungen" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
<TabControl>
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="FileCog" Width="16" Height="16" Size="16" />
<Label FontSize="16" Content="Global" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
<StackPanel Orientation="Vertical" Spacing="10">
<Grid ColumnDefinitions="400,*">
<Label Grid.Column="0">config-Datei</Label>
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBox x:Name="TbConfigPath" HorizontalAlignment="Stretch"
Watermark="/home/username/.config/logofclient/" />
<Button x:Name="BtnConfigPath" HorizontalAlignment="Right">
<Button.Content>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="File" Width="16" Height="16" Size="16" />
<Label Content="Öffnen..." VerticalContentAlignment="Center" />
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
</StackPanel>
</Grid>
<Grid ColumnDefinitions="400,*">
<Label Grid.Column="0">Wiki-Pfad</Label>
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBox x:Name="TbWikiPath" HorizontalAlignment="Stretch"
Watermark="/home/username/.config/logofclient/wiki" />
<Button IsEnabled="True" x:Name="BtnWikiPath" HorizontalAlignment="Right">
<Button.Content>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Folder" Width="16" Height="16" Size="16" />
<Label Content="Öffnen..." VerticalContentAlignment="Center" />
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
</StackPanel>
</Grid>
<Grid ColumnDefinitions="400,*">
<Label Grid.Column="0">Font-Pfad</Label>
<StackPanel Grid.Column="1" Orientation="Vertical" Spacing="5">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBox x:Name="TbFontPath" HorizontalAlignment="Stretch"
Watermark="[App-Direcotry]/assets/fonts/" />
<Button IsEnabled="True" x:Name="BtnFontPath" HorizontalAlignment="Right">
<Button.Content>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Folder" Width="16" Height="16" Size="16" />
<Label Content="Öffnen..." VerticalContentAlignment="Center" />
</StackPanel>
</Button.Content>
</Button>
</StackPanel>
</StackPanel>
</Grid>
</StackPanel>
</TabItem>
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Contact" Width="16" Height="16" Size="16" />
<Label FontSize="16" Content="Kunden" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
<Grid ColumnDefinitions="300,*">
<Grid RowDefinitions="*,60">
<ListBox x:Name="LstSettingsCustomers"
SelectionChanged="LstSettingsCustomers_OnSelectionChanged" />
<Button Grid.Row="1" HorizontalAlignment="Stretch" x:Name="BtnSettingsAddCustomer"
Click="BtnSettingsAddCustomer_OnClick"
HorizontalContentAlignment="Center">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Plus" Width="32" Height="32" />
<Label Content="Hinzufügen" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel>
</Button>
</Grid>
<StackPanel Margin="10,0,0,0" Grid.Column="1" Spacing="10" Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="TableProperties" Width="24" Height="24" />
<Label Content="Eigenschaften" VerticalContentAlignment="Center" FontSize="15"
FontWeight="Bold" />
</StackPanel>
<Grid ColumnDefinitions="150,*">
<Label Content="Name" />
<TextBox Grid.Column="1" Watermark="Kundenbezeichnung"
TextChanged="TbSettingsCustomerName_OnTextChanged"
HorizontalAlignment="Stretch"
x:Name="TbSettingsCustomerName" />
</Grid>
<Grid ColumnDefinitions="150,*">
<Label Content="Beschreibung" />
<TextBox Grid.Column="1" Watermark="Kundenbeschreibung"
HorizontalAlignment="Stretch"
TextChanged="TbSettingsCustomerDescription_OnTextChanged"
x:Name="TbSettingsCustomerDescription" />
</Grid>
<Grid ColumnDefinitions="150,*">
<Label Content="Absenderadresse" />
<TextBox Grid.Column="1" Watermark="Absenderadresse"
HorizontalAlignment="Stretch"
TextChanged="TbSettingsCustomerSenderAddress_OnTextChanged"
x:Name="TbSettingsCustomerSenderAddress" />
</Grid>
<Grid ColumnDefinitions="150,*,Auto">
<Label Content="CSV-Trennzeichen" />
<TextBox Grid.Column="1" Watermark=","
HorizontalAlignment="Stretch" Margin="0,0,5,0"
TextChanged="TbSettingsCustomerCsvSeparator_OnTextChanged"
x:Name="TbSettingsCustomerCsvSeparator" />
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="5">
<Button Click="ChangeCSVDivider" Content=","></Button>
<Button Click="ChangeCSVDivider" Content=";"></Button>
<Button Click="ChangeCSVDivider" Content="TAB"></Button>
<Button Click="ChangeCSVDivider" Content="|"></Button>
<Button Click="ChangeCSVDivider" Content="SPACE"></Button>
<Button Click="ChangeCSVDivider" Content=":"></Button>
</StackPanel>
</Grid>
<Grid ColumnDefinitions="150,*">
<Label Content="Address-Patch-Info" />
<TextBlock Grid.Column="1"
HorizontalAlignment="Stretch"
x:Name="TbSettingsCustomerPatchInfo" />
</Grid>
<Button HorizontalAlignment="Stretch"
x:Name="BtnSettingsImportCustomerAddressPatch"
Click="BtnSettingsImportCustomerAddressPatch_OnClick"
HorizontalContentAlignment="Center">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Route" Width="16" Height="16" Size="16" />
<Label FontSize="16" Content="Address-Patch importieren..."
VerticalContentAlignment="Center" />
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch" x:Name="BtnSettingsSaveCustomerData"
Click="BtnSettingsSaveCustomerData_OnClick"
HorizontalContentAlignment="Center">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Save" Width="16" Height="16" Size="16" />
<Label FontSize="16" Content="Speichern"
VerticalContentAlignment="Center" />
</StackPanel>
</Button>
<Button Background="#99963434" HorizontalAlignment="Stretch" x:Name="BtnDeleteCustomer" Click="BtnDeleteCustomer_OnClick"
HorizontalContentAlignment="Center">
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Trash" Width="16" Height="16" Size="16" />
<Label FontSize="16" Content="Löschen" VerticalContentAlignment="Center" />
</StackPanel>
</Button>
</StackPanel>
</Grid>
</TabItem>
<TabItem>
<TabItem.Header>
<StackPanel Orientation="Horizontal">
<LucideIcon Kind="Globe" Width="16" Height="16" Size="16" />
<Label FontSize="16" Content="Länder" VerticalContentAlignment="Center" />
</StackPanel>
</TabItem.Header>
<Grid ColumnDefinitions="*,*">
<Grid Grid.Column="0" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox x:Name="CountryList" Grid.Row="0"
SelectionChanged="CountryList_OnSelectionChanged" />
<Grid Grid.Row="1" ColumnDefinitions="*,250" Margin="0,5,0,0">
<TextBox x:Name="TbSettingsNewCountry" />
<Button Grid.Column="1" x:Name="BtnSettingsNewCountry"
HorizontalAlignment="Stretch" Click="BtnSettingsNewCountry_OnClick"
HorizontalContentAlignment="Center" Content="+ Hinzufügen"
Margin="5,0,0,0" />
</Grid>
</Grid>
<Grid Grid.Column="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,5"
HorizontalAlignment="Right">
<Button x:Name="BtnSettingsInsertDefaultCountries"
Content="Standard-Länder laden (Englisch)"
Click="BtnSettingsInsertDefaultCountries_OnClick" />
</StackPanel>
<Grid Grid.Row="1" ColumnDefinitions="300,*" Margin="0,0,0,5">
<TextBlock Text="Name:" VerticalAlignment="Center" />
<TextBox Grid.Column="1" x:Name="TbSettingsCountryName"
TextChanged="TbSettingsCountryName_OnTextChanged" />
</Grid>
<Grid ColumnDefinitions="300,*" Margin="0,0,0,5" Grid.Row="2">
<TextBlock Text="Übersetzung:" VerticalAlignment="Center" />
<TextBox Grid.Column="1" x:Name="TbSettingsCountryTranslation"
TextChanged="TbSettingsCountryTranslation_OnTextChanged" />
</Grid>
<Grid Grid.Row="3">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0" x:Name="LbSettingsAlternatives" SelectionMode="Multiple" />
<Grid Grid.Row="1" ColumnDefinitions="*,200" Margin="0,5,0,0">
<TextBox x:Name="TbSettingsNewCountryAlternative"
Watermark="Kürzel/Alternative" />
<Button Grid.Column="1" x:Name="BtnSettingsNewCountryAlternative"
Content="+ Hinzufügen" Margin="5 0 0 0"
HorizontalContentAlignment="Center"
Click="BtnSettingsNewCountryAlternative_OnClick"
HorizontalAlignment="Stretch" />
</Grid>
<Button x:Name="BtnSettingsRemoveSelectedAlternatives" Grid.Row="2"
Content="Ausgewählte Entfernen" Background="#99963434"
HorizontalContentAlignment="Center"
HorizontalAlignment="Stretch"
Click="BtnSettingsRemoveSelectedAlternatives_OnClick"
Margin="0,5,0,0" />
<Button Grid.Row="3"
Content="Land Entfernen" Background="#99963434"
HorizontalContentAlignment="Center" x:Name="BtnSettingsRemoveCountry"
HorizontalAlignment="Stretch" Click="BtnSettingsRemoveCountry_OnClick"
Margin="0,5,0,0" />
</Grid>
</Grid>
</Grid>
</TabItem>
</TabControl>
</TabItem>
</TabControl>
</Grid>
</Border>
</Window>
+1240 -212
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -3,7 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" SizeToContent="WidthAndHeight"
x:Class="Logof_Client.MessageBox"
x:Class="Logof_Client.MessageBox" Icon="assets/icon.ico"
Title="MessageBox">
<StackPanel>
<TextBlock Name="Text" Margin="10" TextWrapping="Wrap" />
+19
View File
@@ -0,0 +1,19 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen" Topmost="True"
x:Class="Logof_Client.NamingWindow"
Title="NamingWindow">
<StackPanel Orientation="Vertical">
<TextBlock Name="Text" Margin="10" TextWrapping="Wrap" />
<TextBox Name="Input" Margin="10" />
<StackPanel HorizontalAlignment="Right" Margin="5" Orientation="Horizontal" Name="Buttons">
<StackPanel.Styles>
<Style Selector="Button">
<Setter Property="Margin" Value="5" />
</Style>
</StackPanel.Styles>
</StackPanel>
</StackPanel>
</Window>
+59
View File
@@ -0,0 +1,59 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Avalonia.Controls;
namespace Logof_Client;
public partial class NamingWindow : Window
{
public NamingWindow()
{
InitializeComponent();
}
public static Task<string> Show(Window parent, string input = "", string info = "Bitte geben Sie einen Namen ein:")
{
try
{
var wind = new NamingWindow
{
Title = "Name eingeben"
};
wind.FindControl<TextBlock>("Text").Text = info;
var buttonPanel = wind.FindControl<StackPanel>("Buttons");
var inputBox = wind.FindControl<TextBox>("Input");
inputBox.Text = input;
string res = null;
void AddButton(string caption)
{
var btn = new Button { Content = caption };
btn.Click += (_, __) =>
{
res = inputBox.Text;
wind.Close();
};
buttonPanel.Children.Add(btn);
}
AddButton("Ok");
var tcs = new TaskCompletionSource<string>();
wind.Closed += delegate { tcs.TrySetResult(res); };
if (parent != null)
wind.ShowDialog(parent);
else wind.Show();
wind.Focus();
return tcs.Task;
}
catch (Exception ex)
{
Logger.Log("Error while showing naming window: " + ex.Message, Logger.LogType.Warning);
return Task.FromResult<string>(null!);
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" Width="800" MinWidth="800" MaxWidth="800" d:DesignHeight="150"
Height="150" MinHeight="150" MaxHeight="150"
Height="150" MinHeight="150" MaxHeight="150" Icon="assets/icon.ico" WindowStartupLocation="CenterScreen" Topmost="True"
x:Class="Logof_Client.ProgressWindow" Title="Verarbeitung läuft...">
<Grid>
<!-- <ScrollViewer x:Name="ScvLog"> -->
+2 -2
View File
@@ -16,9 +16,9 @@ public partial class ProgressWindow : Window
PbProgress.Value = percentage;
}
public void AddToLog(string message)
public void AddToLog(string message, string percent)
{
TbLog.Text = message;
TbLog.Text = message + $"\n{percent}%";
//ScvLog.ScrollToEnd();
}
}
+8
View File
@@ -0,0 +1,8 @@
# Logofclient
Free and open source software suite to manage customers, prices, employees, etc.
## Bugs
Please report any bugs you find to fierke@mypapertown.de, thank you!
## Contributing
Feel free to contribute to this project using your MyPaperCloud-Account (request it via fierke@mypapertown.de) or a local git.mypapercloud.de-Account you are able to create.
+10 -5
View File
@@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" Icon="assets/icon.ico"
x:Class="Logof_Client.ResultWindow"
Title="Ergebnis">
<Grid Grid.ColumnDefinitions="200,*">
@@ -12,13 +12,18 @@
x:Name="LblResultCount" />
<StackPanel x:Name="StpFilterOptions" Orientation="Vertical" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Margin="10,80,10,0" />
<!-- <Button x:Name="BtnUpdateFilter" Content="Aktualisieren" HorizontalAlignment="Stretch" -->
<!-- VerticalAlignment="Bottom" Margin="10,0,10,10" Click="BtnUpdateFilter_OnClick" /> -->
<Button Content="Filter anwenden" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
x:Name="BtnExecuteFilter" Click="BtnExecuteFilter_OnClick"
Margin="10,10,10,50" />
<Button Content="Ausgewählte Anzeigen" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"
x:Name="BtnShwoSelected" Click="BtnShwoSelected_OnClick"
x:Name="BtnShowSelected" Click="BtnShowSelected_OnClick"
Margin="10,10,10,10" />
</Grid>
<DataGrid x:Name="DgResult" Grid.Column="1" AutoGenerateColumns="True" />
<ScrollViewer Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<!-- <TextBlock x:Name="TbResults"></TextBlock> -->
<ListBox x:Name="LbResults"></ListBox>
<!-- <StackPanel x:Name="StkResults" Orientation="Vertical" Margin="10" /> -->
</ScrollViewer>
</Grid>
</Window>
+183 -79
View File
@@ -1,41 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
namespace Logof_Client;
public partial class ResultWindow : Window
{
public List<CheckBox> errortypecheckboxes = new();
public KasAddressList ur_addresses = new();
public List<(int, List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>)> ur_result;
public KasAddressList ur_addresses = new("Ergebnis_" + DateTime.Now.ToString("ddMMyy_HHmmss"));
public List<KasPerson> ur_result;
public List<CheckBox> warningtypecheckboxes = new();
public ResultWindow(List<(int, List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>)> result,
KasAddressList ur_addresses)
public ResultWindow(List<KasPerson> result,
int addressSetID)
{
InitializeComponent();
ur_result = result;
this.ur_addresses = ur_addresses;
ur_addresses = ur_addresses;
Load(result);
//ViewSingle(200552426);
}
private void GenerateView(List<(int, List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>)> result)
private void GenerateView(List<KasPerson> result)
{
var errors = new List<KasPersonError>();
foreach (var single_result in result) errors.Add(new KasPersonError(single_result));
LblResultCount.Content = $"{errors.Count}/{ur_result.Count} Ergebnisse";
DgResult.ItemsSource = errors;
try
{
// Filter to only show persons with errors
var result_with_errors = result.Where(p => p.PersonError != null).ToList();
LblResultCount.Content = $"{result_with_errors.Count}/{ur_result.Count} Ergebnisse";
// TbResults.Text = "";
// foreach (var person in result_with_errors) TbResults.Text += person.PersonError.GetString()+"\n";
LbResults.Items.Clear();
foreach (var person in result_with_errors) LbResults.Items.Add(person.PersonError.ToString(person));
// StkResults.Children.Clear();
// foreach (var person in result_with_errors) StkResults.Children.Add(CreatePersonGrid(person));
}
catch (Exception ex)
{
Logger.Log("Error while generating result view: " + ex.Message, Logger.LogType.Warning);
}
}
private void ViewSingle(int refsid)
private Grid CreatePersonGrid(KasPerson person)
{
var grid = new Grid
{
ColumnDefinitions = ColumnDefinitions.Parse("100,*,100,*,100,*"),
RowDefinitions = RowDefinitions.Parse("Auto"),
Margin = new Thickness(0, 5, 0, 5),
Background = new SolidColorBrush(Color.Parse("#F0F0F0"))
};
// ID
grid.Children.Add(new TextBlock
{
Text = "id: ",
FontWeight = FontWeight.Bold, Margin = new Thickness(5)
});
grid.Children.Add(new TextBlock { Text = person.id.ToString(), Margin = new Thickness(5) });
Grid.SetColumn(grid.Children[1], 1);
// PLZ
grid.Children.Add(new TextBlock { Text = "plz:", FontWeight = FontWeight.Bold, Margin = new Thickness(5) });
Grid.SetColumn(grid.Children[2], 2);
grid.Children.Add(new TextBlock { Text = person.plz, Margin = new Thickness(5) });
Grid.SetColumn(grid.Children[3], 3);
// PPLZ
grid.Children.Add(new TextBlock { Text = "errors:", FontWeight = FontWeight.Bold, Margin = new Thickness(5) });
Grid.SetColumn(grid.Children[4], 4);
grid.Children.Add(new TextBlock { Text = person.PersonError.GetString(), Margin = new Thickness(5) });
Grid.SetColumn(grid.Children[5], 5);
return grid;
}
private void ViewSingle(int id)
{
foreach (var result in ur_addresses.KasPersons)
if (result.refsid == refsid)
if (result.id == id)
{
var wind = new Window();
var stp = new StackPanel();
@@ -44,8 +95,8 @@ public partial class ResultWindow : Window
var tb = new TextBlock();
var tb2 = new TextBlock();
tb.Text =
"refsid:\nanrede:\ntitel:\nvorname:\nadel:\nname:\nnamezus:\nanredzus:\nstrasse:\nstrasse2:\nplz:\nort:\nland:\npplz:\npostfach:\nname1:\nname2:\nname3:\nname4:\nname5:\nfunktion:\nfunktion2:\nabteilung:\nfunktionad:";
tb2.Text = result.refsid + "\n" + result.anrede + "\n" + result.titel + "\n" + result.vorname + "\n" +
"id:\nanrede:\ntitel:\nvorname:\nadel:\nname:\nnamezus:\nanredzus:\nstrasse:\nstrasse2:\nplz:\nort:\nland:\npplz:\npostfach:\nname1:\nname2:\nname3:\nname4:\nname5:\nfunktion:\nfunktion2:\nabteilung:\nfunktionad:";
tb2.Text = result.id + "\n" + result.anrede + "\n" + result.titel + "\n" + result.vorname + "\n" +
result.adel + "\n" + result.name + "\n" + result.namezus + "\n" + result.anredzus + "\n" +
result.strasse + "\n" + result.strasse2 + "\n" + result.plz + "\n" + result.ort + "\n" +
result.land + "\n" + result.pplz + "\n" + result.postfach + "\n" + result.name1 + "\n" +
@@ -62,44 +113,54 @@ public partial class ResultWindow : Window
}
}
private void Load(List<(int, List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>)> result)
private void Load(List<KasPerson> result)
{
var knownErrors = new List<AddressCheck.ErrorTypes>();
var knownWarnings = new List<AddressCheck.WarningTypes>();
foreach (var single_result in result)
try
{
foreach (var errtyp in single_result.Item2)
if (!knownErrors.Contains(errtyp))
knownErrors.Add(errtyp);
var knownErrors = new List<AddressCheck.ErrorTypes>();
var knownWarnings = new List<AddressCheck.WarningTypes>();
foreach (var wartyp in single_result.Item3)
if (!knownWarnings.Contains(wartyp))
knownWarnings.Add(wartyp);
foreach (var person in result)
{
if (person.PersonError == null) continue;
foreach (var errtyp in person.PersonError.errors)
if (!knownErrors.Contains(errtyp))
knownErrors.Add(errtyp);
foreach (var wartyp in person.PersonError.warnings)
if (!knownWarnings.Contains(wartyp))
knownWarnings.Add(wartyp);
}
foreach (var errtype in knownErrors)
{
var cb = new CheckBox();
cb.IsChecked = true;
cb.Content = errtype.ToString();
//cb.Click += (sender, e) => UpdateFilter();
errortypecheckboxes.Add(cb);
StpFilterOptions.Children.Add(cb);
}
foreach (var wartype in knownWarnings)
{
var cb = new CheckBox();
cb.IsChecked = true;
cb.Content = wartype.ToString();
//cb.Click += (sender, e) => UpdateFilter();
warningtypecheckboxes.Add(cb);
StpFilterOptions.Children.Add(cb);
}
GenerateView(result);
}
foreach (var errtype in knownErrors)
catch (Exception ex)
{
var cb = new CheckBox();
cb.IsChecked = true;
cb.Content = errtype.ToString();
cb.Click += (sender, e) => UpdateFilter();
errortypecheckboxes.Add(cb);
StpFilterOptions.Children.Add(cb);
Logger.Log("Error while showing naming window: " + ex.Message, Logger.LogType.Warning);
}
foreach (var wartype in knownWarnings)
{
var cb = new CheckBox();
cb.IsChecked = true;
cb.Content = wartype.ToString();
cb.Click += (sender, e) => UpdateFilter();
warningtypecheckboxes.Add(cb);
StpFilterOptions.Children.Add(cb);
}
GenerateView(result);
}
private void BtnUpdateFilter_OnClick(object? sender, RoutedEventArgs e)
@@ -108,49 +169,92 @@ public partial class ResultWindow : Window
private void UpdateFilter()
{
var temp_result = new List<(int, List<AddressCheck.ErrorTypes>, List<AddressCheck.WarningTypes>)>();
var checked_types = new List<AddressCheck.ErrorTypes>();
var checked_types_war = new List<AddressCheck.WarningTypes>();
try
{
var temp_result = new List<KasPerson>();
var checkedErrors = new HashSet<AddressCheck.ErrorTypes>();
var checkedWarnings = new HashSet<AddressCheck.WarningTypes>();
// safer parsing: use TryParse and trim the Content string
foreach (var cb in errortypecheckboxes)
if (cb.IsChecked == true)
checked_types.Add(
(AddressCheck.ErrorTypes)Enum.Parse(typeof(AddressCheck.ErrorTypes), cb.Content.ToString()));
{
var s = cb.Content?.ToString()?.Trim();
if (!string.IsNullOrEmpty(s) &&
Enum.TryParse<AddressCheck.ErrorTypes>(s, true, out var et))
checkedErrors.Add(et);
}
foreach (var cb in warningtypecheckboxes)
if (cb.IsChecked == true)
checked_types_war.Add(
(AddressCheck.WarningTypes)Enum.Parse(typeof(AddressCheck.WarningTypes), cb.Content.ToString()));
{
var s = cb.Content?.ToString()?.Trim();
if (!string.IsNullOrEmpty(s) &&
Enum.TryParse<AddressCheck.WarningTypes>(s, true, out var wt))
checkedWarnings.Add(wt);
}
foreach (var sres in ur_result)
// If no checkboxes are selected, show all persons with errors (default behavior)
if (checkedErrors.Count == 0 && checkedWarnings.Count == 0)
temp_result = ur_result.Where(p => p.PersonError != null).ToList();
else
foreach (var person in ur_result)
{
if (person.PersonError == null) continue;
var personErrors = person.PersonError.errors ?? Enumerable.Empty<AddressCheck.ErrorTypes>();
var personWarnings = person.PersonError.warnings ?? Enumerable.Empty<AddressCheck.WarningTypes>();
var matchesError = false;
var matchesWarning = false;
// only test errors if the user selected error-types
if (checkedErrors.Count > 0)
matchesError = personErrors.Any(err => checkedErrors.Contains(err));
// only test warnings if the user selected warning-types
if (checkedWarnings.Count > 0)
matchesWarning = personWarnings.Any(war => checkedWarnings.Contains(war));
// If at least one category matches (OR across categories), include person
if ((matchesError || matchesWarning) && !temp_result.Contains(person))
temp_result.Add(person);
}
LblResultCount.Content = $"{temp_result.Count}/{ur_result.Count} Ergebnisse";
LbResults.Items.Clear();
foreach (var person in temp_result) LbResults.Items.Add(person.PersonError.ToString(person));
// TbResults.Text = "";
// foreach (var person in temp_result) TbResults.Text += person.PersonError.GetString() +"\n";
// StkResults.Children.Clear();
// foreach (var person in temp_result) StkResults.Children.Add(CreatePersonGrid(person));
} catch (Exception ex)
{
foreach (var err in sres.Item2)
if (checked_types.Contains(err) && !temp_result.Contains(sres))
temp_result.Add(sres);
foreach (var war in sres.Item3)
if (checked_types_war.Contains(war) && !temp_result.Contains(sres))
temp_result.Add(sres);
Logger.Log("Error while updating filter: " + ex.Message, Logger.LogType.Warning);
}
var errors = new List<KasPersonError>();
foreach (var single_result in temp_result) errors.Add(new KasPersonError(single_result));
LblResultCount.Content = $"{errors.Count}/{ur_result.Count} Ergebnisse";
DgResult.ItemsSource = errors;
}
private void BtnShwoSelected_OnClick(object? sender, RoutedEventArgs e)
private void BtnShowSelected_OnClick(object? sender, RoutedEventArgs e)
{
foreach (var selected in DgResult.SelectedItems)
try
{
var _asKas = (KasPersonError)selected;
ViewSingle(_asKas.refsid);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
// foreach (var selected in DgResult.SelectedItems)
// try
// {
// var _asKas = (KasPerson)selected;
// ViewSingle(_asKas.id);
// }
// catch (Exception ex)
// {
// Console.WriteLine(ex.Message);
// }
}
private void BtnExecuteFilter_OnClick(object? sender, RoutedEventArgs e)
{
Console.WriteLine("Updating filter...");
UpdateFilter();
}
}
+12
View File
@@ -0,0 +1,12 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="400" Width="400" MinWidth="400" MaxWidth="400" d:DesignHeight="400"
Height="400" MinHeight="400" MaxHeight="400" Icon="assets/icon.ico" SystemDecorations="None"
x:Class="Logof_Client.StartupWindow" Title="Verarbeitung läuft...">
<Grid>
<Label Content="Hello, World!" />
<ProgressBar x:Name="PbLoading" Minimum="0" Maximum="100" />
</Grid>
</Window>
+13
View File
@@ -0,0 +1,13 @@
using Avalonia.Controls;
namespace Logof_Client;
public partial class StartupWindow : Window
{
public StartupWindow()
{
InitializeComponent();
}
}
+278
View File
@@ -0,0 +1,278 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Threading;
namespace Logof_Client;
public class AddressCheck
{
public enum ErrorTypes
{
PlzNotUsable,
PPlzNotUsable,
MayBeSameAddress,
NoPLZorPPLZ
}
public enum WarningTypes
{
NoCity,
NoStreet,
NoLastName,
NoFirstName,
NoStreetNumber,
NoPLZ,
NoPPLZ,
DoubledRefsid,
FullAddressTooLong
}
private readonly ProgressWindow _progress;
public AddressCheck(ProgressWindow progressWindow)
{
_progress = progressWindow;
}
public async Task<List<KasPerson>> Perform(int id)
{
try
{
// Find the index of the address set with the given id
var adset_index = -1;
for (var i = 0; i < Settings._instance.addressSets.addresses.Count; i++)
if (Settings._instance.addressSets.addresses[i].ID == id)
{
adset_index = i;
break;
}
if (adset_index == -1) return new List<KasPerson>();
var adset = Settings._instance.addressSets.addresses[adset_index];
var total = adset.KasPersons.Count;
var current = 0;
await Task.Run(async () =>
{
foreach (var person in adset.KasPersons)
{
var errors = new List<ErrorTypes>();
var warnings = new List<WarningTypes>();
var hasFaults = false;
var address_component_count = 2; // cause anrede and name are first
// PLZ-Prüfung
if (person.plz == "" || person.plz == null)
{
hasFaults = true;
warnings.Add(WarningTypes.NoPLZ);
}
else
{
if (!AddressCreator.CheckPLZ(person.plz, person.land))
{
hasFaults = true;
errors.Add(ErrorTypes.PlzNotUsable);
}
// if ((person.plz < 10000 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.plz < 10000 && person.land == "GER") ||
// (person.plz < 10000 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PlzTooShort);
// }
// else if ((person.plz > 99999 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.plz > 99999 && person.land == "GER") ||
// (person.plz > 99999 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PlzTooLong);
// }
}
// PPLZ-Prüfung
if (person.pplz == "" || person.pplz == null)
{
hasFaults = true;
warnings.Add(WarningTypes.NoPPLZ);
}
else
{
if (!AddressCreator.CheckPLZ(person.pplz, person.land))
{
hasFaults = true;
errors.Add(ErrorTypes.PPlzNotUsable);
}
// if ((person.pplz < 10000 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.pplz < 10000 && person.land == "GER") ||
// (person.pplz < 10000 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PPlzTooShort);
// }
// else if ((person.pplz > 99999 && string.IsNullOrWhiteSpace(person.land)) ||
// (person.pplz > 99999 && person.land == "GER") ||
// (person.pplz > 99999 && person.land == "DE"))
// {
// hasFaults = true;
// errors.Add(ErrorTypes.PPlzTooLong);
// }
}
if (warnings.Contains(WarningTypes.NoPLZ) && warnings.Contains(WarningTypes.NoPPLZ))
{
hasFaults = true;
errors.Add(ErrorTypes.NoPLZorPPLZ);
}
// Ort-Prüfung
if (string.IsNullOrWhiteSpace(person.ort))
{
hasFaults = true;
warnings.Add(WarningTypes.NoCity);
}
else
{
address_component_count++;
}
// Street-Number
var street = person.strasse.ToCharArray();
var intcount = 0;
foreach (var c in street)
{
int maybe;
if (int.TryParse(c.ToString(), out maybe)) intcount++;
}
if (intcount == 0)
{
hasFaults = true;
warnings.Add(WarningTypes.NoStreetNumber);
}
// Last-Name
if (string.IsNullOrWhiteSpace(person.name))
{
hasFaults = true;
warnings.Add(WarningTypes.NoLastName);
}
// First-Name
if (string.IsNullOrWhiteSpace(person.vorname))
{
hasFaults = true;
warnings.Add(WarningTypes.NoFirstName);
}
// Street-Check
if (string.IsNullOrWhiteSpace(person.strasse))
{
hasFaults = true;
warnings.Add(WarningTypes.NoStreet);
}
else
{
address_component_count++;
}
// Address-Component-Count
if (!string.IsNullOrWhiteSpace(person.strasse2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.land)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name1)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name3)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name4)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.name5)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktion)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktion2)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.funktionad)) address_component_count++;
if (!string.IsNullOrWhiteSpace(person.abteilung)) address_component_count++;
// Double-Refsid or DoubleAddresses
foreach (var person2 in adset.KasPersons)
{
if (adset.KasPersons.IndexOf(person) == adset.KasPersons.IndexOf(person2)) continue;
if (person.refsid == person2.refsid) // trifft auf Patch-Addressen nicht zu
{
hasFaults = true;
warnings.Add(WarningTypes.DoubledRefsid);
}
if (person.name == person2.name &&
person.strasse == person2.strasse &&
person.vorname == person2.vorname &&
person.ort == person2.ort &&
person.funktion == person2.funktion &&
person.funktion2 == person2.funktion2 &&
person.funktionad == person2.funktionad &&
person.abteilung == person2.abteilung &&
person.name1 == person2.name1 &&
person.name2 == person2.name2 &&
person.name3 == person2.name3 &&
person.name4 == person2.name4 &&
person.name5 == person2.name5) //
{
hasFaults = true;
errors.Add(ErrorTypes.MayBeSameAddress);
}
}
// Adressen-Länge
if (address_component_count > 10)
{
hasFaults = true;
warnings.Add(WarningTypes.FullAddressTooLong);
}
if (hasFaults)
lock (Settings._instance.addressSets.addresses)
{
// Directly set PersonError in the address set
var person_index = adset.KasPersons.IndexOf(person);
if (person_index >= 0)
Settings._instance.addressSets.addresses[adset_index].KasPersons[person_index]
.PersonError =
new KasPersonError((errors, warnings));
}
// Fortschritt aktualisieren
Interlocked.Increment(ref current);
var percent = current / (double)total * 100;
await Dispatcher.UIThread.InvokeAsync(() =>
{
if (hasFaults)
_progress.AddToLog($"Person mit id {person.id} ist fehlerhaft",
Convert.ToInt32(percent).ToString());
_progress.ChangePercentage(percent);
});
}
});
Settings.Save();
// Return only the persons with errors from the address set
return Settings._instance.addressSets.addresses[adset_index].KasPersons
.Where(p => p.PersonError != null)
.ToList();
}
catch (Exception ex)
{
Logger.Log($"Error while performing address check: {ex.Message}", Logger.LogType.Error);
}
return null;
}
}
+280
View File
@@ -0,0 +1,280 @@
using System;
using System.Linq;
namespace Logof_Client;
public static class AddressCreator
{
//+++ Aufbau +++
//
// Von unten anfangen, max. 7 Zeilen
//
// + Wenn Land nicht Deutschland Abbildung von Land (Fettgedruckt)
//
// Alternative A (wenn PPLZ + Ort ausgefüllt):
// + Abbildung PPLZ + Ort - Ort ggf. Abschneiden, wenn länger als eine Zeile … das längste was ich finden konnte: „Giugliano in Campania-Lago Patra“ (passt exakt auf eine Zeile)
// + Abbildung Postfach wenn leer, dann Straße
// + Abbildung Wenn Anredezusatz nicht leer, Anredezusatz, sonst Anrede + Titel + Vorname + Adel + Name + Namenszusatz in Klammern
// + Abbildung Name 1 + Name 2 + Name 3 + Name 4 + Name 5 + Abteilung (insgesamt max. 7 Zeilen)
//
// ansonsten
//
// + Abbildung PLZ + Ort - Ort ggf. Abschneiden, wenn länger als eine Zeile … das längste was ich finden konnte: „Giugliano in Campania-Lago Patra“ (passt exakt auf eine Zeile)
// + Abbildung Straße wenn Straße leer, dann Postfach
// + Abbildung Wenn Anredezusatz nicht leer, Anredezusatz, sonst Anrede + Titel + Vorname + Adel + Name + Namenszusatz in Klammern
// + Abbildung Name 1 + Name 2 + Name 3 + Name 4 + Name 5 + Abteilung (insgesamt max. 7 Zeilen)
//
// Auswurf Fehler-Datei
//
// + wenn keine PLZ und/oder kein Ort -> Fehler
// + wenn kein Name 1-5 und/oder Name -> Fehler
//
// Auswurf CSV-Datei (Komma/TAB, UTF-8, ISO…)
//
// Auswurf PDF mit normalen Absender
//
// Auswurf PDF mit PvSt.
/// <summary>
/// Creates max-seven-lines-long Markdown address-string. Analyzes the KasPerson-Instance to find the best result.
/// </summary>
/// <param name="refsid">KasPerson-ID</param>
/// <returns>A Markdown string with the address that is maximum seven lines long</returns>
public static string? CreateFinalMarkdownString(int id)
{
// Maximum seven lines of information
try
{
// find the address
KasPerson? address = null;
var string_address = "";
var address_line_count = 0;
foreach (var set in Settings._instance.addressSets.addresses)
{
var temp = set.KasPersons.FirstOrDefault(obj => obj.id == id);
if (temp != null)
{
address = temp;
break;
}
}
// no address found
if (address == null) return null;
// let's get started: we start from the bottom
// if the country is not Germany, set it; try to map via Global countries alternatives -> translation
var trimmedLand = (address.land ?? "").Trim();
var trimmedLowerLand = trimmedLand.ToLower();
var isGermany = trimmedLowerLand == "germany" || trimmedLowerLand == "ger" ||
trimmedLowerLand == "" || trimmedLowerLand == "de" ||
trimmedLowerLand == "deutschland";
if (!isGermany)
{
var countryToShow = trimmedLand; // default: use raw land value
if (!string.IsNullOrEmpty(trimmedLand))
// search for matching country via alternatives using for-loops
for (var ci = 0; ci < Global._instance.countries.Count; ci++)
{
var country = Global._instance.countries[ci];
for (var ai = 0; ai < country.alternatives.Count; ai++)
try
{
var alt = (country.alternatives[ai] ?? "").Trim();
if (string.Equals(alt, trimmedLand, StringComparison.OrdinalIgnoreCase))
{
countryToShow = string.IsNullOrWhiteSpace(country.translation)
? country.name
: country.translation;
goto CountryFound;
}
}
catch
{
// ignore malformed alternative
}
}
CountryFound:
string_address = "**" + countryToShow + "**"; // Needs to be bold
address_line_count++;
}
// Alternative A: pplz valid and city existing
if (!string.IsNullOrEmpty(address.ort) && CheckPLZ(address.pplz, address.land))
{
string_address = address.pplz + " " + address.ort + "\n" + string_address;
address_line_count++;
if (!string.IsNullOrWhiteSpace(address.postfach))
{
string_address = "Postfach " + address.postfach.Trim() + "\n" + string_address;
address_line_count++;
}
else if (!string.IsNullOrWhiteSpace(address.strasse.Trim()))
{
string_address = address.strasse.Trim() + "\n" + string_address;
address_line_count++;
}
var nameline = CreateNameLine(address.anredzus, address.anrede, address.titel, address.vorname,
address.adel, address.name, address.namezus);
if (!string.IsNullOrWhiteSpace(nameline))
{
string_address = nameline + "\n" + string_address;
address_line_count++;
}
// REIHENFOLGE
var nameattribs = new[]
{ address.name1, address.name2, address.name3, address.name4, address.name5, address.abteilung };
var names = "";
for (var i = 0; i < nameattribs.Length; i++)
try
{
if (address_line_count >= 7) break;
if (!string.IsNullOrWhiteSpace(nameattribs[i]))
{
names += "\n" + nameattribs[i];
address_line_count++;
}
}
catch
{
Console.WriteLine("ERROR 15821");
}
string_address = names + "\n" + string_address;
} // Alternative B: plz valid and city existing
else if (!string.IsNullOrEmpty(address.ort) && CheckPLZ(address.plz, address.land))
{
string_address = address.plz + " " + address.ort + "\n" + string_address;
address_line_count++;
if (!string.IsNullOrWhiteSpace(address.strasse))
{
string_address = address.strasse.Trim() + "\n" + string_address;
address_line_count++;
}
else if (!string.IsNullOrWhiteSpace(address.postfach.Trim()))
{
string_address = "Postfach " + address.postfach.Trim() + "\n" + string_address;
address_line_count++;
}
var nameline = CreateNameLine(address.anredzus, address.anrede, address.titel, address.vorname,
address.adel, address.name, address.namezus);
if (!string.IsNullOrWhiteSpace(nameline))
{
string_address = nameline + "\n" + string_address;
address_line_count++;
}
var nameattribs = new[]
{ address.name1, address.name2, address.name3, address.name4, address.name5, address.abteilung };
var names = "";
for (var i = 0; i < nameattribs.Length; i++)
try
{
if (address_line_count >= 7) break;
if (!string.IsNullOrWhiteSpace(nameattribs[i]))
{
names += "\n" + nameattribs[i];
address_line_count++;
}
}
catch
{
Console.WriteLine("ERROR 15821");
}
string_address = names + "\n" + string_address;
} // Error-Handling?
if (address_line_count > 1) return string_address;
return null;
}
catch (Exception ex)
{
Logger.Log($"Error while creating markdown string: {ex.Message}", Logger.LogType.Error);
}
return null;
}
public static string CreateNameLine(string anredezus, string anrede, string titel, string vorname, string adel,
string name, string namezus)
{
try
{
if (!string.IsNullOrWhiteSpace(anredezus))
return string.Join(" ",
new[] { anredezus, titel, vorname, adel, name }
.Where(s => !string.IsNullOrWhiteSpace(s))
)
+ (string.IsNullOrWhiteSpace(namezus) ? "" : $" ({namezus.Trim()})");
// else
return string.Join(" ",
new[] { anrede, titel, vorname, adel, name }
.Where(s => !string.IsNullOrWhiteSpace(s))
)
+ (string.IsNullOrWhiteSpace(namezus) ? "" : $" ({namezus.Trim()})");
}
catch (Exception ex)
{
Logger.Log($"Error while performing address name line creation: {ex.Message}", Logger.LogType.Error);
}
return null;
}
/// <summary>
/// Returns true if a plz (or pplz) is valid
/// </summary>
/// <param name="plz">the plz itself</param>
/// <param name="land">country, to check the plz</param>
/// <returns>true or faslse, depending on result :+1:</returns>
public static bool CheckPLZ(string plz, string land)
{
try
{
if (string.IsNullOrWhiteSpace(plz)) return false;
var trimmedPlz = plz.Trim();
var trimmedLand = land.ToLower().Trim();
// Check if it's a German country code
var isGermany = trimmedLand == "germany" || trimmedLand == "ger" || trimmedLand == "de" ||
trimmedLand == "deutschland" || trimmedLand == "";
if (isGermany)
// For Germany (including empty land), accept numeric postal codes with 5 digits
try
{
var iplz = Convert.ToInt32(trimmedPlz);
if (trimmedPlz.Length == 5) return true;
return false;
}
catch
{
return false;
}
// For non-German countries, accept any non-empty postal code
return true;
}
catch (Exception ex)
{
Logger.Log($"Error while performing address plz check: {ex.Message}", Logger.LogType.Error);
}
return false;
}
}
+140
View File
@@ -0,0 +1,140 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Logof_Client;
public class AddressPatch
{
public string refsid_is { get; set; } = "";
public string anrede_is { get; set; } = "";
public string titel_is { get; set; } = "";
public string vorname_is { get; set; } = "";
public string adel_is { get; set; } = "";
public string name_is { get; set; } = "";
public string namezus_is { get; set; } = "";
public string anredzus_is { get; set; } = "";
public string strasse_is { get; set; } = "";
public string strasse2_is { get; set; } = "";
public string plz_is { get; set; } = "";
public string ort_is { get; set; } = "";
public string land_is { get; set; } = "";
public string pplz_is { get; set; } = "";
public string postfach_is { get; set; } = "";
public string name1_is { get; set; } = "";
public string name2_is { get; set; } = "";
public string name3_is { get; set; } = "";
public string name4_is { get; set; } = "";
public string name5_is { get; set; } = "";
public string funktion_is { get; set; } = "";
public string funktion2_is { get; set; } = "";
public string abteilung_is { get; set; } = "";
public string funktionad_is { get; set; } = "";
public bool has_refsid { get; set; }
public bool has_anrede { get; set; }
public bool has_titel { get; set; }
public bool has_vorname { get; set; }
public bool has_adel { get; set; }
public bool has_name { get; set; }
public bool has_namezus { get; set; }
public bool has_anredzus { get; set; }
public bool has_strasse { get; set; }
public bool has_strasse2 { get; set; }
public bool has_plz { get; set; }
public bool has_ort { get; set; }
public bool has_land { get; set; }
public bool has_pplz { get; set; }
public bool has_postfach { get; set; }
public bool has_name1 { get; set; }
public bool has_name2 { get; set; }
public bool has_name3 { get; set; }
public bool has_name4 { get; set; }
public bool has_name5 { get; set; }
public bool has_funktion { get; set; }
public bool has_funktion2 { get; set; }
public bool has_abteilung { get; set; }
public bool has_funktionad { get; set; }
public static AddressPatch Import(Uri filename)
{
try
{
var patch = new AddressPatch();
// Alle Zeilen aus der Datei laden
var lines = File.ReadAllLines(filename.LocalPath);
// Alle Properties der Klasse (Strings und bools)
var properties = typeof(AddressPatch).GetProperties(BindingFlags.Public | BindingFlags.Instance);
// Nur die Properties, die mit _is enden (also die String-Werte)
var stringProps = properties.Where(p => p.PropertyType == typeof(string) && p.Name.EndsWith("_is"));
foreach (var prop in stringProps)
{
// Beispiel: prop.Name = "name_is"
var baseName = prop.Name.Substring(0, prop.Name.Length - 3); // "name"
// In der Datei wird nach "name:" gesucht (ohne _is)
var line = lines.FirstOrDefault(l => l.StartsWith(baseName + ":"));
if (line != null)
{
// Wert extrahieren (alles nach dem Doppelpunkt)
var value = line.Substring(line.IndexOf(':') + 1).Trim();
// Wert im Patch-Objekt setzen
prop.SetValue(patch, value);
// Passendes has_ Feld aktivieren, z.B. "has_name"
var hasProp = properties.FirstOrDefault(p => p.Name == "has_" + baseName);
if (hasProp != null && hasProp.PropertyType == typeof(bool)) hasProp.SetValue(patch, true);
}
}
return patch;
}
catch (Exception ex)
{
Logger.Log($"Error while importing address patch: {ex.Message}", Logger.LogType.Error);
}
return null;
}
public override string ToString()
{
try
{
var properties = GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
var stringProps = properties.Where(p => p.PropertyType == typeof(string) && p.Name.EndsWith("_is"));
var lines = new StringBuilder();
foreach (var prop in stringProps)
{
// passendes has_ Feld
var hasProp =
properties.FirstOrDefault(p => p.Name == "has_" + prop.Name.Substring(0, prop.Name.Length - 3));
if (hasProp != null && (bool)hasProp.GetValue(this))
{
var value = (string)prop.GetValue(this);
lines.AppendLine($"{prop.Name} => {value}");
}
}
return lines.ToString().TrimEnd();
}
catch (Exception ex)
{
Logger.Log($"Error while parsing: {ex.Message}", Logger.LogType.Error);
}
return "Error while parsing";
}
}
+9 -7
View File
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace Logof_Client;
@@ -9,15 +10,16 @@ public class AddressRepair(ProgressWindow progressWindow)
public KasAddressList Perform(KasAddressList all_addresses,
List<(int, List<AddressCheck.ErrorTypes>)> failed_addresses)
{
foreach (var k in all_addresses.KasPersons)
foreach (var p in failed_addresses)
try
{
if (k.refsid != p.Item1) continue;
if (p.Item2.Contains(AddressCheck.ErrorTypes.DoubledRefsid))
{
}
}
catch (Exception ex)
{
Logger.Log($"Error while performing address repair: {ex.Message}", Logger.LogType.Error);
}
return null;
return null;
}
+84
View File
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
namespace Logof_Client;
public class AddressShortener(ProgressWindow progressWindow)
{
private readonly ProgressWindow _progress = progressWindow;
public async Task Perform(KasAddressList list)
{
try
{
List<int> doubled_ids = new List<int>();
for (int i = 0; i < list.KasPersons.Count; i++)
{
var address = list.KasPersons[i];
for (int j = 0; j < list.KasPersons.Count; j++)
{
if (i == j) continue;
var sec_address = list.KasPersons[j];
if (address.refsid == sec_address.refsid && !doubled_ids.Contains(address.refsid) && address.refsid != 0)
{
doubled_ids.Add(address.refsid);
}
}
}
// delete doubled addresses by refsid
foreach (int id in doubled_ids)
{
// does this remove both of the doubled addresses?
list.KasPersons.Remove(list.KasPersons.FirstOrDefault(x => x.refsid == id));
}
List<int> toRemove = new List<int>();
foreach (var address in list.KasPersons)
{
try
{
if (address.PersonError.errors.Contains(AddressCheck.ErrorTypes.NoPLZorPPLZ))
{
toRemove.Add(address.id);
}
else if (address.PersonError.errors.Contains(AddressCheck.ErrorTypes.PlzNotUsable) &&
address.PersonError.errors.Contains(AddressCheck.ErrorTypes.PPlzNotUsable))
{
toRemove.Add(address.id);
} else if (address.PersonError.errors.Contains(AddressCheck.ErrorTypes.PlzNotUsable) &&
address.PersonError.warnings.Contains(AddressCheck.WarningTypes.NoPPLZ))
{
toRemove.Add(address.id);
}
else if (address.PersonError.errors.Contains(AddressCheck.ErrorTypes.PPlzNotUsable) &&
address.PersonError.warnings.Contains(AddressCheck.WarningTypes.NoPLZ))
{
toRemove.Add(address.id);
}
}
catch
{
Console.WriteLine("PersonError not accessible: " + address.id);
}
}
// delete doubled addresses by refsid
foreach (int id in toRemove)
{
// does this remove both of the doubled addresses?
list.KasPersons.Remove(list.KasPersons.Find(x => x.id == id));
}
}
catch (Exception ex)
{
Logger.Log($"Error while performing address shortener: {ex.Message}", Logger.LogType.Error);
}
}
}
+365
View File
@@ -0,0 +1,365 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Threading;
namespace Logof_Client;
public class CombineAddresses
{
private readonly ProgressWindow _progress;
public CombineAddresses(ProgressWindow progressWindow)
{
_progress = progressWindow;
}
public enum CombineType
{
refsid,
final_adress,
none
}
public async Task<(KasAddressList, KasAddressList)> Perform(List<KasAddressList> address_lists, string type, CombineType comb_type,
bool? exportUnused, bool? deleteOld)
{
try
{
var result = await Execute(address_lists,type,comb_type,exportUnused);
if (deleteOld == true)
{
foreach (var list in address_lists)
{
Settings._instance.addressSets.addresses.Remove(list);
}
}
return result;
}
catch (Exception ex)
{
Logger.Log($"Error while performing address combining: {ex.Message}", Logger.LogType.Error);
}
return (null,null);
}
private async Task<(KasAddressList, KasAddressList)> Execute(List<KasAddressList> address_lists, string type, CombineType comb_type,
bool? exportUnused)
{
try
{
if (type == "difference") return await Difference(address_lists, comb_type, exportUnused);
if (type == "union") return await Union(address_lists, comb_type, exportUnused);
if (type == "intersection") return await Intersection(address_lists, comb_type, exportUnused);
if (type == "symdiff") return await SymmetricDifference(address_lists, comb_type, exportUnused);
return (null, null);
}
catch (Exception ex)
{
Logger.Log($"Error while executing address combining: {ex.Message}", Logger.LogType.Error);
}
return (null,null);
}
/// <summary>
/// Returns true if the addresses are the same.
/// </summary>
/// <param name="first">First address to compare</param>
/// <param name="second">Second address to compare</param>
/// <param name="only_refsid">If true, only a refsid-check will be done</param>
/// <returns></returns>
public bool CompareAddresses(KasPerson first, KasPerson second, CombineType comb_type)
{
// A refsid of 0 means "missing", so it must not collapse unrelated entries.
if(comb_type == CombineType.refsid)
if (first.refsid != 0 && second.refsid != 0 && first.refsid == second.refsid) return true;
if (comb_type == CombineType.final_adress)
if (first.name == second.name &&
first.anrede == second.anrede &&
first.anredzus == second.anredzus &&
first.namezus == second.namezus &&
first.titel == second.titel &&
first.adel == second.adel &&
first.strasse == second.strasse &&
first.strasse2 == second.strasse2 &&
first.vorname == second.vorname &&
first.ort == second.ort &&
first.land == second.land &&
first.plz == second.plz &&
first.pplz == second.pplz &&
first.funktion == second.funktion &&
first.funktion2 == second.funktion2 &&
first.funktionad == second.funktionad &&
first.abteilung == second.abteilung &&
first.postfach == second.postfach &&
first.name1 == second.name1 &&
first.name2 == second.name2 &&
first.name3 == second.name3 &&
first.name4 == second.name4 &&
first.name5 == second.name5)
return true;
return false;
}
public async Task<(KasAddressList, KasAddressList)> Difference(List<KasAddressList> address_lists, CombineType comb_type,
bool? return_unused,
Progress? progress = null)
{
try
{
if (address_lists == null || address_lists.Count == 0)
return (new KasAddressList(await KasAddressList.GenerateName("difference")), null);
progress ??= new Progress
{
TotalPersons = address_lists.Sum(l => l.KasPersons.Count),
ComparedPersons = 0
};
// Vereinigung aller Listen außer der ersten
var restUnion = new List<KasPerson>();
for (var i = 1; i < address_lists.Count; i++)
restUnion.AddRange(address_lists[i].KasPersons);
var result = new KasAddressList(await KasAddressList.GenerateName("difference"));
var second_result = new KasAddressList("none");
if(return_unused == true)
second_result = new KasAddressList(await KasAddressList.GenerateName("difference_rest", false));
foreach (var person in address_lists[0].KasPersons)
{
var isDouble = restUnion.Any(p => CompareAddresses(person, p, comb_type));
if (!isDouble)
result.KasPersons.Add(person);
else
second_result.KasPersons.Add(person);
progress.Increment();
if (progress.LogAction == null) continue;
var logMessage =
$"Person mit id {person.id} verglichen mit {restUnion.Count} Personen des Restes.";
await Dispatcher.UIThread.InvokeAsync(() => progress.LogAction?.Invoke(logMessage));
}
if (return_unused == true) return (result, second_result);
return (result, null);
}
catch (Exception ex)
{
Logger.Log($"Error while performing difference-combining: {ex.Message}", Logger.LogType.Error);
}
return (null,null);
}
public async Task<(KasAddressList, KasAddressList)> Union(List<KasAddressList> address_lists, CombineType comb_type, bool? return_unused,
Progress progress = null)
{
try
{
var result = new KasAddressList(await KasAddressList.GenerateName("union"));
var second_result = new KasAddressList("none");
if(return_unused == true)
second_result = new KasAddressList(await KasAddressList.GenerateName("union_rest", false));
if (address_lists == null || address_lists.Count == 0)
return (result, null);
var total = address_lists.Sum(l => l.KasPersons.Count);
var processed = 0;
foreach (var list in address_lists)
foreach (var person in list.KasPersons)
{
if (!result.KasPersons.Any(existing => CompareAddresses(existing, person, comb_type)))
result.KasPersons.Add(person);
else
second_result.KasPersons.Add(person);
processed++;
var percent = processed / (double)total * 100;
var logMessage =
$"{percent:F1}%: Person mit {person.id} hinzugefügt (aktuell {result.KasPersons.Count} Einträge)";
if (progress == null) continue;
if (Dispatcher.UIThread.CheckAccess())
progress.LogAction?.Invoke(logMessage);
else
Dispatcher.UIThread.Post(() => progress.LogAction?.Invoke(logMessage));
}
if (return_unused == true) return (result, second_result);
return (result, null);
}
catch (Exception ex)
{
Logger.Log($"Error while performing union-combining: {ex.Message}", Logger.LogType.Error);
}
return (null,null);
}
public async Task<KasAddressList> MoveDuplicatesToNew()
{
return null;
}
public async Task<(KasAddressList, KasAddressList)> Intersection(List<KasAddressList> address_lists, CombineType comb_type,
bool? return_unused, Progress progress = null)
{
try
{
var result = new KasAddressList(await KasAddressList.GenerateName("intersection"));
var second_result = new KasAddressList("none");
if(return_unused == true) second_result = new KasAddressList(await KasAddressList.GenerateName("intersection_rest", false));
if (address_lists == null || address_lists.Count == 0)
return (result, null);
// Nur die erste Liste als Ausgangspunkt verwenden
var baseList = address_lists[0];
var otherLists = address_lists.Skip(1).ToList();
var total = baseList.KasPersons.Count;
var processed = 0;
foreach (var person in baseList.KasPersons)
{
// Prüfen, ob Person in *allen* anderen Listen vorkommt
var isInAll = otherLists.All(list =>
list.KasPersons.Any(existing => CompareAddresses(existing, person, comb_type)));
if (isInAll)
result.KasPersons.Add(person);
else
second_result.KasPersons.Add(person);
processed++;
var percent = processed / (double)total * 100;
var logMessage =
$"{percent:F1}%: Person mit {person.id} geprüft {(isInAll ? "in allen enthalten" : "nicht überall vorhanden")}";
// Sicher und nicht blockierend loggen
if (progress != null)
{
if (Dispatcher.UIThread.CheckAccess())
progress.LogAction?.Invoke(logMessage);
else
Dispatcher.UIThread.Post(() => progress.LogAction?.Invoke(logMessage));
}
}
if (return_unused == true) return (result, second_result);
return (result, null);
}
catch (Exception ex)
{
Logger.Log($"Error while performing intersection-combining: {ex.Message}", Logger.LogType.Error);
}
return (null,null);
}
public async Task<(KasAddressList, KasAddressList)> SymmetricDifference(List<KasAddressList> address_lists, CombineType comb_type,
bool? return_unused, Progress progress = null)
{
try
{
var result = new KasAddressList(await KasAddressList.GenerateName("symmetric_difference"));
var second_result = new KasAddressList("none");
if(return_unused == true) second_result = new KasAddressList(await KasAddressList.GenerateName("symmetric_rest", false));
if (address_lists == null || address_lists.Count == 0)
return (result, null);
var total = address_lists.Sum(l => l.KasPersons.Count);
var processed = 0;
// Wir sammeln alle Personen + zählen, wie oft sie vorkommen
var allPersons = new List<(KasPerson person, int count)>();
foreach (var list in address_lists)
foreach (var person in list.KasPersons)
{
// Prüfen, ob schon vorhanden
var existing = allPersons.FirstOrDefault(p => CompareAddresses(p.person, person, comb_type));
if (existing.person != null)
{
// Falls schon drin → Vorkommen erhöhen
var index = allPersons.IndexOf(existing);
allPersons[index] = (existing.person, existing.count + 1);
}
else
{
// Neu hinzufügen
allPersons.Add((person, 1));
}
processed++;
var percent = processed / (double)total * 100;
var logMessage =
$"{percent:F1}%: Person mit {person.id} verarbeitet (Zwischengröße {allPersons.Count})";
if (progress != null)
{
if (Dispatcher.UIThread.CheckAccess())
progress.LogAction?.Invoke(logMessage);
else
Dispatcher.UIThread.Post(() => progress.LogAction?.Invoke(logMessage));
}
}
// Nur diejenigen übernehmen, die *genau einmal* vorkamen
foreach (var (person, count) in allPersons)
if (count == 1)
result.KasPersons.Add(person);
else
second_result.KasPersons.Add(person);
if (return_unused == true) return (result, second_result);
return (result, null);
}
catch (Exception ex)
{
Logger.Log($"Error while performing symdiff-combining: {ex.Message}", Logger.LogType.Error);
}
return (null,null);
}
}
public class Progress
{
public int TotalPersons { get; set; } // Gesamtzahl der zu prüfenden Personen
public int ComparedPersons { get; set; } // Schon verglichene Personen
public double Percentage => TotalPersons == 0 ? 0 : (double)ComparedPersons / TotalPersons * 100;
public Action<string>? LogAction { get; set; } // z.B. Dispatcher-UI-Callback
public void Increment()
{
var comparedPersons = ComparedPersons;
Interlocked.Increment(ref comparedPersons);
}
}
+86
View File
@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Logof_Client;
public class CsvBuilder
{
private readonly string Header;
private readonly List<object> Instances;
private readonly KasAddressList KasAddressList;
private readonly char Separator;
public CsvBuilder(string header, List<object> instances, char separator = ',')
{
Header = header;
Instances = instances;
Separator = separator;
}
public CsvBuilder(string header, KasAddressList instances, char separator = ',')
{
Header = header;
KasAddressList = instances;
Separator = separator;
}
public string? BuildKas()
{
var result = new StringBuilder();
result.AppendLine(Header);
foreach (var l in KasAddressList.KasPersons)
result.AppendLine(string.Join(Separator, new[]
{
EscapeCsvField(l.refsid.ToString()),
EscapeCsvField(l.anrede),
EscapeCsvField(l.titel),
EscapeCsvField(l.vorname),
EscapeCsvField(l.adel),
EscapeCsvField(l.name),
EscapeCsvField(l.namezus),
EscapeCsvField(l.anredzus),
EscapeCsvField(l.strasse),
EscapeCsvField(l.strasse2),
EscapeCsvField(l.plz),
EscapeCsvField(l.ort),
EscapeCsvField(l.land),
EscapeCsvField(l.pplz),
EscapeCsvField(l.postfach),
EscapeCsvField(l.name1),
EscapeCsvField(l.name2),
EscapeCsvField(l.name3),
EscapeCsvField(l.name4),
EscapeCsvField(l.name5),
EscapeCsvField(l.funktion),
EscapeCsvField(l.funktion2),
EscapeCsvField(l.abteilung),
EscapeCsvField(l.funktionad)
}));
// weitere Cases
return result.ToString();
}
private string EscapeCsvField(string? value)
{
try
{
var field = value ?? string.Empty;
var mustQuote = field.Contains(Separator) || field.Contains('"') || field.Contains('\r') || field.Contains('\n');
if (!mustQuote)
return field;
return "\"" + field.Replace("\"", "\"\"") + "\"";
}
catch (Exception ex)
{
Logger.Log($"Error while escapting csv field: {ex.Message}",Logger.LogType.Warning);
}
return "";
}
}
+310
View File
@@ -0,0 +1,310 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Logof_Client;
public class DataImport
{
public static async Task<(bool, KasAddressList)> ImportKasAddressList(Uri pathToCsv, AddressPatch patch = null,
char separator = ',')
{
if (patch == null)
return await ImportKasAddressListWithoutPatch(pathToCsv, separator);
return await ImportKasAddressListWithPatch(pathToCsv, patch, separator);
}
private static async Task<(bool, KasAddressList)> ImportKasAddressListWithoutPatch(Uri pathToCsv, char separator)
{
try
{
if (!File.Exists(pathToCsv.LocalPath))
{
Console.WriteLine($"File not found: {pathToCsv}");
return (false, null);
}
using var reader = new StreamReader(pathToCsv.LocalPath);
var headerLine = reader.ReadLine();
if (headerLine == null)
{
Console.WriteLine("File is empty.");
return (false, null);
}
var imported = new KasAddressList(
await KasAddressList.GenerateName(Path.GetFileNameWithoutExtension(pathToCsv.LocalPath)));
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line))
continue;
var parts = ParseCsvLine(line, separator);
if (parts.Length < 24)
{
Console.WriteLine($"Not enough columns in line: {line}");
continue;
}
try
{
var person = new KasPerson(KasPerson.GenerateNewID(imported.KasPersons.Count),
ParseInt(parts[0]),
parts[1],
parts[2],
parts[3],
parts[4],
parts[5],
parts[6],
parts[7],
parts[8],
parts[9],
parts[10],
parts[11],
parts[12],
parts[13],
parts[14],
parts[15],
parts[16],
parts[17],
parts[18],
parts[19],
parts[20],
parts[21],
parts[22],
parts[23]
);
imported.KasPersons.Add(person);
}
catch (Exception ex)
{
Logger.Log($"Error while creating new kas person (import): {ex.Message}",Logger.LogType.Error);
Console.WriteLine(ex.StackTrace);
return (false, null);
}
}
return (true, imported);
}
catch (Exception ex)
{
Logger.Log($"Error while importing kas address list without patch: {ex.Message}",Logger.LogType.Error);
}
return (false, null);
}
private static async Task<(bool, KasAddressList)> ImportKasAddressListWithPatch(Uri pathToCsv, AddressPatch patch,
char separator)
{
try
{
if (!File.Exists(pathToCsv.LocalPath))
{
Console.WriteLine($"File not found: {pathToCsv}");
return (false, null);
}
using var reader = new StreamReader(pathToCsv.LocalPath);
var headerLine = reader.ReadLine();
if (headerLine == null)
{
Console.WriteLine("File is empty.");
return (false, null);
}
var headers = ParseCsvLine(headerLine, separator);
var imported = new KasAddressList(
await KasAddressList.GenerateName(Path.GetFileNameWithoutExtension(pathToCsv.LocalPath)));
var patchType = typeof(AddressPatch);
var binding = BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase;
var hasProperties = patchType.GetProperties(binding)
.Where(p => p.PropertyType == typeof(bool) && p.Name.StartsWith("has_", StringComparison.OrdinalIgnoreCase))
.ToArray();
//var last_refsid = 1000000;
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line))
continue;
var parts = ParseCsvLine(line, separator);
var fieldValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
//var refsid_existing = false;
foreach (var hasProp in hasProperties)
{
var fieldName = hasProp.Name.Substring(4);
var hasObj = hasProp.GetValue(patch);
var has = hasObj is bool b && b;
var patchProp = patchType.GetProperty(fieldName + "_is", binding);
string desiredHeader = null;
if (has && patchProp != null)
desiredHeader = patchProp.GetValue(patch)?.ToString();
else
desiredHeader = fieldName;
var resolvedValue = "";
if (!string.IsNullOrEmpty(desiredHeader))
{
var idx = Array.FindIndex(headers,
h => string.Equals(h, desiredHeader, StringComparison.OrdinalIgnoreCase));
if (idx >= 0 && idx < parts.Length)
{
resolvedValue = parts[idx];
}
else
{
var altIdx = Array.FindIndex(headers, h =>
string.Equals(h, fieldName, StringComparison.OrdinalIgnoreCase) ||
string.Equals(h, fieldName + "_is", StringComparison.OrdinalIgnoreCase));
if (altIdx >= 0 && altIdx < parts.Length)
resolvedValue = parts[altIdx];
else
resolvedValue = "";
}
}
fieldValues[fieldName] = resolvedValue ?? "";
}
string GetField(string name)
{
return fieldValues.TryGetValue(name, out var v) ? v : "";
}
var refsid = 0;
if (patch.has_refsid)
refsid = ParseInt(GetField("refsid"));
try
{
var person = new KasPerson(KasPerson.GenerateNewID(imported.KasPersons.Count), refsid,
GetField("anrede"),
GetField("titel"),
GetField("vorname"),
GetField("adel"),
GetField("name"),
GetField("namezus"),
GetField("anredzus"),
GetField("strasse"),
GetField("strasse2"),
GetField("plz"),
GetField("ort"),
GetField("land"),
GetField("pplz"),
GetField("postfach"),
GetField("name1"),
GetField("name2"),
GetField("name3"),
GetField("name4"),
GetField("name5"),
GetField("funktion"),
GetField("funktion2"),
GetField("abteilung"),
GetField("funktionad")
);
imported.KasPersons.Add(person);
}
catch (Exception ex)
{
Logger.Log($"Error while creating kas person (import, patch): {ex.Message}",Logger.LogType.Error);
Console.WriteLine(ex.StackTrace);
return (false, null);
}
}
return (true, imported);
}
catch (Exception ex)
{
Logger.Log($"Error while importing kas address list with patch: {ex.Message}",Logger.LogType.Error);
}
// int GenerateNewRefsid()
// {
// var biggest = last_refsid;
// foreach (var set in Settings._instance.addressSets.addresses)
// foreach (var address in set.KasPersons)
// if (biggest < address.id)
// biggest = address.id + 1;
//
// last_refsid = biggest + 1;
// return last_refsid;
// }
return (false, null);
}
private static int ParseInt(string input)
{
return int.TryParse(input, out var result) ? result : 0;
}
private static string[] ParseCsvLine(string line, char separator)
{
try
{
var fields = new List<string>();
var current = new StringBuilder();
var inQuotes = false;
for (var i = 0; i < line.Length; i++)
{
var c = line[i];
if (c == '"')
{
if (inQuotes && i + 1 < line.Length && line[i + 1] == '"')
{
current.Append('"');
i++;
continue;
}
inQuotes = !inQuotes;
continue;
}
if (c == separator && !inQuotes)
{
fields.Add(current.ToString().Trim());
current.Clear();
continue;
}
current.Append(c);
}
fields.Add(current.ToString().Trim());
return fields.ToArray();
}
catch (Exception ex)
{
Logger.Log($"Error while persing csv line: {ex.Message}",Logger.LogType.Error);
}
return [];
}
}
+483
View File
@@ -0,0 +1,483 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using PdfSharp;
using PdfSharp.Drawing;
using PdfSharp.Fonts;
using PdfSharp.Pdf;
namespace Logof_Client;
public class PdfBuilder
{
private readonly PdfExportSettings _settings;
private readonly XFont _boldFont;
private readonly XFont _regularFont;
private readonly XFont _smallFont;
public PdfBuilder(PdfExportSettings? settings = null)
{
try
{
EnsureFontResolverRegistered();
_settings = settings ?? new PdfExportSettings();
// Select first font from build output fonts folder (AppContext.BaseDirectory/fonts)
var chosenFamily = "Arial";
try
{
if (Directory.Exists(Global._instance.font_path))
{
var first = Directory.EnumerateFiles(Global._instance.font_path, "*.ttf").FirstOrDefault();
if (!string.IsNullOrEmpty(first))
chosenFamily = StripStyleSuffix(Path.GetFileNameWithoutExtension(first)) ?? chosenFamily;
}
}
catch (Exception ex)
{
Logger.Log($"Error while searching for fonts: {ex.Message}",Logger.LogType.Error);
chosenFamily = "Arial";
}
_boldFont = new XFont(chosenFamily, _settings.fontSize, XFontStyleEx.Bold);
_regularFont = new XFont(chosenFamily, _settings.fontSize, XFontStyleEx.Regular);
_smallFont = new XFont(chosenFamily, _settings.smallFontSize, XFontStyleEx.Regular);
}
catch (Exception ex)
{
Logger.Log($"Error while font resolving: {ex.Message}",Logger.LogType.Error);
}
}
private static void EnsureFontResolverRegistered()
{
try
{
if (GlobalFontSettings.FontResolver != null) return;
//var fontsDir = Path.Combine(AppContext.BaseDirectory, "fonts");
GlobalFontSettings.FontResolver = new StableFontResolver(Global._instance.font_path);
}
catch (Exception ex)
{
Logger.Log($"Error while ensuring font resolver register state: {ex.Message}",Logger.LogType.Error);
}
}
private static string StripStyleSuffix(string name)
{
try
{
if (string.IsNullOrEmpty(name)) return name;
var idx = name.IndexOf('-');
if (idx < 0) idx = name.IndexOf('_');
if (idx > 0)
return name.Substring(0, idx);
return name;
}
catch (Exception ex)
{
Logger.Log($"Error while stripping style suffix: {ex.Message}",Logger.LogType.Error);
}
return null;
}
/// <summary>
/// Creates a PDF document with address stickers from an AddressSet with a placeholder in the first cell.
/// </summary>
/// <param name="addressSetId">The ID of the AddressSet to use</param>
/// <param name="placeholderText">Text for the first cell (top-left)</param>
/// <param name="outputPath">Path where the PDF should be saved</param>
public void CreateAddressLabelPdfFromAddressSetWithPlaceholder(int addressSetId, string placeholderText,
string outputPath)
{
try
{
// Find the AddressSet by ID
var addressSet = Settings._instance.addressSets.GetAddressSetByID(addressSetId);
if (addressSet == null)
throw new ArgumentException($"AddressSet with ID {addressSetId} not found");
if (addressSet.KasPersons == null || addressSet.KasPersons.Count == 0)
throw new ArgumentException($"AddressSet with ID {addressSetId} contains no addresses");
// Generate markdown addresses from all KasPersons in the set
//var addresses = new string?[addressSet.KasPersons.Count];
var addresses = new List<string>();
// find customer (owner) to include sender_address
string senderLine = null;
try
{
var owner = Settings._instance.customers.customers.FirstOrDefault(c => c.ID == addressSet.owner_id);
if (owner != null && !string.IsNullOrWhiteSpace(owner.sender_address))
senderLine = "<font6>" + owner.sender_address.Replace("\n", " ").Trim() + "</font6>\n";
}
catch
{
senderLine = null;
}
for (var i = 0; i < addressSet.KasPersons.Count; i++)
{
var addr = AddressCreator.CreateFinalMarkdownString(addressSet.KasPersons[i].id);
if (string.IsNullOrWhiteSpace(addr)) continue;
if (!string.IsNullOrEmpty(senderLine))
addresses.Add(senderLine + (addr ?? ""));
else
addresses.Add(addr);
}
CreateAddressLabelPdfWithPlaceholder(addresses, placeholderText, outputPath);
}
catch (Exception ex)
{
Logger.Log($"Error while creating address label pdf from address set with placeholder: {ex.Message}",Logger.LogType.Error);
}
}
/// <summary>
/// Creates a PDF document with a single placeholder cell for other information.
/// </summary>
/// <param name="addresses">Array of addresses</param>
/// <param name="placeholderText">Text for the first cell (top-left)</param>
/// <param name="outputPath">Path where the PDF should be saved</param>
public void CreateAddressLabelPdfWithPlaceholder(List<string> addresses, string placeholderText, string outputPath)
{
try
{
if (addresses == null || addresses.Count == 0)
throw new ArgumentException("Addresses array cannot be null or empty");
var document = new PdfDocument();
var addressIndex = 0;
var isFirstCell = true;
while (addressIndex < addresses.Count || isFirstCell)
{
var page = document.AddPage();
page.Size = PageSize.A4;
using (var gfx = XGraphics.FromPdfPage(page))
{
DrawPageWithPlaceholder(gfx, addresses, ref addressIndex, ref isFirstCell, placeholderText);
}
}
document.Save(outputPath);
}
catch (Exception ex)
{
Logger.Log($"Error while creating address label pdf with placeholder: {ex.Message}",Logger.LogType.Error);
}
}
private void DrawPage(XGraphics gfx, List<string> addresses, ref int addressIndex)
{
for (var row = 0; row < _settings.rowsPerPage; row++)
{
for (var col = 0; col < _settings.columnsPerPage; col++)
{
if (addressIndex >= addresses.Count) break;
var x = MmToPoints(_settings.pageMarginLeftMm + col * GetCellWidthMm());
var y = MmToPoints(_settings.pageMarginTopMm + row * GetCellHeightMm());
DrawCell(gfx, x, y, addresses[addressIndex]);
addressIndex++;
}
if (addressIndex >= addresses.Count) break;
}
}
private void DrawPageWithPlaceholder(XGraphics gfx, List<string> addresses, ref int addressIndex,
ref bool isFirstCell, string placeholderText)
{
try
{
for (var row = 0; row < _settings.rowsPerPage; row++)
for (var col = 0; col < _settings.columnsPerPage; col++)
{
var x = MmToPoints(_settings.pageMarginLeftMm + col * GetCellWidthMm());
var y = MmToPoints(_settings.pageMarginTopMm + row * GetCellHeightMm());
// First cell: placeholder
if (isFirstCell)
{
DrawCell(gfx, x, y, placeholderText);
isFirstCell = false;
}
else if (addressIndex < addresses.Count)
{
DrawCell(gfx, x, y, addresses[addressIndex]);
addressIndex++;
}
else
{
DrawEmptyCell(gfx, x, y);
}
}
}
catch (Exception ex)
{
Logger.Log($"Error while drawing page with placholder: {ex.Message}",Logger.LogType.Error);
}
}
private void DrawCell(XGraphics gfx, double x, double y, string? address)
{
try
{
var cellWidthPoints = MmToPoints(GetCellWidthMm());
var cellHeightPoints = MmToPoints(GetCellHeightMm());
// Draw cell border
var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints);
gfx.DrawRectangle(XPens.Black, rect);
// Draw address content if available
if (!string.IsNullOrEmpty(address)) DrawMarkdownText(gfx, address, x, y, cellWidthPoints, cellHeightPoints);
}
catch (Exception ex)
{
Logger.Log($"Error while drawing cell: {ex.Message}",Logger.LogType.Error);
}
}
private void DrawEmptyCell(XGraphics gfx, double x, double y)
{
try
{
var cellWidthPoints = MmToPoints(GetCellWidthMm());
var cellHeightPoints = MmToPoints(GetCellHeightMm());
var rect = new XRect(x, y, cellWidthPoints, cellHeightPoints);
gfx.DrawRectangle(XPens.Black, rect);
}
catch (Exception ex)
{
Logger.Log($"Error while drawing empty cell: {ex.Message}",Logger.LogType.Error);
}
}
private void DrawMarkdownText(XGraphics gfx, string text, double x, double y, double cellWidth, double cellHeight)
{
try
{
var paddingLeftPoints = MmToPoints(_settings.cellPaddingLeftMm);
var paddingRightPoints = MmToPoints(_settings.cellPaddingRightMm);
var paddingTopPoints = MmToPoints(_settings.cellPaddingTopMm);
var paddingBottomPoints = MmToPoints(_settings.cellPaddingBottomMm);
var maxWidth = Math.Max(0, cellWidth - paddingLeftPoints - paddingRightPoints);
// Split text by newlines and remove empty trailing lines
var rawLines = text.Split(new[] { "\n" }, StringSplitOptions.None);
var lines = rawLines.Where(l => l != null).ToArray();
// Use a conservative line height in points
var lineHeight = _regularFont.Size * 1.2;
// Calculate total height of the text block
var totalHeight = lines.Length * lineHeight;
// Start drawing from the top of the cell (align addresses to top)
var startY = y + paddingTopPoints;
var currentY = startY;
foreach (var line in lines)
{
// Stop if we've reached the top boundary
if (currentY + lineHeight > y + cellHeight - paddingBottomPoints + 0.001)
break;
// Parse and draw the line with markdown support
DrawLineWithMarkdown(gfx, line, x + paddingLeftPoints, currentY, maxWidth);
currentY += lineHeight;
}
}
catch (Exception ex)
{
Logger.Log($"Error while drawing markdown text: {ex.Message}",Logger.LogType.Error);
}
}
private void DrawLineWithMarkdown(XGraphics gfx, string line, double x, double y, double maxWidth)
{
try
{
if (string.IsNullOrWhiteSpace(line)) return;
var currentX = x;
var i = 0;
while (i < line.Length)
{
if (currentX - x >= maxWidth)
break;
var remainingWidth = maxWidth - (currentX - x);
// Check for small-font tag <font6> ... </font6>
if (i <= line.Length - 7 && line.Substring(i, 7) == "<font6>")
{
var endTag = line.IndexOf("</font6>", i + 7, StringComparison.Ordinal);
if (endTag != -1)
{
var inner = line.Substring(i + 7, endTag - (i + 7));
if (!string.IsNullOrEmpty(inner))
{
var measuredSmall = gfx.MeasureString(inner, _smallFont);
if (measuredSmall.Width > remainingWidth)
{
inner = TruncateTextToWidth(gfx, inner, _smallFont, remainingWidth);
measuredSmall = gfx.MeasureString(inner, _smallFont);
}
gfx.DrawString(inner, _smallFont, XBrushes.Black,
new XRect(currentX, y, remainingWidth, _smallFont.Size * 1.2),
XStringFormats.TopLeft);
currentX += measuredSmall.Width;
}
i = endTag + 8; // move past </font6>
continue;
}
}
// Check for bold marker (**)
if (i < line.Length - 1 && line[i] == '*' && line[i + 1] == '*')
{
// Find closing **
var endIndex = line.IndexOf("**", i + 2, StringComparison.Ordinal);
if (endIndex != -1)
{
var boldText = line.Substring(i + 2, endIndex - (i + 2));
var measured = gfx.MeasureString(boldText, _boldFont);
if (measured.Width > remainingWidth)
{
boldText = TruncateTextToWidth(gfx, boldText, _boldFont, remainingWidth);
measured = gfx.MeasureString(boldText, _boldFont);
}
// Draw bold text and measure width accurately
gfx.DrawString(boldText, _boldFont, XBrushes.Black,
new XRect(currentX, y, remainingWidth, _boldFont.Size * 1.2),
XStringFormats.TopLeft);
currentX += measured.Width;
i = endIndex + 2;
continue;
}
}
// Regular text until next ** or end of line
var nextBoldIndex = line.IndexOf("**", i, StringComparison.Ordinal);
var textEnd = nextBoldIndex == -1 ? line.Length : nextBoldIndex;
var regularText = line.Substring(i, textEnd - i);
if (!string.IsNullOrEmpty(regularText))
{
var measured = gfx.MeasureString(regularText, _regularFont);
if (measured.Width > remainingWidth)
{
regularText = TruncateTextToWidth(gfx, regularText, _regularFont, remainingWidth);
measured = gfx.MeasureString(regularText, _regularFont);
}
gfx.DrawString(regularText, _regularFont, XBrushes.Black,
new XRect(currentX, y, remainingWidth, _regularFont.Size * 1.2), XStringFormats.TopLeft);
currentX += measured.Width;
}
i = textEnd;
}
}
catch (Exception ex)
{
Logger.Log($"Error while drawing markdown line: {ex.Message}",Logger.LogType.Error);
}
}
private string TruncateTextToWidth(XGraphics gfx, string text, XFont font, double maxWidth)
{
try
{
if (string.IsNullOrEmpty(text))
return text;
for (var len = text.Length; len > 0; len--)
{
var truncated = text.Substring(0, len);
var measured = gfx.MeasureString(truncated, font);
if (measured.Width <= maxWidth)
return truncated;
}
return string.Empty;
}
catch (Exception ex)
{
Logger.Log($"Error while truncating text to width: {ex.Message}",Logger.LogType.Error);
}
return null;
}
/// <summary>
/// Converts millimeters to points (1 mm = 2.834645669 points)
/// </summary>
private double MmToPoints(double mm)
{
return mm * 2.834645669;
}
private double GetCellWidthMm()
{
var availableWidthMm = 210d - _settings.pageMarginLeftMm - _settings.pageMarginRightMm;
return availableWidthMm / _settings.columnsPerPage;
}
private double GetCellHeightMm()
{
var availableHeightMm = 297d - _settings.pageMarginTopMm - _settings.pageMarginBottomMm;
return availableHeightMm / _settings.rowsPerPage;
}
// Configuration methods to allow customization
/// <summary>
/// Sets the cell dimensions in millimeters
/// </summary>
public void SetCellDimensions(double width, double height)
{
if (width <= 0 || height <= 0)
throw new ArgumentException("Cell dimensions must be positive");
}
/// <summary>
/// Sets the page margins in millimeters
/// </summary>
public void SetMargins(double left, double top, double right, double bottom)
{
if (left < 0 || top < 0 || right < 0 || bottom < 0)
throw new ArgumentException("Margins cannot be negative");
}
}
+163
View File
@@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using PdfSharp.Fonts;
using System.Globalization;
namespace Logof_Client;
public class StableFontResolver : IFontResolver
{
// family -> style -> bytes
private readonly Dictionary<string, Dictionary<string, byte[]>> _families = new(StringComparer.OrdinalIgnoreCase);
private readonly List<string> _orderedFamilies = new();
public StableFontResolver(string fontsDirectory)
{
try
{
if (string.IsNullOrEmpty(fontsDirectory)) return;
if (!Directory.Exists(fontsDirectory)) return;
var files = Directory.EnumerateFiles(fontsDirectory, "*.ttf");
foreach (var f in files)
{
try
{
var baseName = Path.GetFileNameWithoutExtension(f);
if (string.IsNullOrEmpty(baseName)) continue;
// Try to split family and style by hyphen or underscore
var family = baseName;
var style = "Regular";
var idx = baseName.IndexOf('-');
if (idx < 0) idx = baseName.IndexOf('_');
if (idx > 0)
{
family = baseName.Substring(0, idx);
style = baseName.Substring(idx + 1);
}
// Normalize common style names
style = NormalizeStyle(style);
var data = File.ReadAllBytes(f);
if (data == null || data.Length == 0) continue;
if (!_families.TryGetValue(family, out var dict))
{
dict = new Dictionary<string, byte[]>(StringComparer.OrdinalIgnoreCase);
_families[family] = dict;
_orderedFamilies.Add(family);
}
if (!dict.ContainsKey(style))
dict[style] = data;
}
catch
{
// ignore individual font load errors
}
}
}
catch (Exception ex)
{
Logger.Log($"Error while initializing FontResolver: {ex.Message}",Logger.LogType.Error);
}
}
private static string NormalizeStyle(string s)
{
try
{
if (string.IsNullOrEmpty(s)) return "Regular";
s = s.ToLowerInvariant();
if (s.Contains("bold") && s.Contains("italic")) return "BoldItalic";
if (s.Contains("bold")) return "Bold";
if (s.Contains("italic") || s.Contains("oblique")) return "Italic";
if (s.Contains("regular") || s == "r") return "Regular";
return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(s);
}
catch (Exception ex)
{
Logger.Log($"Error while normalizing style: {ex.Message}",Logger.LogType.Error);
}
return null;
}
public FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic)
{
try
{
// If requested family exists, pick corresponding style if available
string familyToUse = null;
if (!string.IsNullOrEmpty(familyName) && _families.ContainsKey(familyName))
familyToUse = familyName;
if (familyToUse == null && _orderedFamilies.Count > 0)
familyToUse = _orderedFamilies[0];
if (familyToUse == null)
return new FontResolverInfo("Arial");
var style = "Regular";
if (isBold && isItalic) style = "BoldItalic";
else if (isBold) style = "Bold";
else if (isItalic) style = "Italic";
// Face name format: Family#Style
return new FontResolverInfo($"{familyToUse}#{style}");
}
catch (Exception ex)
{
Logger.Log($"Error while resolving typeface: {ex.Message}",Logger.LogType.Error);
}
return null;
}
public byte[] GetFont(string faceName)
{
try
{
if (string.IsNullOrEmpty(faceName)) return null;
// faceName expected as "Family#Style" or just "Family"
var family = faceName;
var style = "Regular";
var idx = faceName.IndexOf('#');
if (idx >= 0)
{
family = faceName.Substring(0, idx);
style = faceName.Substring(idx + 1);
}
style = NormalizeStyle(style);
if (_families.TryGetValue(family, out var dict))
{
// Try exact style
if (dict.TryGetValue(style, out var data)) return data;
// Fallback order
if (style != "Regular" && dict.TryGetValue("Regular", out data)) return data;
if (dict.TryGetValue("Bold", out data)) return data;
if (dict.TryGetValue("Italic", out data)) return data;
// Any available
foreach (var kv in dict) return kv.Value;
}
return null;
}
catch (Exception ex)
{
Logger.Log($"Error while getting font: {ex.Message}",Logger.LogType.Error);
}
return [];
}
}
+17
View File
@@ -0,0 +1,17 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Logof_Client.Wiki.EditorWindow"
Title="Wiki Editor" MinWidth="600" MinHeight="350" WindowState="Maximized">
<Grid Margin="10,0,10,10">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Spacing="10" Orientation="Horizontal">
<Button x:Name="BtnSave" Content="Speichern" Click="BtnSave_OnClick" />
<Button x:Name="BtnSaveAs" Content="Speichern unter..." Click="BtnSaveAs_OnClick" />
<Button x:Name="BtnDelete" Content="Löschen" Click="BtnDelete_OnClick" />
</StackPanel>
<TextBox AcceptsTab="True" AcceptsReturn="True" Grid.Row="1" x:Name="TbContent" />
</Grid>
</Window>
+69
View File
@@ -0,0 +1,69 @@
using System;
using System.IO;
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace Logof_Client.Wiki;
public partial class EditorWindow : Window
{
public string filename = "";
public EditorWindow(string filename = "")
{
try
{
InitializeComponent();
this.filename = PathUtilities.NormalizeFileSystemPath(filename);
if (!string.IsNullOrWhiteSpace(this.filename) && File.Exists(this.filename))
{
var content = TbContent;
if (content != null) content.Text = File.ReadAllText(this.filename);
Title = "Wiki Editor - " + filename;
}
else if (!string.IsNullOrWhiteSpace(this.filename))
{
MessageBox.Show(null, "Die Datei existiert nicht", "Fehler");
Close();
}
} catch (Exception ex) { Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);}
}
private void BtnSave_OnClick(object? sender, RoutedEventArgs e)
{
try
{
File.WriteAllText(filename, TbContent.Text);
MainWindow._instance.PopulateNavTree();
}
catch (Exception ex)
{
MessageBox.Show(null,
"Es ist ein Fehler aufgetreten. Bitte senden Sie ihn über git.mypapercloud.de/fierke/logofclient ein:\n" +
ex.StackTrace, "Fehler");
Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);
}
}
private void BtnSaveAs_OnClick(object? sender, RoutedEventArgs e)
{
MessageBox.Show(null,
"Feature noch nicht implemetiert.\nErstelle neue Dateien unter " +
PathUtilities.NormalizeFileSystemPath(Global._instance.wiki_storage_path),
"Fehler");
}
private async void BtnDelete_OnClick(object? sender, RoutedEventArgs e)
{
var result = await MessageBox.Show(null, "Sicher?", "Sicher?", MessageBoxButton.YesNo);
if (result == MessageBoxResult.No) return;
try
{
File.Delete(filename);
} catch (Exception ex) { Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);}
MainWindow._instance.PopulateNavTree();
Close();
}
}
+140
View File
@@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Media;
using Markdig;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
using System.Text;
namespace Logof_Client.Wiki;
public static class MarkdownRenderer
{
public static Control Render(string markdown)
{
try
{
var panel = new StackPanel { Spacing = 6 };
if (string.IsNullOrWhiteSpace(markdown)) return panel;
var doc = Markdown.Parse(markdown);
foreach (var block in doc)
{
switch (block)
{
case HeadingBlock hb:
{
var text = GetInlineText(hb.Inline);
var tb = new TextBlock
{
Text = text,
FontWeight = FontWeight.Bold,
Margin = new Avalonia.Thickness(0, hb.Level == 1 ? 6 : 2, 0, 2)
};
tb.FontSize = hb.Level switch { 1 => 22, 2 => 18, 3 => 16, _ => 14 };
panel.Children.Add(tb);
break;
}
case ParagraphBlock pb:
{
var text = GetInlineText(pb.Inline);
var tb = new TextBlock { Text = text, TextWrapping = Avalonia.Media.TextWrapping.Wrap };
panel.Children.Add(tb);
break;
}
case FencedCodeBlock cb:
{
var sb = new StringBuilder();
foreach (var line in cb.Lines.Lines)
{
sb.Append(line.ToString());
}
var codeBox = new TextBox
{
Text = sb.ToString(),
FontFamily = "Consolas, monospace",
IsReadOnly = true,
AcceptsReturn = true
};
panel.Children.Add(codeBox);
break;
}
case ListBlock lb:
{
var sp = new StackPanel { Spacing = 2 };
var number = 1;
foreach (var item in lb)
{
if (item is ListItemBlock lib)
{
var itemText = new StringBuilder();
foreach (var sub in lib)
{
if (sub is ParagraphBlock pp)
itemText.Append(GetInlineText(pp.Inline));
}
var tb = new TextBlock { Text = (lb.IsOrdered ? (number++ + ". ") : "• ") + itemText.ToString() };
sp.Children.Add(tb);
}
}
panel.Children.Add(sp);
break;
}
default:
{
// fallback: raw text
panel.Children.Add(new TextBlock { Text = block.ToString() });
break;
}
}
}
return panel;
} catch (Exception ex) { Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);}
return new Panel();
}
private static string GetInlineText(ContainerInline? container)
{
try
{
if (container == null) return string.Empty;
var sb = new StringBuilder();
foreach (var inline in container)
{
switch (inline)
{
case LiteralInline li:
sb.Append(li.Content.ToString());
break;
case EmphasisInline ei:
sb.Append(GetInlineText(ei));
break;
case CodeInline ci:
sb.Append(ci.Content);
break;
case LinkInline li:
sb.Append(GetInlineText(li));
break;
case LineBreakInline:
sb.Append("\n");
break;
default:
sb.Append(inline.ToString());
break;
}
}
return sb.ToString();
} catch (Exception ex) { Logger.Log($"Error while : {ex.Message}",Logger.LogType.Error);}
return null;
}
}
+13
View File
@@ -0,0 +1,13 @@
using System.Collections.ObjectModel;
namespace Logof_Client.Wiki;
public class WikiItem
{
public string Name { get; set; }
public string Path { get; set; }
public bool IsFolder { get; set; }
public ObservableCollection<WikiItem> Children { get; } = new();
public override string ToString() => Name;
}
+74
View File
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Logof_Client.Wiki;
public class WikiService
{
public WikiService(string wikiRoot = null)
{
// prefer global wiki storage path if configured
if (Global._instance != null && !string.IsNullOrWhiteSpace(Global._instance.wiki_storage_path))
{
var cfg = PathUtilities.NormalizeFileSystemPath(Global._instance.wiki_storage_path);
WikiRootFullPath = Path.IsPathRooted(cfg)
? cfg
: Path.Combine(Directory.GetCurrentDirectory(), cfg);
WikiRoot = WikiRootFullPath;
}
else
{
if (wikiRoot == null)
wikiRoot = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"logofclient",
"wiki");
wikiRoot = PathUtilities.NormalizeFileSystemPath(wikiRoot);
WikiRoot = wikiRoot;
WikiRootFullPath = Path.IsPathRooted(wikiRoot)
? wikiRoot
: Path.Combine(Directory.GetCurrentDirectory(), wikiRoot);
}
}
public string WikiRoot { get; }
public string WikiRootFullPath { get; }
public List<WikiItem> GetRootItems()
{
var list = new List<WikiItem>();
if (!Directory.Exists(WikiRootFullPath)) return list;
var dirInfo = new DirectoryInfo(WikiRootFullPath);
// Add folders
foreach (var dir in dirInfo.GetDirectories()) list.Add(BuildFolderItem(dir));
// Add files in root
foreach (var file in dirInfo.GetFiles("*.md"))
list.Add(new WikiItem { Name = file.Name, Path = file.FullName, IsFolder = false });
return list.OrderBy(i => i.IsFolder ? 0 : 1).ToList();
}
private WikiItem BuildFolderItem(DirectoryInfo dir)
{
var node = new WikiItem { Name = dir.Name, Path = dir.FullName, IsFolder = true };
foreach (var subdir in dir.GetDirectories()) node.Children.Add(BuildFolderItem(subdir));
foreach (var file in dir.GetFiles("*.md"))
node.Children.Add(new WikiItem { Name = file.Name, Path = file.FullName, IsFolder = false });
return node;
}
public Task<string?> LoadFileContentAsync(string path)
{
if (!File.Exists(path)) return Task.FromResult<string?>(null);
return File.ReadAllTextAsync(path);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.
@@ -1,4 +0,0 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")]
@@ -1 +0,0 @@
7c9a93128a982a1e9b3ed6a98b955291256152e83f2d8c0a81b93bfa11851fbe
Binary file not shown.
@@ -1,22 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Logof Client")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+412de903f5b3dfc96eb50f28565ef826c68c4250")]
[assembly: System.Reflection.AssemblyProductAttribute("Logof Client")]
[assembly: System.Reflection.AssemblyTitleAttribute("Logof Client")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// Generated by the MSBuild WriteCodeFragment class.
@@ -1 +0,0 @@
106552b4e3cc179639dda17b27641bd40928d1e6b4629b5c3baae2909de72cef
@@ -1,37 +0,0 @@
is_global = true
build_property.AvaloniaNameGeneratorIsEnabled = true
build_property.AvaloniaNameGeneratorBehavior = InitializeComponent
build_property.AvaloniaNameGeneratorDefaultFieldModifier = internal
build_property.AvaloniaNameGeneratorFilterByPath = *
build_property.AvaloniaNameGeneratorFilterByNamespace = *
build_property.AvaloniaNameGeneratorViewFileNamingStrategy = NamespaceAndClassName
build_property.AvaloniaNameGeneratorAttachDevTools = true
build_property.TargetFramework = net9.0
build_property.TargetPlatformMinVersion =
build_property.UsingMicrosoftNETSdkWeb =
build_property.ProjectTypeGuids =
build_property.InvariantGlobalization =
build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = Logof_Client
build_property.ProjectDir = /home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.EffectiveAnalysisLevelStyle = 9.0
build_property.EnableCodeStyleSeverity =
[/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/App.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/MainWindow.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/MessageBox.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/ProgressWindow.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
[/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/ResultWindow.axaml]
build_metadata.AdditionalFiles.SourceItemGroup = AvaloniaXaml
Binary file not shown.
-103
View File
@@ -1,103 +0,0 @@
{
"format": 1,
"restore": {
"/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/Logof Client.csproj": {}
},
"projects": {
"/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/Logof Client.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/Logof Client.csproj",
"projectName": "Logof Client",
"projectPath": "/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/Logof Client.csproj",
"packagesPath": "/home/fierke/.nuget/packages/",
"outputPath": "/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/obj/",
"projectStyle": "PackageReference",
"configFilePaths": [
"/home/fierke/.nuget/NuGet/NuGet.Config"
],
"originalTargetFrameworks": [
"net9.0"
],
"sources": {
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net9.0": {
"targetAlias": "net9.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "9.0.100"
},
"frameworks": {
"net9.0": {
"targetAlias": "net9.0",
"dependencies": {
"Avalonia": {
"target": "Package",
"version": "[11.3.2, )"
},
"Avalonia.Controls.DataGrid": {
"target": "Package",
"version": "[11.3.2, )"
},
"Avalonia.Desktop": {
"target": "Package",
"version": "[11.3.2, )"
},
"Avalonia.Diagnostics": {
"target": "Package",
"version": "[11.3.2, )"
},
"Avalonia.Fonts.Inter": {
"target": "Package",
"version": "[11.3.2, )"
},
"Avalonia.Themes.Fluent": {
"target": "Package",
"version": "[11.3.2, )"
},
"Lucide.Avalonia": {
"target": "Package",
"version": "[0.1.35, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"downloadDependencies": [
{
"name": "Microsoft.AspNetCore.App.Ref",
"version": "[9.0.8, 9.0.8]"
}
],
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "/usr/share/dotnet/sdk/9.0.109/PortableRuntimeIdentifierGraph.json"
}
}
}
}
}
-24
View File
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">/home/fierke/.nuget/packages/</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">/home/fierke/.nuget/packages/</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="/home/fierke/.nuget/packages/" />
</ItemGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)skiasharp.nativeassets.webassembly/2.88.9/buildTransitive/netstandard1.0/SkiaSharp.NativeAssets.WebAssembly.props" Condition="Exists('$(NuGetPackageRoot)skiasharp.nativeassets.webassembly/2.88.9/buildTransitive/netstandard1.0/SkiaSharp.NativeAssets.WebAssembly.props')" />
<Import Project="$(NuGetPackageRoot)avalonia/11.3.2/buildTransitive/Avalonia.props" Condition="Exists('$(NuGetPackageRoot)avalonia/11.3.2/buildTransitive/Avalonia.props')" />
<Import Project="$(NuGetPackageRoot)harfbuzzsharp.nativeassets.webassembly/8.3.1.1/buildTransitive/netstandard1.0/HarfBuzzSharp.NativeAssets.WebAssembly.props" Condition="Exists('$(NuGetPackageRoot)harfbuzzsharp.nativeassets.webassembly/8.3.1.1/buildTransitive/netstandard1.0/HarfBuzzSharp.NativeAssets.WebAssembly.props')" />
</ImportGroup>
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<PkgAvalonia_BuildServices Condition=" '$(PkgAvalonia_BuildServices)' == '' ">/home/fierke/.nuget/packages/avalonia.buildservices/0.0.31</PkgAvalonia_BuildServices>
<PkgAvalonia Condition=" '$(PkgAvalonia)' == '' ">/home/fierke/.nuget/packages/avalonia/11.3.2</PkgAvalonia>
</PropertyGroup>
</Project>
-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)skiasharp.nativeassets.webassembly/2.88.9/buildTransitive/netstandard1.0/SkiaSharp.NativeAssets.WebAssembly.targets" Condition="Exists('$(NuGetPackageRoot)skiasharp.nativeassets.webassembly/2.88.9/buildTransitive/netstandard1.0/SkiaSharp.NativeAssets.WebAssembly.targets')" />
<Import Project="$(NuGetPackageRoot)avalonia.buildservices/0.0.31/buildTransitive/Avalonia.BuildServices.targets" Condition="Exists('$(NuGetPackageRoot)avalonia.buildservices/0.0.31/buildTransitive/Avalonia.BuildServices.targets')" />
<Import Project="$(NuGetPackageRoot)avalonia/11.3.2/buildTransitive/Avalonia.targets" Condition="Exists('$(NuGetPackageRoot)avalonia/11.3.2/buildTransitive/Avalonia.targets')" />
<Import Project="$(NuGetPackageRoot)harfbuzzsharp.nativeassets.webassembly/8.3.1.1/buildTransitive/netstandard1.0/HarfBuzzSharp.NativeAssets.WebAssembly.targets" Condition="Exists('$(NuGetPackageRoot)harfbuzzsharp.nativeassets.webassembly/8.3.1.1/buildTransitive/netstandard1.0/HarfBuzzSharp.NativeAssets.WebAssembly.targets')" />
</ImportGroup>
</Project>
File diff suppressed because it is too large Load Diff
-40
View File
@@ -1,40 +0,0 @@
{
"version": 2,
"dgSpecHash": "aE0qKetUvS8=",
"success": true,
"projectFilePath": "/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/Logof Client.csproj",
"expectedPackageFiles": [
"/home/fierke/.nuget/packages/avalonia/11.3.2/avalonia.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.angle.windows.natives/2.1.25547.20250602/avalonia.angle.windows.natives.2.1.25547.20250602.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.buildservices/0.0.31/avalonia.buildservices.0.0.31.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.controls.colorpicker/11.3.2/avalonia.controls.colorpicker.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.controls.datagrid/11.3.2/avalonia.controls.datagrid.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.desktop/11.3.2/avalonia.desktop.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.diagnostics/11.3.2/avalonia.diagnostics.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.fonts.inter/11.3.2/avalonia.fonts.inter.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.freedesktop/11.3.2/avalonia.freedesktop.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.native/11.3.2/avalonia.native.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.remote.protocol/11.3.2/avalonia.remote.protocol.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.skia/11.3.2/avalonia.skia.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.themes.fluent/11.3.2/avalonia.themes.fluent.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.themes.simple/11.3.2/avalonia.themes.simple.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.win32/11.3.2/avalonia.win32.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/avalonia.x11/11.3.2/avalonia.x11.11.3.2.nupkg.sha512",
"/home/fierke/.nuget/packages/harfbuzzsharp/8.3.1.1/harfbuzzsharp.8.3.1.1.nupkg.sha512",
"/home/fierke/.nuget/packages/harfbuzzsharp.nativeassets.linux/8.3.1.1/harfbuzzsharp.nativeassets.linux.8.3.1.1.nupkg.sha512",
"/home/fierke/.nuget/packages/harfbuzzsharp.nativeassets.macos/8.3.1.1/harfbuzzsharp.nativeassets.macos.8.3.1.1.nupkg.sha512",
"/home/fierke/.nuget/packages/harfbuzzsharp.nativeassets.webassembly/8.3.1.1/harfbuzzsharp.nativeassets.webassembly.8.3.1.1.nupkg.sha512",
"/home/fierke/.nuget/packages/harfbuzzsharp.nativeassets.win32/8.3.1.1/harfbuzzsharp.nativeassets.win32.8.3.1.1.nupkg.sha512",
"/home/fierke/.nuget/packages/lucide.avalonia/0.1.35/lucide.avalonia.0.1.35.nupkg.sha512",
"/home/fierke/.nuget/packages/microcom.runtime/0.11.0/microcom.runtime.0.11.0.nupkg.sha512",
"/home/fierke/.nuget/packages/skiasharp/2.88.9/skiasharp.2.88.9.nupkg.sha512",
"/home/fierke/.nuget/packages/skiasharp.nativeassets.linux/2.88.9/skiasharp.nativeassets.linux.2.88.9.nupkg.sha512",
"/home/fierke/.nuget/packages/skiasharp.nativeassets.macos/2.88.9/skiasharp.nativeassets.macos.2.88.9.nupkg.sha512",
"/home/fierke/.nuget/packages/skiasharp.nativeassets.webassembly/2.88.9/skiasharp.nativeassets.webassembly.2.88.9.nupkg.sha512",
"/home/fierke/.nuget/packages/skiasharp.nativeassets.win32/2.88.9/skiasharp.nativeassets.win32.2.88.9.nupkg.sha512",
"/home/fierke/.nuget/packages/system.io.pipelines/8.0.0/system.io.pipelines.8.0.0.nupkg.sha512",
"/home/fierke/.nuget/packages/tmds.dbus.protocol/0.21.2/tmds.dbus.protocol.0.21.2.nupkg.sha512",
"/home/fierke/.nuget/packages/microsoft.aspnetcore.app.ref/9.0.8/microsoft.aspnetcore.app.ref.9.0.8.nupkg.sha512"
],
"logs": []
}
-1
View File
@@ -1 +0,0 @@
"restore":{"projectUniqueName":"/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/Logof Client.csproj","projectName":"Logof Client","projectPath":"/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/Logof Client.csproj","outputPath":"/home/fierke/Nextcloud/Documents/source/repos/logofclient/Logof Client/obj/","projectStyle":"PackageReference","originalTargetFrameworks":["net9.0"],"sources":{"https://api.nuget.org/v3/index.json":{}},"frameworks":{"net9.0":{"targetAlias":"net9.0","projectReferences":{}}},"warningProperties":{"warnAsError":["NU1605"]},"restoreAuditProperties":{"enableAudit":"true","auditLevel":"low","auditMode":"direct"},"SdkAnalysisLevel":"9.0.100"}"frameworks":{"net9.0":{"targetAlias":"net9.0","dependencies":{"Avalonia":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Controls.DataGrid":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Desktop":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Diagnostics":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Fonts.Inter":{"target":"Package","version":"[11.3.2, )"},"Avalonia.Themes.Fluent":{"target":"Package","version":"[11.3.2, )"},"Lucide.Avalonia":{"target":"Package","version":"[0.1.35, )"}},"imports":["net461","net462","net47","net471","net472","net48","net481"],"assetTargetFallback":true,"warn":true,"downloadDependencies":[{"name":"Microsoft.AspNetCore.App.Ref","version":"[9.0.8, 9.0.8]"}],"frameworkReferences":{"Microsoft.NETCore.App":{"privateAssets":"all"}},"runtimeIdentifierGraphPath":"/usr/share/dotnet/sdk/9.0.109/PortableRuntimeIdentifierGraph.json"}}
-1
View File
@@ -1 +0,0 @@
17581228084312842
-1
View File
@@ -1 +0,0 @@
17581228084312842